"""Service health monitor — detects failures and triggers remediation."""

from __future__ import annotations

import asyncio
import datetime
import logging
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable

logger = logging.getLogger(__name__)


class ServiceStatus(str, Enum):
    HEALTHY = "healthy"
    DEGRADED = "degraded"
    DOWN = "down"
    UNKNOWN = "unknown"


@dataclass
class HealthCheck:
    name: str
    check_fn: Callable[[], Awaitable[bool]]
    interval_seconds: int = 30
    failure_threshold: int = 3  # consecutive failures before action
    _consecutive_failures: int = field(default=0, init=False, repr=False)
    _last_status: ServiceStatus = field(default=ServiceStatus.UNKNOWN, init=False, repr=False)
    _last_checked: datetime.datetime | None = field(default=None, init=False, repr=False)

    async def check(self) -> ServiceStatus:
        try:
            ok = await self.check_fn()
            if ok:
                self._consecutive_failures = 0
                self._last_status = ServiceStatus.HEALTHY
            else:
                self._consecutive_failures += 1
                self._last_status = (
                    ServiceStatus.DOWN
                    if self._consecutive_failures >= self.failure_threshold
                    else ServiceStatus.DEGRADED
                )
        except Exception as exc:
            logger.debug("Health check '%s' raised: %s", self.name, exc)
            self._consecutive_failures += 1
            self._last_status = (
                ServiceStatus.DOWN if self._consecutive_failures >= self.failure_threshold else ServiceStatus.DEGRADED
            )
        finally:
            self._last_checked = datetime.datetime.now(datetime.timezone.utc)
        return self._last_status

    @property
    def status(self) -> ServiceStatus:
        return self._last_status

    @property
    def consecutive_failures(self) -> int:
        return self._consecutive_failures


class HealthMonitor:
    """Runs periodic health checks and triggers healing when services fail."""

    def __init__(self) -> None:
        self._checks: dict[str, HealthCheck] = {}
        self._running = False

    def register(self, check: HealthCheck) -> None:
        self._checks[check.name] = check

    async def check_all(self) -> dict[str, ServiceStatus]:
        results = {}
        for name, check in self._checks.items():
            status = await check.check()
            results[name] = status
        return results

    def all_statuses(self) -> dict[str, ServiceStatus]:
        return {name: check.status for name, check in self._checks.items()}

    async def start_monitoring(
        self,
        on_failure: Callable[[str, ServiceStatus], Awaitable[None]] | None = None,
        stop_event: asyncio.Event | None = None,
    ) -> None:
        """Run monitoring loop until stop_event is set."""
        self._running = True
        while self._running:
            if stop_event and stop_event.is_set():
                break
            results = await self.check_all()
            if on_failure:
                for name, status in results.items():
                    if status in (ServiceStatus.DOWN, ServiceStatus.DEGRADED):
                        try:
                            await on_failure(name, status)
                        except Exception as exc:
                            logger.error("on_failure callback raised: %s", exc)
            await asyncio.sleep(30)

    def stop(self) -> None:
        self._running = False
