"""Health watchdog — periodic system health checker."""
from __future__ import annotations

import asyncio
import time
from typing import Any

import httpx
import structlog

from proxym.config import get_settings
from proxym.observability.health_events import (
    HealthEventStore, Severity, get_health_store,
)

log = structlog.get_logger()


class HealthWatchdog:
    """Daemon sprawdzający zdrowie systemu w pętli.

    Checks:
      - proxy /health
      - provider connectivity (nagłówki rate limit)
      - vscode-web healthz
      - budget remaining
      - error rate z ring buffer
      - disk / process uptime
    """

    def __init__(
        self,
        store: HealthEventStore | None = None,
        interval_seconds: int = 60,
        repair_callback: Any = None,
    ) -> None:
        self._store = store or get_health_store()
        self._interval = interval_seconds
        self._repair_callback = repair_callback  # async fn(event) -> bool
        self._running = False
        self._task: asyncio.Task | None = None
        self._previous: dict[str, str] = {}  # source → last status

    async def start(self) -> None:
        if self._running:
            return
        self._running = True
        self._task = asyncio.create_task(self._loop())
        log.info("watchdog.started", interval=self._interval)

    async def stop(self) -> None:
        self._running = False
        if self._task:
            self._task.cancel()
        log.info("watchdog.stopped")

    async def _loop(self) -> None:
        while self._running:
            try:
                await self._run_checks()
            except Exception as e:
                log.error("watchdog.check_failed", error=str(e))
            await asyncio.sleep(self._interval)

    async def _run_checks(self) -> None:
        checks = [
            self._check_proxy_health,
            self._check_providers,
            self._check_vscode,
            self._check_budget,
            self._check_error_rate,
        ]
        for check in checks:
            try:
                await check()
            except Exception as e:
                log.warning("watchdog.check_error",
                            check=check.__name__, error=str(e))

    async def _check_proxy_health(self) -> None:
        source = "proxy"
        try:
            async with httpx.AsyncClient(timeout=5) as client:
                r = await client.get("http://localhost:4000/health")
                if r.status_code == 200:
                    self._resolve_if_active(source)
                else:
                    self._emit_if_new(source, Severity.critical,
                                      "Proxy health check failed",
                                      f"Status {r.status_code}: {r.text[:200]}")
        except httpx.ConnectError:
            self._emit_if_new(source, Severity.critical,
                              "Proxy not reachable", "Connection refused on :4000")

    async def _check_providers(self) -> None:
        settings = get_settings()
        providers = {
            "anthropic": bool(settings.anthropic_api_key),
            "openrouter": bool(getattr(settings, "openrouter_api_key", "")),
            "google": bool(getattr(settings, "google_ai_key", "")),
        }
        configured = [k for k, v in providers.items() if v]
        if not configured:
            self._emit_if_new("providers", Severity.warning,
                              "No providers configured",
                              "Add at least one API key to .env")
        else:
            self._resolve_if_active("providers")

        # Test provider connectivity
        for name in configured:
            source = f"provider:{name}"
            try:
                async with httpx.AsyncClient(timeout=10) as client:
                    r = await client.get(
                        "http://localhost:4000/console/routing-test",
                        timeout=10,
                    )
                    if r.status_code == 200:
                        self._resolve_if_active(source)
            except Exception as e:
                self._emit_if_new(source, Severity.warning,
                                  f"Provider {name} unreachable", str(e))

    async def _check_vscode(self) -> None:
        source = "vscode"
        try:
            async with httpx.AsyncClient(timeout=5) as client:
                r = await client.get("http://localhost:8080/healthz")
                if r.status_code == 200:
                    self._resolve_if_active(source)
                else:
                    self._emit_if_new(source, Severity.warning,
                                      "VS Code Web unhealthy",
                                      f"Status {r.status_code}")
        except httpx.ConnectError:
            # VS Code Web may not be running — info, not critical
            pass

    async def _check_budget(self) -> None:
        source = "budget"
        try:
            async with httpx.AsyncClient(timeout=5) as client:
                r = await client.get("http://localhost:4000/v1/budget")
                if r.status_code == 200:
                    data = r.json()
                    daily_remaining = data.get("daily_remaining", 999)
                    monthly_remaining = data.get("monthly_remaining", 999)
                    if daily_remaining <= 0 or monthly_remaining <= 0:
                        self._emit_if_new(source, Severity.critical,
                                          "Budget exhausted",
                                          f"Daily: ${daily_remaining:.2f}, Monthly: ${monthly_remaining:.2f}")
                    elif daily_remaining < 1.0 or monthly_remaining < 5.0:
                        self._emit_if_new(source, Severity.warning,
                                          "Budget running low",
                                          f"Daily: ${daily_remaining:.2f}, Monthly: ${monthly_remaining:.2f}")
                    else:
                        self._resolve_if_active(source)
        except Exception:
            pass

    async def _check_error_rate(self) -> None:
        source = "error_rate"
        from proxym.observability import get_error_summary
        summary = get_error_summary()
        total = summary["total_logs"]
        errors = summary["total_errors"]
        if total > 20 and errors / total > 0.3:
            detail = f"{errors}/{total} logs are errors. Top sources: {summary['by_source']}"
            self._emit_if_new(source, Severity.warning,
                              "High error rate detected", detail)
        else:
            self._resolve_if_active(source)

    # --- Helpers ---

    def _emit_if_new(
        self, source: str, severity: Severity, title: str, detail: str = "",
    ) -> None:
        """Emituj event tylko jeśli jest nowy (nie duplikuj istniejącego active)."""
        prev = self._previous.get(source)
        key = f"{severity}:{title}"
        if prev == key:
            return  # Already reported
        self._previous[source] = key
        event = self._store.emit(severity, source, title, detail)

        # Trigger repair na critical
        if severity == Severity.critical and self._repair_callback:
            asyncio.create_task(self._try_repair(event))

    def _resolve_if_active(self, source: str) -> None:
        if source in self._previous:
            del self._previous[source]
            self._store.resolve(source, "watchdog")

    async def _try_repair(self, event) -> None:
        """Uruchom auto-repair jeśli callback jest ustawiony."""
        if self._repair_callback:
            try:
                success = await self._repair_callback(event)
                event.repair_attempted = True
                event.repair_result = "success" if success else "failed"
            except Exception as e:
                event.repair_attempted = True
                event.repair_result = f"error: {e}"
