"""Agent registry for dynamic agent loading."""

import importlib
from dataclasses import dataclass, field
from typing import Any

from pydantic_ai import Agent


@dataclass
class RegisteredAgent:
    """A registered agent in the platform."""

    id: str
    name: str
    description: str | None = None
    default_model: str | None = None
    agent_class: str = ""  # Python path: 'myapp.agents.support:support_agent'
    is_active: bool = True


class AgentRegistry:
    """Registry for managing and loading agents.

    Agents can be registered either:
    1. In-memory (for built-in agents)
    2. From database (for dynamic agents)

    Example:
        ```python
        registry = AgentRegistry(db)

        # Register built-in agents
        registry.register_builtin("support", support_agent, "Customer support")
        registry.register_builtin("research", research_agent, "Research assistant")

        # Load agent by name
        agent = await registry.get_agent("support")

        # Load with tenant-specific config
        agent = await registry.get_agent_for_tenant("support", tenant)
        ```
    """

    def __init__(self, db: Any | None = None) -> None:
        self.db = db
        self._builtin_agents: dict[str, Agent[Any, Any]] = {}
        self._agent_metadata: dict[str, RegisteredAgent] = {}

    def register_builtin(
        self,
        name: str,
        agent: Agent[Any, Any],
        description: str | None = None,
        default_model: str | None = None,
    ) -> None:
        """Register a built-in agent.

        Built-in agents are stored in memory and don't require database.
        """
        self._builtin_agents[name] = agent
        self._agent_metadata[name] = RegisteredAgent(
            id=name,
            name=name,
            description=description,
            default_model=default_model,
            is_active=True,
        )

    async def sync_builtin_agents_to_db(self) -> None:
        """Sync built-in agents to the database for foreign key support.

        This ensures tenant_agents can reference built-in agents.
        """
        if not self.db:
            return

        for name, meta in self._agent_metadata.items():
            await self.db.execute(
                """
                INSERT INTO agents (id, name, description, default_model, agent_class, is_active)
                VALUES ($1, $2, $3, $4, $5, true)
                ON CONFLICT (name) DO UPDATE
                SET description = $3, default_model = $4, is_active = true
                """,
                name,  # Use name as ID for built-in agents
                name,
                meta.description,
                meta.default_model,
                f"builtin:{name}",  # Mark as built-in
            )

    async def register_agent(
        self,
        name: str,
        agent_class: str,
        description: str | None = None,
        default_model: str | None = None,
    ) -> RegisteredAgent:
        """Register an agent in the database.

        Args:
            name: Unique name for the agent.
            agent_class: Python path to agent, e.g., 'myapp.agents:my_agent'
            description: Human-readable description.
            default_model: Default model override.
        """
        import uuid

        agent_id = f"agent_{uuid.uuid4().hex[:12]}"

        await self.db.execute(
            """
            INSERT INTO agents (id, name, description, default_model, agent_class)
            VALUES ($1, $2, $3, $4, $5)
            ON CONFLICT (name) DO UPDATE
            SET description = $3, default_model = $4, agent_class = $5
            """,
            agent_id,
            name,
            description,
            default_model,
            agent_class,
        )

        return RegisteredAgent(
            id=agent_id,
            name=name,
            description=description,
            default_model=default_model,
            agent_class=agent_class,
        )

    def _load_agent_from_path(self, agent_class: str) -> Agent[Any, Any]:
        """Dynamically load an agent from a Python path.

        Args:
            agent_class: Path like 'myapp.agents.support:support_agent'
        """
        module_path, agent_name = agent_class.rsplit(":", 1)
        module = importlib.import_module(module_path)
        agent = getattr(module, agent_name)

        if not isinstance(agent, Agent):
            raise ValueError(f"{agent_class} is not a pydantic_ai.Agent")

        return agent

    async def get_agent(self, name: str) -> Agent[Any, Any] | None:
        """Get an agent by name.

        Checks built-in agents first, then database.
        """
        # Check built-in agents
        if name in self._builtin_agents:
            return self._builtin_agents[name]

        # Check database
        if self.db:
            result = await self.db.fetch_one(
                "SELECT agent_class FROM agents WHERE name = $1 AND is_active = true",
                name,
            )
            if result:
                return self._load_agent_from_path(result["agent_class"])

        return None

    async def get_agent_for_tenant(
        self,
        name: str,
        tenant_id: str,
    ) -> tuple[Agent[Any, Any], str] | None:
        """Get an agent configured for a specific tenant.

        Returns:
            Tuple of (agent, model_to_use) or None if not accessible.
        """
        # Check if tenant has access
        if self.db:
            access = await self.db.fetch_one(
                """
                SELECT a.agent_class, a.default_model as agent_model,
                       ta.model_override, t.default_model as tenant_model
                FROM agents a
                JOIN tenant_agents ta ON a.id = ta.agent_id
                JOIN tenants t ON ta.tenant_id = t.id
                WHERE a.name = $1 AND ta.tenant_id = $2 AND a.is_active = true
                """,
                name,
                tenant_id,
            )

            if not access:
                return None

            # Priority: tenant_agent override > agent default > tenant default
            model = (
                access["model_override"]
                or access["agent_model"]
                or access["tenant_model"]
            )

            # Load agent
            if name in self._builtin_agents:
                agent = self._builtin_agents[name]
            else:
                agent = self._load_agent_from_path(access["agent_class"])

            return agent, model

        # No database - just return builtin if exists
        if name in self._builtin_agents:
            return self._builtin_agents[name], "openai:gpt-4o"

        return None

    async def list_agents(self) -> list[RegisteredAgent]:
        """List all available agents."""
        agents = list(self._agent_metadata.values())

        if self.db:
            results = await self.db.fetch_all(
                "SELECT id, name, description, default_model, agent_class FROM agents WHERE is_active = true"
            )
            for r in results:
                if r["name"] not in self._builtin_agents:
                    agents.append(
                        RegisteredAgent(
                            id=r["id"],
                            name=r["name"],
                            description=r["description"],
                            default_model=r["default_model"],
                            agent_class=r["agent_class"],
                        )
                    )

        return agents

    async def get_tenant_default_agent(
        self, tenant_id: str
    ) -> tuple[Agent[Any, Any], str] | None:
        """Get the default agent for a tenant."""
        if not self.db:
            return None

        result = await self.db.fetch_one(
            """
            SELECT a.name
            FROM agents a
            JOIN tenant_agents ta ON a.id = ta.agent_id
            WHERE ta.tenant_id = $1 AND ta.is_default = true AND a.is_active = true
            """,
            tenant_id,
        )

        if result:
            return await self.get_agent_for_tenant(result["name"], tenant_id)

        return None
