"""Agent builder — creates fully configured ICARUS agents.

Resolves model via the unified config system, then delegates
backend/middleware/graph creation to the runtime agent layer.
The system prompt is built by the runtime agent layer from
``assets/system_prompt.md``.
"""

from __future__ import annotations

from collections.abc import Sequence
from typing import TYPE_CHECKING, Any, NamedTuple

from icarus.config.app_config import create_model
from icarus.runtime.context import UserContext
from langchain_core.language_models import BaseChatModel

if TYPE_CHECKING:
    from deepagents.backends import CompositeBackend
    from langchain_core.tools import BaseTool
    from langgraph.checkpoint.base import BaseCheckpointSaver
    from langgraph.graph.state import CompiledStateGraph


class AgentResult(NamedTuple):
    """Result of create_icarus_agent — graph, backend, and model for cleanup."""

    graph: CompiledStateGraph
    backend: CompositeBackend
    model: BaseChatModel


def create_icarus_agent(
    ctx: UserContext,
    *,
    model: str | BaseChatModel | None = None,
    tools: Sequence[BaseTool | Any] | None = None,
    auto_approve: bool = False,
    debug: bool = False,
    checkpointer: BaseCheckpointSaver | None = None,
) -> AgentResult:
    """Create a fully configured ICARUS agent scoped to a UserContext.

    Uses ICARUS's model registry to resolve the LLM, then delegates
    graph/backend/middleware creation to the runtime agent layer.
    The system prompt is built by ``get_system_prompt()`` in the
    runtime agent layer using the template at ``assets/system_prompt.md``.

    Args:
        ctx: UserContext scoping all resources to a specific user.
        model: Model spec ('provider:model') or BaseChatModel instance.
               If None, uses default from ~/.icarus/config.toml.
        tools: Extra tools to inject (e.g. MCP tools). Passed through
               to create_cli_agent().
        auto_approve: Skip HITL approval for all tool calls.
        debug: Enable debug mode.
        checkpointer: Checkpoint saver for session persistence.
               If None, uses InMemorySaver (no persistence across restarts).

    Returns:
        AgentResult with (graph, backend, model).
        Callers should call ``await result.model.aclose()`` when done
        if the model supports it (e.g. AzureAIChatCompletionsModel).
    """
    from icarus.runtime.agent import create_cli_agent

    ctx.ensure_dirs()

    # Resolve model via unified config system
    if model is not None and not isinstance(model, str):
        resolved_model = model
    else:
        result = create_model(model)
        result.apply_to_settings()
        resolved_model = result.model

    # Build PIM tools scoped to this user's directories (optional — requires icarus[pim])
    from icarus.tools import get_current_time
    from icarus.tools.browser import create_browser_tools

    try:
        from icarus.tools.pim import create_pim_tools
        pim_tools = create_pim_tools(ctx)
    except ImportError:
        pim_tools = []

    browser_tools = create_browser_tools()
    all_tools = list(tools or []) + pim_tools + browser_tools + [get_current_time]

    # Delegate to runtime agent — system_prompt=None lets agent.py
    # use get_system_prompt() which loads assets/system_prompt.md
    graph, backend = create_cli_agent(
        model=resolved_model,
        assistant_id="default",
        auto_approve=auto_approve,
        tools=all_tools,
        memory_dir=ctx.memory_dir,
        ctx=ctx,
        checkpointer=checkpointer,
    )

    # Retry the model node on transient failures (empty streams, connection drops)
    # Backoff: 1s → 2s → 4s → 8s → 16s (5 attempts, ~31s total before giving up)
    from langgraph.types import RetryPolicy

    def _should_retry(exc: Exception) -> bool:
        """Retry on empty-stream errors and connection failures."""
        if isinstance(exc, (ConnectionError, OSError)):
            return True
        if isinstance(exc, ValueError) and "No generations found" in str(exc):
            return True
        return False

    graph.nodes["model"].retry_policy = [RetryPolicy(
        initial_interval=1.0,
        backoff_factor=2.0,
        max_interval=16.0,
        max_attempts=5,
        jitter=True,
        retry_on=_should_retry,
    )]

    return AgentResult(graph=graph, backend=backend, model=resolved_model)
