"""Agent runner — wire up and execute tasks from the CLI."""

from __future__ import annotations

import uuid

from agent_scout.agent import Agent
from agent_scout.config.settings import Settings
from agent_scout.db.models import AutonomyLevel, Task
from agent_scout.events import Event, EventBus, EventType
from agent_scout.llm.budget import BudgetConfig, BudgetEngine
from agent_scout.llm.estimator import CostEstimator
from agent_scout.llm.provider import (
    AgentLLMProvider,
    CustomEndpoint,
    LLMClient,
    ProviderConfig,
)
from agent_scout.llm.router import ModelRouter, RouterConfig
from agent_scout.plugins.registry import PluginRegistry
from agent_scout.plugins.web_search import WebSearchPlugin
from rich.console import Console

from agent_scout_cli.display.components import (
    action_panel,
    cost_display,
    error_panel,
    finish_panel,
    observation_panel,
    thought_panel,
)


def _build_provider_config(settings: Settings) -> ProviderConfig:
    """Build ProviderConfig from Settings."""
    endpoints = [
        CustomEndpoint(
            name=ep.name,
            base_url=ep.base_url,
            api_key=ep.api_key,
            model_name=ep.model_name,
        )
        for ep in settings.custom_endpoints
    ]
    return ProviderConfig(
        default_model=settings.llm.default_model,
        temperature=settings.llm.temperature,
        max_tokens=settings.llm.max_tokens,
        custom_endpoints=endpoints,
    )


def _build_router(settings: Settings) -> ModelRouter:
    """Build ModelRouter from Settings."""
    config = RouterConfig(
        fast_model=settings.llm.fast_model,
        balanced_model=settings.llm.default_model,
        best_model=settings.llm.best_model,
        enabled=settings.llm.enable_routing,
    )
    return ModelRouter(config)


def _build_budget(settings: Settings, bus: EventBus) -> BudgetEngine:
    """Build BudgetEngine from Settings."""
    config = BudgetConfig(
        task_limit=settings.budget.task_limit,
        daily_limit=settings.budget.daily_limit,
        monthly_limit=settings.budget.monthly_limit,
        enforce=settings.budget.enforce,
    )
    return BudgetEngine(config=config, event_bus=bus)


def _build_registry(settings: Settings) -> PluginRegistry:
    """Build and populate the plugin registry."""
    from agent_scout.plugins.base import PluginConfig

    registry = PluginRegistry()

    if "web_search" in settings.plugins:
        ws_config = PluginConfig(
            settings={
                "tavily_api_key": settings.search.tavily_api_key,
                "serpapi_api_key": settings.search.serpapi_api_key,
                "brave_api_key": settings.search.brave_api_key,
            }
        )
        registry.register(WebSearchPlugin(config=ws_config))

    return registry


class CLIEventHandler:
    """Display agent events in the terminal."""

    def __init__(self, console: Console, verbose: bool = False) -> None:
        self._console = console
        self._verbose = verbose
        self._step_count = 0
        self._total_input = 0
        self._total_output = 0
        self._total_cost = 0.0

    async def handle(self, event: Event) -> None:
        """Route events to display methods."""
        handlers = {
            EventType.THOUGHT: self._on_thought,
            EventType.ACTION: self._on_action,
            EventType.OBSERVATION: self._on_observation,
            EventType.TOOL_CALL_START: self._on_tool_start,
            EventType.TOOL_CALL_END: self._on_tool_end,
            EventType.TOOL_ERROR: self._on_tool_error,
            EventType.COST_UPDATE: self._on_cost,
            EventType.BUDGET_WARNING: self._on_budget_warning,
            EventType.BUDGET_EXCEEDED: self._on_budget_exceeded,
            EventType.TASK_COMPLETED: self._on_completed,
            EventType.TASK_FAILED: self._on_failed,
            EventType.ERROR: self._on_error,
        }
        handler = handlers.get(event.event_type)
        if handler:
            handler(event)

    def _on_thought(self, event: Event) -> None:
        self._step_count += 1
        self._console.print(thought_panel(event.data.get("thought", ""), self._step_count))

    def _on_action(self, event: Event) -> None:
        tool = event.data.get("tool_name", "")
        if tool:
            self._console.print(action_panel(tool, event.data.get("tool_input")))

    def _on_observation(self, event: Event) -> None:
        obs = event.data.get("observation", "")
        if obs and self._verbose:
            self._console.print(observation_panel(obs))
        elif obs:
            short = obs[:200] + "..." if len(obs) > 200 else obs
            self._console.print(f"  [dim]{short}[/dim]")

    def _on_tool_start(self, event: Event) -> None:
        tool = event.data.get("tool_name", "")
        self._console.print(f"  [dim]Running {tool}...[/dim]")

    def _on_tool_end(self, event: Event) -> None:
        pass  # observation handles the output

    def _on_tool_error(self, event: Event) -> None:
        err = event.data.get("error", "")
        self._console.print(f"  [red]Tool error: {err}[/red]")

    def _on_cost(self, event: Event) -> None:
        self._total_input += event.data.get("input_tokens", 0)
        self._total_output += event.data.get("output_tokens", 0)
        self._total_cost += event.data.get("cost_usd", 0.0)

    def _on_budget_warning(self, event: Event) -> None:
        pct = event.data.get("percentage", 0)
        limit_type = event.data.get("limit_type", "")
        self._console.print(f"  [yellow]Budget warning: {limit_type} at {pct}%[/yellow]")

    def _on_budget_exceeded(self, event: Event) -> None:
        limit_type = event.data.get("limit_type", "")
        self._console.print(f"  [bold red]Budget exceeded: {limit_type}[/bold red]")

    def _on_completed(self, event: Event) -> None:
        result = event.data.get("result", {})
        message = result.get("message", "") if isinstance(result, dict) else str(result)
        if message:
            self._console.print(finish_panel(message))
        self._print_summary()

    def _on_failed(self, event: Event) -> None:
        err = event.data.get("error", "Task failed")
        self._console.print(error_panel(err))
        self._print_summary()

    def _on_error(self, event: Event) -> None:
        err = event.data.get("error", "")
        self._console.print(f"  [red]{err}[/red]")

    def _print_summary(self) -> None:
        if self._total_input > 0 or self._total_output > 0:
            self._console.print(
                cost_display(
                    self._total_input,
                    self._total_output,
                    self._total_cost,
                )
            )
        self._console.print(f"[dim]Completed in {self._step_count} steps[/dim]")


async def run_task(
    task_description: str,
    settings: Settings,
    console: Console,
    model_override: str | None = None,
    budget_override: float | None = None,
    verbose: bool = False,
) -> None:
    """Build the full agent stack and execute a task."""
    bus = EventBus()

    # Event display
    handler = CLIEventHandler(console, verbose=verbose)
    bus.subscribe(handler.handle)

    # LLM stack
    provider_config = _build_provider_config(settings)
    client = LLMClient(config=provider_config, event_bus=bus)
    router = _build_router(settings)
    estimator = CostEstimator()
    budget = _build_budget(settings, bus)

    if budget_override is not None:
        budget.config.task_limit = budget_override

    # Route to best model for this task
    model = router.route(task_description, override_model=model_override)
    llm_provider = AgentLLMProvider(client, model=model)

    # Plugin registry
    registry = _build_registry(settings)
    await registry.load_all()

    # Build autonomy level
    autonomy_map = {
        "full": AutonomyLevel.FULL,
        "semi": AutonomyLevel.SEMI,
        "step": AutonomyLevel.STEP_BY_STEP,
    }
    autonomy = autonomy_map.get(settings.autonomy, AutonomyLevel.FULL)

    # Pre-execution cost estimate
    estimate = estimator.estimate(model=model, input_tokens=500, expected_output_tokens=1000)
    console.print(f"[dim]Model: {model} | Estimated cost: {estimate.formatted}[/dim]")

    # Build and run agent
    agent = Agent(
        llm=llm_provider,
        tools=registry,
        event_bus=bus,
        autonomy=autonomy,
    )

    task = Task(
        id=uuid.uuid4().hex[:12],
        description=task_description,
    )

    await agent.run_task(task)

    # Cleanup
    await registry.unload_all()
