"""Agent helper utilities.

Supporting utilities for agent creation and introspection.
"""

# @invar:allow shell_result: Returns list on error (defensive introspection)
# @shell_orchestration: MCP tool introspection helpers for agent setup
# @shell_complexity: MCP tool introspection with defensive error handling

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from anima.core.tool_namespace import (
    detect_tool_conflicts,
    format_conflict_warnings,
)

if TYPE_CHECKING:
    from pydantic_ai.toolsets import AbstractToolset


# =============================================================================
# MCP Tool Detection
# =============================================================================

# @invar:allow shell_result: Defensive introspection with exception handling
# @shell_complexity: MCP server introspection with multiple fallback sources


def _get_server_display_name(server: Any) -> str:
    """Get a display name for an MCP server.

    Uses priority: server_info.name > command basename > url > "unknown"

    Args:
        server: An MCP server (AbstractToolset) to get display name for.

    Returns:
        Human-readable display name for the server.
    """
    try:
        # Try server_info.name first (highest priority)
        if hasattr(server, "server_info") and server.server_info:
            if hasattr(server.server_info, "name") and server.server_info.name:
                return server.server_info.name

        # Try command basename (second priority)
        if hasattr(server, "command") and server.command:
            command = server.command
            if isinstance(command, list) and command:
                command = command[0]
            if isinstance(command, str) and command:
                # Get basename of the command path
                import os

                return os.path.basename(command)

        # Try url (third priority)
        if hasattr(server, "url") and server.url:
            return server.url

        # Fallback
        return "unknown"
    except Exception:
        # Defensive: any error returns unknown
        return "unknown"


# @invar:allow shell_result: Defensive iteration over MCP servers
# @shell_complexity: MCP instruction collection with multiple fallbacks


def _get_server_version(server: Any) -> str | None:
    """Get version for an MCP server if available."""
    try:
        if hasattr(server, "server_info") and server.server_info:
            if hasattr(server.server_info, "version") and server.server_info.version:
                return str(server.server_info.version)
        return None
    except Exception:
        return None


# @invar:allow shell_result: Returns list on error (defensive introspection)
async def _get_server_tool_names(server: Any, ctx: Any) -> list[str]:
    """Get sorted tool names from an MCP server wrapper."""
    try:
        tools_dict = await server.get_tools(ctx)  # type: ignore[misc]
        return sorted(tools_dict.keys())
    except Exception:
        return []


# @invar:allow shell_result: Returns context string or None (defensive introspection)
# @shell_complexity: MCP context synthesis combines introspection, truncation, and conflict summary in one pass
async def collect_mcp_server_context(
    mcp_servers: list["AbstractToolset[Any]"] | None,
    ctx: Any,
) -> str | None:
    """Collect MCP server context for dynamic instructions.

    Output format:
        ## Available Tool Sources

        **name** (vX.Y.Z): tool1, tool2
          Instructions: text

        [Tool name conflicts detected (if any)]
    """
    if not mcp_servers:
        return None

    server_parts: list[str] = []
    server_tools: dict[str, list[str]] = {}

    for server in mcp_servers:
        try:
            target = getattr(server, "_wrapped", server)

            name = _get_server_display_name(target)
            version = _get_server_version(target)
            tool_names = await _get_server_tool_names(server, ctx)

            # Build server_tools dict for conflict detection
            server_tools[name] = tool_names

            header = f"**{name}**"
            if version:
                header += f" (v{version})"
            header += ": " + ", ".join(tool_names)

            part_lines = [header]

            instructions_raw = getattr(target, "instructions", None)
            if isinstance(instructions_raw, str):
                instructions = "\n".join(
                    line for line in instructions_raw.splitlines() if line.strip()
                )
                if instructions:
                    part_lines.append(f"  Instructions: {instructions}")

            server_parts.append("\n".join(part_lines))
        except Exception:
            continue

    if not server_parts:
        return None

    output = "## Available Tool Sources\n\n" + "\n\n".join(server_parts)

    # Run conflict detection after building output sections
    conflicts = detect_tool_conflicts(server_tools)
    conflict_warning = format_conflict_warnings(conflicts)
    if conflict_warning:
        output += "\n\n" + conflict_warning

    return output


class _MockModel:
    """Minimal model for RunContext creation during MCP tool introspection.

    PydanticAI's RunContext requires a Model, but for introspection we don't
    need an actual working model - just something that satisfies the type.
    This creates a lightweight mock that avoids API key requirements.
    """

    @property
    def id(self) -> str:
        return "introspection-mock"

    @property
    def model_name(self) -> str:
        return "introspection-mock"


# @invar:allow shell_result: Returns list on error (defensive introspection)
# @shell_orchestration: MCP tool introspection function
# @shell_complexity: MCP tool introspection with defensive error handling
async def detect_mcp_ctl_tools(
    mcp_servers: list["AbstractToolset[Any]"] | None,
) -> list[str]:
    """Detect built-in tool names from MCP toolsets.

    Introspects MCP toolsets to find tool names starting with "ctl_".
    Used to prevent duplicate registration when MCP provides built-in tools.

    Args:
        mcp_servers: Optional list of MCP toolsets to introspect.

    Returns:
        List of built-in tool names (ctl_*) found in MCP toolsets.
        Returns empty list if mcp_servers is None or empty.

    Examples:
        >>> import asyncio
        >>> result = asyncio.run(detect_mcp_ctl_tools(None))
        >>> result
        []
    """
    if not mcp_servers:
        return []

    builtin_tools: list[str] = []

    # Create a minimal run context for tool introspection
    # PydanticAI's AbstractToolset.get_tools() requires a RunContext
    from pydantic_ai import RunContext, RunUsage

    mock_model = _MockModel()
    usage = RunUsage()
    ctx = RunContext(deps=None, model=mock_model, usage=usage)  # type: ignore[arg-type]

    for toolset in mcp_servers:
        try:
            # get_tools returns dict[str, ToolsetTool], keys are tool names
            tools_dict = await toolset.get_tools(ctx)  # type: ignore[misc]
            for tool_name in tools_dict:
                # Detect ctl_* tools
                if tool_name.startswith("ctl_"):
                    builtin_tools.append(tool_name)
        except Exception:
            # If toolset introspection fails, continue with other toolsets
            # This is defensive - we don't want to fail agent creation
            # just because one toolset couldn't be introspected
            continue

    return builtin_tools


# =============================================================================
# CTL Tool Logging
# =============================================================================


# All built-in ctl tools for reference
BUILTIN_CTL_TOOLS = [
    "ctl_finish",
    "ctl_request_approval",
    "ctl_spawn",
    "ctl_spawn_subagent",  # backward compat alias
    "ctl_delegate",
    "ctl_await",
    "ctl_poll",
    "ctl_cancel",
    "ctl_child_logs",
    "ctl_switch_persona",
]


# @invar:allow shell_result: Logging helper for tool registration
# @shell_orchestration: Tool registration logging formatter
def log_ctl_tool_registration(
    instance_id: str,
    mcp_ctl_tools: list[str] | None = None,
) -> None:
    """Log which ctl tools were registered for verification.

    Logs the tool names to the agent log for debugging and verification.
    Detects MCP-provided ctl tools vs built-in ctl tools.

    Args:
        instance_id: The agent's unique identifier.
        mcp_ctl_tools: List of ctl tool names provided by MCP toolsets.
    """
    from anima.shell.state import append_log

    mcp_ctl_tools = mcp_ctl_tools or []

    # Determine which tools were actually registered
    registered_builtin = [
        tool for tool in BUILTIN_CTL_TOOLS if tool not in mcp_ctl_tools
    ]

    # Log which side owns each tool
    tool_ownership = {}
    for tool in BUILTIN_CTL_TOOLS:
        if tool in mcp_ctl_tools:
            tool_ownership[tool] = "mcp"
        else:
            tool_ownership[tool] = "builtin"

    append_log(
        instance_id,
        "ctl_tools_registered",
        {
            "registered_builtin_ctl_tools": registered_builtin,
            "mcp_ctl_tools": mcp_ctl_tools,
            "tool_ownership": tool_ownership,
            "builtin_tool_count": len(registered_builtin),
            "mcp_tool_count": len(mcp_ctl_tools),
        },
    )


__all__ = [
    "detect_mcp_ctl_tools",
    "log_ctl_tool_registration",
    "BUILTIN_CTL_TOOLS",
    "_get_server_display_name",
    "collect_mcp_server_context",
]
