"""Agent – wraps an LLM, tools, and config into a single callable unit."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from thryve.core.agent.loop import AgentLoop
from thryve.core.agent.models import LoopConfig, StopReason, TurnResult
from thryve.core.backends.adapter import BackendAdapter
from thryve.core.context.models import Message
from thryve.core.tools.models import Tool
from thryve.utils import get_logger

if TYPE_CHECKING:
    from thryve.core.context.manager import ContextManager

logger = get_logger("agent")


class Agent:
    """A single autonomous agent.

    An agent owns:

    * A backend adapter (LLM).
    * A set of tools it may invoke.
    * A loop configuration.
    * An optional system prompt.
    """

    def __init__(
        self,
        name: str,
        llm: BackendAdapter,
        tools: list[Tool] | None = None,
        config: LoopConfig | None = None,
        system_prompt: str = "",
    ) -> None:
        self.name = name
        self.llm = llm
        self.tools = tools or []
        self.config = config or LoopConfig()
        self.system_prompt = system_prompt

    # ------------------------------------------------------------------
    # Execution
    # ------------------------------------------------------------------

    async def run(
        self,
        task: str,
        context: ContextManager | None = None,
        **llm_kwargs: Any,
    ) -> TurnResult:
        """Execute *task* using the agent's LLM and tools.

        If a *context* manager is supplied, messages are drawn from (and
        written back to) it.  Otherwise a fresh message list is created.
        """
        # Build initial messages
        messages: list[Message] = []
        if self.system_prompt:
            messages.append(Message(role="system", content=self.system_prompt))

        # Incorporate existing context
        if context is not None:
            messages.extend(context.messages)

        # Append user task
        messages.append(Message(role="user", content=task))

        # Run the loop
        loop = AgentLoop(self.llm, self.tools, self.config)
        result = await loop.execute(messages, **llm_kwargs)

        # Persist conversation back into context if available
        if context is not None:
            # Messages were mutated in-place by the loop; sync back.
            context.messages = messages

        return result

    # ------------------------------------------------------------------
    # Repr
    # ------------------------------------------------------------------

    def __repr__(self) -> str:
        return (
            f"Agent(name={self.name!r}, model={self.llm.get_info().model!r}, "
            f"tools={[t.name for t in self.tools]})"
        )
