"""Remediation engine — runbooks for common LLMHosts failures."""

from __future__ import annotations

import asyncio
import datetime
import logging
import subprocess
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

logger = logging.getLogger(__name__)


@dataclass
class RemediationResult:
    service: str
    action: str
    success: bool
    message: str
    timestamp: str = field(default_factory=lambda: datetime.datetime.now(datetime.timezone.utc).isoformat())

    def to_dict(self) -> dict:
        return {
            "service": self.service,
            "action": self.action,
            "success": self.success,
            "message": self.message,
            "timestamp": self.timestamp,
        }


class RemediationEngine:
    """Executes healing runbooks for known failure modes."""

    # Circuit breaker: max auto-remediation attempts per service per hour
    MAX_ATTEMPTS_PER_HOUR = 3

    def __init__(self) -> None:
        self._attempt_log: dict[str, list[datetime.datetime]] = {}
        self._history: list[RemediationResult] = []

    def _is_circuit_open(self, service: str) -> bool:
        """Return True if we've hit the remediation limit for this service."""
        now = datetime.datetime.now(datetime.timezone.utc)
        cutoff = now - datetime.timedelta(hours=1)
        attempts = [t for t in self._attempt_log.get(service, []) if t > cutoff]
        self._attempt_log[service] = attempts
        return len(attempts) >= self.MAX_ATTEMPTS_PER_HOUR

    def _record_attempt(self, service: str) -> None:
        self._attempt_log.setdefault(service, []).append(datetime.datetime.now(datetime.timezone.utc))

    async def remediate(self, service: str) -> RemediationResult:
        """Run the healing runbook for a service. Never raises."""
        if self._is_circuit_open(service):
            result = RemediationResult(
                service=service,
                action="circuit_breaker",
                success=False,
                message=f"Circuit open: too many remediation attempts for '{service}' in the last hour.",
            )
            self._history.append(result)
            return result

        self._record_attempt(service)
        runbook = self._get_runbook(service)
        result = await runbook()
        self._history.append(result)
        logger.info("Remediation '%s': %s — %s", service, result.action, result.message)
        return result

    def _get_runbook(self, service: str) -> Callable[[], Awaitable[RemediationResult]]:
        """Return the appropriate remediation coroutine for a service."""
        runbooks: dict[str, Callable[[], Awaitable[RemediationResult]]] = {
            "ollama": self._remediate_ollama,
            "proxy": self._remediate_proxy,
            "cache": self._remediate_cache,
        }
        # Partial match
        for key, fn in runbooks.items():
            if key in service.lower():
                return fn
        return self._remediate_generic(service)

    async def _remediate_ollama(self) -> RemediationResult:
        """Restart Ollama service."""
        try:
            subprocess.run(  # nosec B603 B607
                ["ollama", "serve"],
                capture_output=True,
                text=True,
                timeout=5,
                start_new_session=True,
            )
            await asyncio.sleep(2)
            return RemediationResult(
                service="ollama",
                action="restart_service",
                success=True,
                message="Ollama restart initiated.",
            )
        except FileNotFoundError:
            return RemediationResult(
                service="ollama",
                action="restart_service",
                success=False,
                message="Ollama binary not found — cannot auto-remediate.",
            )
        except Exception as exc:
            return RemediationResult(
                service="ollama",
                action="restart_service",
                success=False,
                message=f"Restart attempt failed: {exc}",
            )

    async def _remediate_proxy(self) -> RemediationResult:
        """Log proxy failure — proxy is self-contained."""
        return RemediationResult(
            service="proxy",
            action="log_failure",
            success=True,
            message="Proxy health degraded — logged for operator review.",
        )

    async def _remediate_cache(self) -> RemediationResult:
        """Clear cache state if corrupted."""
        return RemediationResult(
            service="cache",
            action="soft_reset",
            success=True,
            message="Cache soft-reset initiated.",
        )

    def _remediate_generic(self, service: str) -> Callable[[], Awaitable[RemediationResult]]:
        async def _fn() -> RemediationResult:
            return RemediationResult(
                service=service,
                action="log_only",
                success=True,
                message=f"No runbook for '{service}' — failure logged.",
            )

        return _fn

    def healing_history(self, limit: int = 20) -> list[dict]:
        """Return recent healing events."""
        return [r.to_dict() for r in self._history[-limit:]]

    def healing_stats(self) -> dict:
        """Return summary stats."""
        total = len(self._history)
        success = sum(1 for r in self._history if r.success)
        return {
            "total_actions": total,
            "successful_actions": success,
            "services_seen": list({r.service for r in self._history}),
        }
