"""Tool Startup Orchestrator — probes all registered tools at boot and periodically.

On application startup, runs health checks against every tool in the
ToolRegistry, emits ToolReadinessProbed events (pushed to dashboard via SSE),
and stores a readiness snapshot accessible via API.

Usage (in main.py lifespan):
    orchestrator = ToolStartupOrchestrator()
    await orchestrator.start()
    ...
    await orchestrator.stop()
"""

from __future__ import annotations

import asyncio
import shutil
import time
from typing import Any

import structlog

from proxym.tools import ToolRegistry

logger = structlog.get_logger()


class ToolStartupOrchestrator:
    """Probes tools on startup and periodically; emits SSE events."""

    def __init__(self, probe_interval: int = 120) -> None:
        self._probe_interval = probe_interval
        self._task: asyncio.Task | None = None
        self._snapshot: dict[str, Any] = {}
        self._last_probe: float = 0

    # ── Public API ────────────────────────────────────────────

    async def start(self) -> None:
        """Run initial probe then start background loop."""
        await self._probe_all()
        self._task = asyncio.create_task(self._loop())
        logger.info("tool_startup_orchestrator_started", interval=self._probe_interval)

    async def stop(self) -> None:
        if self._task and not self._task.done():
            self._task.cancel()
            try:
                await self._task
            except asyncio.CancelledError:
                pass
        logger.info("tool_startup_orchestrator_stopped")

    @property
    def snapshot(self) -> dict[str, Any]:
        """Current readiness snapshot."""
        return self._snapshot

    # ── Background loop ───────────────────────────────────────

    async def _loop(self) -> None:
        try:
            while True:
                await asyncio.sleep(self._probe_interval)
                await self._probe_all()
        except asyncio.CancelledError:
            pass

    # ── Core probe logic ──────────────────────────────────────

    async def _probe_all(self) -> None:
        """Probe every registered tool and emit events."""
        start = time.time()
        reg = ToolRegistry.get()

        from proxym.config import get_settings
        settings = get_settings()
        configured_providers = settings.available_providers()
        tool_providers = settings.tool_providers

        results: list[dict[str, Any]] = []
        ok = degraded = unavailable = 0

        for tool in reg.list_tools():
            diag = self._probe_tool(tool, tool_providers, configured_providers)
            results.append(diag)
            if diag["status"] == "ok":
                ok += 1
            elif diag["status"] == "degraded":
                degraded += 1
            else:
                unavailable += 1

            # Emit per-tool event
            self._emit(
                "ToolReadinessProbed",
                aggregate_id=tool.name,
                data={
                    "tool": tool.name,
                    "status": diag["status"],
                    "available": diag["available"],
                    "binary_found": diag["binary_found"],
                    "provider_configured": diag["provider_configured"],
                    "docker_instance": diag["docker_instance"],
                    "issues": diag["issues"],
                },
            )

        elapsed = round(time.time() - start, 2)
        self._snapshot = {
            "tools": results,
            "summary": {"total": len(results), "ok": ok, "degraded": degraded, "unavailable": unavailable},
            "providers_configured": list(configured_providers.keys()),
            "probed_at": time.time(),
            "probe_duration_s": elapsed,
        }
        self._last_probe = time.time()

        # Emit summary event
        self._emit(
            "ToolStartupComplete",
            aggregate_id="system",
            data={
                "total": len(results),
                "ok": ok,
                "degraded": degraded,
                "unavailable": unavailable,
                "duration_s": elapsed,
            },
        )
        logger.info(
            "tool_probe_complete",
            total=len(results), ok=ok, degraded=degraded,
            unavailable=unavailable, elapsed_s=elapsed,
        )

    # ── Single-tool probe ─────────────────────────────────────

    def _probe_tool(
        self,
        tool,
        tool_providers: dict[str, str],
        configured_providers: dict[str, Any],
    ) -> dict[str, Any]:
        """Run health diagnostics for a single tool (sync, fast)."""
        diag: dict[str, Any] = {
            "name": tool.name,
            "category": tool.category.value,
            "available": tool.is_available,
            "binary": tool.binary,
            "binary_found": bool(tool.binary and shutil.which(tool.binary)),
            "provider": tool_providers.get(tool.name, "openrouter"),
            "provider_configured": False,
            "api_key_present": False,
            "docker_instance": False,
            "kvm_instance": False,
            "vm_instance": False,
            "status": "unknown",
            "issues": [],
        }

        # Human tool — always available
        if tool.name == "human":
            diag["provider"] = "manual"
            diag["provider_configured"] = True
            diag["api_key_present"] = True
            diag["status"] = "ok"
            return diag

        # Provider check
        provider_name = diag["provider"]
        if provider_name in configured_providers:
            diag["provider_configured"] = True
            diag["api_key_present"] = True
        elif "openrouter" in configured_providers and provider_name != "ollama":
            diag["provider_configured"] = True
            diag["api_key_present"] = True
            diag["provider"] = f"{provider_name} (via openrouter)"
        else:
            diag["issues"].append(f"Provider '{provider_name}' not configured")

        # Runtime check — binary
        if tool.binary and not diag["binary_found"]:
            diag["issues"].append(f"{tool.binary} not found on PATH")

        # Runtime check — instances (docker, kvm, vm)
        for inst in tool.instances:
            if inst.kind == "docker" and inst.is_available:
                diag["docker_instance"] = True
            elif inst.kind == "kvm" and inst.is_available:
                diag["kvm_instance"] = True
            elif inst.kind == "vm" and inst.is_available:
                diag["vm_instance"] = True

        # Finalize status
        if not diag["issues"]:
            diag["status"] = "ok"
        elif diag["available"] or diag["provider_configured"]:
            diag["status"] = "degraded"
        else:
            diag["status"] = "unavailable"

        return diag

    # ── Event emission ────────────────────────────────────────

    @staticmethod
    def _emit(event_type: str, aggregate_id: str = "", data: dict | None = None) -> None:
        """Emit a domain event (best-effort)."""
        try:
            from proxym.domain.setup import get_event_store
            from proxym.domain.events import ToolReadinessProbed, ToolStartupComplete

            cls = ToolReadinessProbed if event_type == "ToolReadinessProbed" else ToolStartupComplete
            get_event_store().append(cls(
                aggregate_id=aggregate_id,
                actor="startup_orchestrator",
                data=data or {},
            ))
        except Exception:
            pass
