"""Agent — produces answers using the current skillbook of strategies.

Uses PydanticAI for structured output validation with automatic retry
and error feedback.
"""

from __future__ import annotations

import logging
from typing import Any, Optional, Union

from pydantic_ai import Agent as PydanticAgent
from pydantic_ai.settings import ModelSettings

from ..core.context import SkillbookView
from ..core.outputs import AgentOutput
from ..core.skillbook import Skillbook
from ..providers.pydantic_ai import resolve_model
from .helpers import extract_cited_skill_ids, format_optional
from .prompts import AGENT_PROMPT

logger = logging.getLogger(__name__)


class Agent:
    """Produces answers using the current skillbook of strategies.

    The Agent is one of three core ACE roles. It takes a question and
    uses the accumulated strategies in the skillbook to produce reasoned
    answers.

    Args:
        model: Model identifier string. Supports any LiteLLM model
            (e.g. ``"gpt-4o-mini"``, ``"openrouter/anthropic/claude-3.5-sonnet"``)
            or a PydanticAI-native identifier (e.g. ``"openai:gpt-4o"``).
        prompt_template: Custom prompt template (defaults to
            :data:`AGENT_PROMPT`).
        max_retries: Maximum retries for structured output validation.
            PydanticAI feeds validation errors back to the LLM on retry.
        model_settings: Optional PydanticAI ``ModelSettings`` for
            temperature, max_tokens, etc.

    Example::

        agent = Agent("gpt-4o-mini")
        output = agent.generate(
            question="What is the capital of France?",
            context="Answer concisely",
            skillbook=skillbook,
        )
        print(output.final_answer)  # "Paris"
    """

    def __init__(
        self,
        model: str,
        *,
        prompt_template: str = AGENT_PROMPT,
        max_retries: int = 3,
        model_settings: ModelSettings | None = None,
    ) -> None:
        self._prompt_template = prompt_template
        self._agent = PydanticAgent(
            resolve_model(model),
            output_type=AgentOutput,
            retries=max_retries,
            model_settings=model_settings,
            defer_model_check=True,
        )

    def generate(
        self,
        *,
        question: str,
        context: Optional[str],
        skillbook: Union[SkillbookView, Skillbook],
        reflection: Optional[str] = None,
        **kwargs: Any,
    ) -> AgentOutput:
        """Generate an answer using skillbook strategies.

        This method signature matches :class:`AgentLike`.

        Args:
            question: The question to answer.
            context: Additional context or requirements.
            skillbook: Current skillbook (needs ``as_prompt``).
            reflection: Optional reflection from a previous attempt.
            **kwargs: Accepted for protocol compatibility but not forwarded.

        Returns:
            :class:`AgentOutput` with reasoning, final_answer, and
            cited skill_ids.
        """
        prompt = self._prompt_template.format(
            skillbook=skillbook.as_prompt() or "(empty skillbook)",
            reflection=format_optional(reflection),
            question=question,
            context=format_optional(context),
        )

        result = self._agent.run_sync(prompt)
        output = result.output
        output.skill_ids = extract_cited_skill_ids(output.reasoning)
        output.raw = _extract_usage(result)
        return output


def _extract_usage(result: Any) -> dict[str, Any]:
    """Extract usage metadata from a PydanticAI run result."""
    usage = result.usage()
    return {
        "usage": {
            "prompt_tokens": usage.input_tokens or 0,
            "completion_tokens": usage.output_tokens or 0,
            "total_tokens": usage.total_tokens or 0,
        },
    }
