"""Base Agent layer for Fluxibly.

Defines BaseAgent (abstract), AgentConfig, HandoffConfig, and AgentResponse.
All Agent implementations extend BaseAgent and implement forward().
"""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel, Field

from fluxibly.llm.base import LLMConfig
from fluxibly.logging import Logger
from fluxibly.schema.response import LLMResponse

# ── HandoffConfig ─────────────────────────────────────────────────


class HandoffConfig(BaseModel):
    """Configuration for a handoff target with custom options."""

    agent: str = Field(
        ..., description="Agent name/path — resolved via load_agent_config()"
    )
    tool_name: str | None = Field(
        default=None,
        description="Custom tool name (default: transfer_to_<name>)",
    )
    tool_description: str | None = Field(
        default=None,
        description="Custom description (default: agent.description)",
    )
    input_filter: str | None = Field(
        default=None,
        description='Filter for context passed to receiving agent: "remove_tools" | "last_message_only" | "path::func" | None',
    )
    is_enabled: bool = Field(
        default=True, description="Whether this handoff is currently active"
    )


# ── AgentConfig ───────────────────────────────────────────────────


class AgentConfig(BaseModel):
    """Configuration for Agent initialization."""

    name: str = Field(..., description="Agent identifier")
    description: str | None = Field(
        default=None,
        description="Human-readable description. Used as default for handoff/agent-as-tool tool descriptions.",
    )

    # --- Agent class ---
    agent_class: str = Field(
        default="SimpleAgent",
        description='Which agent class to instantiate. Built-in: "SimpleAgent". Custom: "module.path::ClassName".',
    )

    # --- LLM(s) ---
    llm: LLMConfig = Field(..., description="Primary LLM configuration")
    additional_llms: dict[str, LLMConfig] = Field(
        default_factory=dict, description="Named additional LLMs"
    )

    # --- Prompts ---
    system_prompt: str | None = Field(
        default=None, description="System prompt (supports {param} templates)"
    )
    user_prompt: str | None = Field(
        default=None,
        description="User prompt template (supports {param} and {input})",
    )
    prompt_parsers: dict[str, str] | None = Field(
        default=None,
        description="Custom parser per prompt, e.g. {'system': 'path/to.py::fn'}",
    )

    # --- Output ---
    output_type: str | None = Field(
        default=None,
        description="Structured output type (for deterministic routing)",
    )

    # --- Tools ---
    tools: list[str | dict[str, Any]] = Field(
        default_factory=list,
        description="Tool references: str (name/path) or dict (resource bundle {name, type, resources, structure})",
    )

    # --- Handoffs (one-way transfer of control) ---
    handoffs: list[str | HandoffConfig | dict[str, Any]] = Field(
        default_factory=list,
        description="Handoff targets: str (agent name/path), HandoffConfig, or resource bundle dict",
    )

    # --- Agents-as-Tools (delegate & return) ---
    agents: list[str | dict[str, Any]] = Field(
        default_factory=list,
        description="Agent-as-tool references: str (agent name/path) or resource bundle dict",
    )

    # --- Skills ---
    skills: list[str | dict[str, Any]] = Field(
        default_factory=list,
        description="Skill references: str (skill name/path) or resource bundle dict",
    )

    # --- Execution ---
    max_retries: int = Field(
        default=3, description="Max retries for LLM calls"
    )
    max_tool_retries: int = Field(
        default=2, description="Max retries for individual tool calls"
    )
    max_tool_iterations: int = Field(
        default=10, description="Max agentic loop iterations"
    )
    max_tool_workers: int = Field(
        default=1, description="Concurrent tool executions (1=sequential)"
    )

    # --- Sandbox ---
    sandbox: dict[str, Any] | None = Field(
        default=None,
        description="Sandbox configuration for shell execution. "
        "Keys: allow_write, deny_read, allowed_domains, denied_domains, "
        "timeout_seconds, use_uv, python_version, packages.",
    )

    # --- Logging ---
    log_level: str = Field(
        default="INFO", description="'DEBUG' | 'INFO' | 'WARNING' | 'ERROR'"
    )


# ── AgentResponse ─────────────────────────────────────────────────


class AgentResponse(BaseModel):
    """Standardized response from any Agent forward() call."""

    content: LLMResponse = Field(
        ..., description="The LLM response (output_text, tool_calls, etc.)"
    )
    metadata: dict[str, Any] = Field(
        default_factory=dict,
        description="Agent-level metadata (timing, iterations, etc.)",
    )
    handoff_to: Any | None = Field(
        default=None,
        description="If set, Runner swaps to this agent (BaseAgent instance)",
    )


# ── BaseAgent ─────────────────────────────────────────────────────


class BaseAgent:
    """Abstract base class for all Agent implementations.

    Defines the interface but does NOT implement forward().
    Extended classes provide actual implementations.
    """

    def __init__(self, config: AgentConfig) -> None:
        self.config = config
        self.logger = Logger(
            component="agent", level=config.log_level, agent=config.name
        )

        # Session state — set by Runner or parent agent
        self.session_id: str | None = None
        self.sandbox_dir: str | None = None
        self._sandbox_session: Any = None

    async def forward(
        self,
        messages: list[dict[str, Any]],
        context: dict[str, Any] | None = None,
    ) -> AgentResponse:
        """Execute agent logic.

        Args:
            messages: Full conversation history as list of message dicts.
                Each message: {"role": "system"|"user"|"assistant"|"tool", "content": "..."}
                The caller owns and manages conversation history.
            context: Optional runtime context dict. Supported keys:

                prompt_params: dict — Template params for prompt substitution.
                    "system": dict — params only for system prompt
                    "user": dict — params only for user prompt
                    Other keys are shared params applied to all prompts.
                    Prompt-specific params override shared params.

                tools: list[str | dict] — Additional tools for this call (name/path or resource bundle).
                agents: list[str | dict] — Additional agents-as-tools (name/path or resource bundle).
                handoffs: list[str | HandoffConfig | dict] — Additional handoff targets.
                skills: list[str | dict] — Additional skills (name/path or resource bundle).

        Returns:
            AgentResponse with content (LLMResponse), metadata, and optional handoff_to.

        Raises:
            NotImplementedError: Must be implemented by subclass.
        """
        raise NotImplementedError

    def as_tool(
        self, tool_name: str | None = None, tool_description: str | None = None
    ) -> dict:
        """Expose this agent as a callable tool for another agent.

        The calling agent retains control — gets results back.

        Args:
            tool_name: Custom tool name (default: agent_<config.name>).
            tool_description: Custom description (default: config.description).

        Returns:
            Unified tool dict (function type).
        """
        name = tool_name or f"agent_{self.config.name}"
        desc = (
            tool_description
            or self.config.description
            or f"Delegate to {self.config.name} agent"
        )
        return {
            "type": "function",
            "function": {
                "name": name,
                "description": desc,
                "parameters": {
                    "type": "object",
                    "properties": {
                        "input": {
                            "type": "string",
                            "description": "The task or question to delegate",
                        },
                    },
                    "required": ["input"],
                },
            },
        }

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(name={self.config.name})"
