from __future__ import annotations

from collections.abc import AsyncIterator, Iterator

from supply_agent.agent.loop import AgentLoop
from supply_agent.config import Settings, get_settings
from supply_agent.llm.client import LLMClient
from supply_agent.logging.logger import AgentLogger
from supply_agent.logging.publish import publish_run_artifacts
from supply_agent.skills.registry import SkillRegistry
from supply_agent.tools.registry import ToolRegistry
from supply_agent.types import (
    AgentEvent,
    AgentEventType,
    AgentResult,
    CompletionGuard,
    Message,
    Role,
)


DEFAULT_SYSTEM_PROMPT = """\
你是一个通用 AI 助手，根据用户的要求理解任务并完成目标。

## 工作方式
1. 先理解用户真正想要什么，必要时先澄清关键信息
2. 将复杂任务拆解为可执行的步骤，逐步推进
3. 需要外部信息或操作时，主动调用可用工具
4. 完成后给出清晰、可直接使用的结果

## 工具使用
- 你有权访问一组工具，仅在确实需要时调用
- 调用前想清楚：需要什么输入、期望得到什么输出
- 工具返回后，结合结果继续推理，不要重复无效调用

## 技能（Skills）
- 若系统提示中列出了可用 Skills，且任务与某项技能匹配，先调用 `load_skill` 加载对应技能
- 技能包含专业流程和输出规范，加载后严格遵循，不要自行猜测格式

## 输出要求
- 回答紧扣用户要求，避免无关内容
- 结构清晰，重点突出，便于阅读和直接使用
- 无法完成时，说明原因并给出可行的替代方案
"""


class Agent:
    """
    Main Agent class — orchestrates LLM, tools, and skills.

    Usage:
        agent = Agent(model="anthropic/claude-sonnet-5")
        agent.tools.register(my_tool)
        result = agent.run("What is 2+2?")
    """

    def __init__(
        self,
        settings: Settings | None = None,
        *,
        name: str | None = None,
        model: str | None = None,
        system_prompt: str | None = None,
        tools: ToolRegistry | None = None,
        skills: SkillRegistry | None = None,
        max_iterations: int | None = None,
        temperature: float | None = None,
        reasoning_effort: str | None = None,
        logger: AgentLogger | None = None,
        completion_guard: CompletionGuard | None = None,
    ) -> None:
        self.name = name
        self.settings = settings or get_settings()
        self.logger = logger or AgentLogger(
            self.settings.logs_dir,
            enabled=self.settings.log_enabled,
        )
        self.llm = LLMClient(
            self.settings,
            logger=self.logger,
            reasoning_effort=reasoning_effort,
        )
        self.tools = tools or ToolRegistry()
        self.skills = skills or SkillRegistry(self.settings.skills_dir)
        self.system_prompt = system_prompt or DEFAULT_SYSTEM_PROMPT
        self.max_iterations = max_iterations or self.settings.agent_max_iterations
        self.temperature = temperature
        self.completion_guard = completion_guard

        if model:
            self.llm.set_model(model)

        self._active_skills: list[str] = []
        self._setup_builtin_tools()

    @property
    def model(self) -> str:
        return self.llm.model

    @model.setter
    def model(self, value: str) -> None:
        self.llm.set_model(value)

    def _setup_builtin_tools(self) -> None:
        """Register built-in tools for skill loading."""

        def load_skill(name: str) -> str:
            """Load a skill by name to get specialized instructions for a task."""
            context = self.skills.get_skill_context(name)
            if context is None:
                available = ", ".join(self.skills.list_skills()) or "none"
                return f'{{"error": "Skill \'{name}\' not found. Available: {available}"}}'
            if name not in self._active_skills:
                self._active_skills.append(name)
            return context

        load_skill.__doc__ = "Load a skill by name to get specialized instructions for a task."
        from supply_agent.tools.base import tool as tool_decorator

        decorated = tool_decorator(name="load_skill")(load_skill)
        self.tools.register(decorated, name="load_skill")

    def _build_system_message(self) -> Message:
        parts = [self.system_prompt]
        catalog = self.skills.get_catalog()
        if catalog:
            parts.append(catalog)
        for skill_name in self._active_skills:
            ctx = self.skills.get_skill_context(skill_name)
            if ctx:
                parts.append(ctx)
        return Message(role=Role.SYSTEM, content="\n\n".join(parts))

    def _create_loop(self, messages: list[Message]) -> AgentLoop:
        return AgentLoop(
            llm=self.llm,
            tools=self.tools,
            system_message=self._build_system_message(),
            system_message_builder=self._build_system_message,
            messages=messages,
            max_iterations=self.max_iterations,
            temperature=self.temperature,
            logger=self.logger,
            active_skills=self._active_skills,
            completion_guard=self.completion_guard,
        )

    def _finish_run(self, result: AgentResult) -> None:
        """Close run logs and publish visualization artifacts."""
        self.logger.end_run(result)
        publish_run_artifacts(self.logger)

    def run(self, user_input: str, *, history: list[Message] | None = None) -> AgentResult:
        """Run the agent synchronously with a user message."""
        self.logger.start_run(user_input, model=self.model, agent_name=self.name)
        messages = list(history or [])
        messages.append(Message(role=Role.USER, content=user_input))
        loop = self._create_loop(messages)
        result = loop.run()
        self._finish_run(result)
        return result

    async def arun_core(
        self, user_input: str, *, history: list[Message] | None = None
    ) -> AgentResult:
        """Run the agent loop without closing logs or publishing artifacts."""
        self.logger.start_run(user_input, model=self.model, agent_name=self.name)
        messages = list(history or [])
        messages.append(Message(role=Role.USER, content=user_input))
        loop = self._create_loop(messages)
        return await loop.arun()

    async def arun(
        self, user_input: str, *, history: list[Message] | None = None
    ) -> AgentResult:
        """Run the agent asynchronously."""
        result = await self.arun_core(user_input, history=history)
        self._finish_run(result)
        return result

    def stream(
        self, user_input: str, *, history: list[Message] | None = None
    ) -> Iterator[AgentEvent]:
        """Stream agent events during execution."""
        self.logger.start_run(user_input, model=self.model, agent_name=self.name)
        messages = list(history or [])
        messages.append(Message(role=Role.USER, content=user_input))
        loop = self._create_loop(messages)
        final_result: AgentResult | None = None
        for event in loop.stream():
            if event.type == AgentEventType.DONE:
                data = event.data
                final_result = AgentResult(
                    content=data.get("content", ""),
                    messages=loop.messages,
                    iterations=data.get("iterations", 0),
                    tool_calls_made=data.get("tool_calls_made", 0),
                    skills_used=list(self._active_skills),
                )
            yield event
        if final_result:
            self._finish_run(final_result)

    async def astream(
        self, user_input: str, *, history: list[Message] | None = None
    ) -> AsyncIterator[AgentEvent]:
        """Async stream agent events."""
        self.logger.start_run(user_input, model=self.model, agent_name=self.name)
        messages = list(history or [])
        messages.append(Message(role=Role.USER, content=user_input))
        loop = self._create_loop(messages)
        final_result: AgentResult | None = None
        async for event in loop.astream():
            if event.type == AgentEventType.DONE:
                data = event.data
                final_result = AgentResult(
                    content=data.get("content", ""),
                    messages=loop.messages,
                    iterations=data.get("iterations", 0),
                    tool_calls_made=data.get("tool_calls_made", 0),
                    skills_used=list(self._active_skills),
                )
            yield event
        if final_result:
            self._finish_run(final_result)

    def reset(self) -> None:
        """Reset active skills state."""
        self._active_skills.clear()
