"""
Health Check Aggregator.

Aggregates health status from all system components:
- Database connectivity
- Event bus status
- External API connections
- Background workers
- WebSocket connections

Emits health events for SSE streaming.
"""

from __future__ import annotations

import asyncio
import logging
import time
from collections.abc import Callable, Coroutine
from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import StrEnum
from typing import TYPE_CHECKING, Any
from uuid import uuid4

from dazzle_back.runtime.ops_database import (
    ComponentType,
    HealthCheckRecord,
    HealthStatus,
    OpsDatabase,
)

if TYPE_CHECKING:
    from dazzle_back.events.bus import EventBus

logger = logging.getLogger(__name__)


class AggregateStatus(StrEnum):
    """Overall system health status."""

    ALL_HEALTHY = "all_healthy"
    SOME_DEGRADED = "some_degraded"
    SOME_UNHEALTHY = "some_unhealthy"
    ALL_UNHEALTHY = "all_unhealthy"


@dataclass
class ComponentHealth:
    """Health status of a single component."""

    name: str
    component_type: ComponentType
    status: HealthStatus
    latency_ms: float | None = None
    message: str | None = None
    last_checked: datetime | None = None
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class SystemHealth:
    """Aggregated system health."""

    status: AggregateStatus
    components: list[ComponentHealth]
    checked_at: datetime
    healthy_count: int
    degraded_count: int
    unhealthy_count: int
    unknown_count: int

    @property
    def total_components(self) -> int:
        return len(self.components)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for JSON serialization."""
        return {
            "status": self.status.value,
            "checked_at": self.checked_at.isoformat(),
            "summary": {
                "total": self.total_components,
                "healthy": self.healthy_count,
                "degraded": self.degraded_count,
                "unhealthy": self.unhealthy_count,
                "unknown": self.unknown_count,
            },
            "components": [
                {
                    "name": c.name,
                    "type": c.component_type.value,
                    "status": c.status.value,
                    "latency_ms": c.latency_ms,
                    "message": c.message,
                    "last_checked": c.last_checked.isoformat() if c.last_checked else None,
                    "metadata": c.metadata,
                }
                for c in self.components
            ],
        }


# Type alias for health check functions
HealthCheckFn = Callable[[], Coroutine[Any, Any, ComponentHealth]]


class HealthAggregator:
    """
    Aggregates health from registered components.

    Components register health check functions that are called
    periodically. Results are stored in OpsDatabase and emitted
    via EventBus for SSE streaming.
    """

    def __init__(
        self,
        ops_db: OpsDatabase,
        event_bus: EventBus | None = None,
        check_interval_seconds: float = 30.0,
    ):
        """
        Initialize health aggregator.

        Args:
            ops_db: Operations database for storing health history
            event_bus: Optional event bus for emitting health events
            check_interval_seconds: Interval between health checks
        """
        self.ops_db = ops_db
        self.event_bus = event_bus
        self.check_interval = check_interval_seconds
        self._checks: dict[str, tuple[ComponentType, HealthCheckFn]] = {}
        self._latest: dict[str, ComponentHealth] = {}
        self._running = False
        self._task: asyncio.Task[None] | None = None

    def register(
        self,
        name: str,
        component_type: ComponentType,
        check_fn: HealthCheckFn,
    ) -> None:
        """
        Register a health check function.

        Args:
            name: Unique component name
            component_type: Type of component
            check_fn: Async function that returns ComponentHealth
        """
        self._checks[name] = (component_type, check_fn)

    def unregister(self, name: str) -> None:
        """Unregister a health check."""
        self._checks.pop(name, None)
        self._latest.pop(name, None)

    async def check_component(self, name: str) -> ComponentHealth | None:
        """Run health check for a single component."""
        if name not in self._checks:
            return None

        component_type, check_fn = self._checks[name]

        start = time.monotonic()
        try:
            health = await asyncio.wait_for(check_fn(), timeout=10.0)
        except TimeoutError:
            health = ComponentHealth(
                name=name,
                component_type=component_type,
                status=HealthStatus.UNHEALTHY,
                message="Health check timed out",
            )
        except Exception as e:
            health = ComponentHealth(
                name=name,
                component_type=component_type,
                status=HealthStatus.UNHEALTHY,
                message=f"Health check failed: {e}",
            )

        # Set latency if not already set
        if health.latency_ms is None:
            health.latency_ms = (time.monotonic() - start) * 1000

        health.last_checked = datetime.now(UTC)
        self._latest[name] = health

        # Record to database
        record = HealthCheckRecord(
            id=str(uuid4()),
            component=name,
            component_type=component_type,
            status=health.status,
            latency_ms=health.latency_ms,
            message=health.message,
            metadata=health.metadata,
            checked_at=health.last_checked,
        )
        self.ops_db.record_health_check(record)

        return health

    async def check_all(self) -> SystemHealth:
        """Run all registered health checks."""
        tasks = [self.check_component(name) for name in self._checks]
        results = await asyncio.gather(*tasks, return_exceptions=True)

        components: list[ComponentHealth] = []
        for result in results:
            if isinstance(result, ComponentHealth):
                components.append(result)
            elif isinstance(result, Exception):
                # This shouldn't happen given our error handling, but be safe
                pass

        # Calculate aggregate status
        healthy = sum(1 for c in components if c.status == HealthStatus.HEALTHY)
        degraded = sum(1 for c in components if c.status == HealthStatus.DEGRADED)
        unhealthy = sum(1 for c in components if c.status == HealthStatus.UNHEALTHY)
        unknown = sum(1 for c in components if c.status == HealthStatus.UNKNOWN)

        if unhealthy == len(components):
            aggregate_status = AggregateStatus.ALL_UNHEALTHY
        elif unhealthy > 0:
            aggregate_status = AggregateStatus.SOME_UNHEALTHY
        elif degraded > 0:
            aggregate_status = AggregateStatus.SOME_DEGRADED
        else:
            aggregate_status = AggregateStatus.ALL_HEALTHY

        system_health = SystemHealth(
            status=aggregate_status,
            components=components,
            checked_at=datetime.now(UTC),
            healthy_count=healthy,
            degraded_count=degraded,
            unhealthy_count=unhealthy,
            unknown_count=unknown,
        )

        # Emit event for SSE subscribers
        if self.event_bus:
            await self._emit_health_event(system_health)

        return system_health

    async def _emit_health_event(self, health: SystemHealth) -> None:
        """Emit health update event via EventBus."""
        if not self.event_bus:
            return

        from dazzle_back.events.envelope import EventEnvelope

        envelope = EventEnvelope.create(
            event_type="ops.health.updated",
            key="system",
            payload=health.to_dict(),
        )
        await self.event_bus.publish("ops.health", envelope)

    def get_latest(self) -> SystemHealth:
        """Get most recent health status without running new checks."""
        components = list(self._latest.values())

        healthy = sum(1 for c in components if c.status == HealthStatus.HEALTHY)
        degraded = sum(1 for c in components if c.status == HealthStatus.DEGRADED)
        unhealthy = sum(1 for c in components if c.status == HealthStatus.UNHEALTHY)
        unknown = sum(1 for c in components if c.status == HealthStatus.UNKNOWN)

        if not components:
            aggregate_status = AggregateStatus.ALL_HEALTHY
        elif unhealthy == len(components):
            aggregate_status = AggregateStatus.ALL_UNHEALTHY
        elif unhealthy > 0:
            aggregate_status = AggregateStatus.SOME_UNHEALTHY
        elif degraded > 0:
            aggregate_status = AggregateStatus.SOME_DEGRADED
        else:
            aggregate_status = AggregateStatus.ALL_HEALTHY

        return SystemHealth(
            status=aggregate_status,
            components=components,
            checked_at=datetime.now(UTC),
            healthy_count=healthy,
            degraded_count=degraded,
            unhealthy_count=unhealthy,
            unknown_count=unknown,
        )

    async def start_periodic_checks(self) -> None:
        """Start periodic health checking."""
        if self._running:
            return

        self._running = True
        self._task = asyncio.create_task(self._check_loop())

    async def stop_periodic_checks(self) -> None:
        """Stop periodic health checking."""
        self._running = False
        if self._task:
            self._task.cancel()
            try:
                await self._task
            except asyncio.CancelledError:
                pass
            self._task = None

    async def _check_loop(self) -> None:
        """Background loop for periodic checks."""
        while self._running:
            try:
                await self.check_all()
            except Exception:
                logger.warning("Health check loop iteration failed", exc_info=True)
            await asyncio.sleep(self.check_interval)


# =============================================================================
# Built-in Health Checks
# =============================================================================


def create_database_check(
    database_url: str,
    name: str = "database",
    db_path: str | None = None,  # Deprecated, ignored
) -> HealthCheckFn:
    """Create a database connectivity health check."""

    async def check() -> ComponentHealth:
        start = time.monotonic()
        try:
            import psycopg

            conn: Any = psycopg.connect(database_url)
            cursor = conn.cursor()
            cursor.execute("SELECT 1")
            cursor.fetchone()
            conn.close()
            latency = (time.monotonic() - start) * 1000

            return ComponentHealth(
                name=name,
                component_type=ComponentType.DATABASE,
                status=HealthStatus.HEALTHY,
                latency_ms=latency,
                message="Database responding",
            )
        except Exception as e:
            return ComponentHealth(
                name=name,
                component_type=ComponentType.DATABASE,
                status=HealthStatus.UNHEALTHY,
                message=f"Database error: {e}",
            )

    return check


def create_event_bus_check(
    event_bus: EventBus,
    name: str = "event_bus",
) -> HealthCheckFn:
    """Create an event bus health check."""

    async def check() -> ComponentHealth:
        start = time.monotonic()
        try:
            # Try to get topics or perform a lightweight operation
            # This depends on the EventBus implementation
            stats: dict[str, Any] = getattr(event_bus, "get_stats", lambda: {})()
            latency = (time.monotonic() - start) * 1000

            return ComponentHealth(
                name=name,
                component_type=ComponentType.EVENT_BUS,
                status=HealthStatus.HEALTHY,
                latency_ms=latency,
                message="Event bus operational",
                metadata=stats if isinstance(stats, dict) else {},
            )
        except Exception as e:
            return ComponentHealth(
                name=name,
                component_type=ComponentType.EVENT_BUS,
                status=HealthStatus.UNHEALTHY,
                message=f"Event bus error: {e}",
            )

    return check


def create_external_api_check(
    url: str,
    name: str,
    timeout_seconds: float = 5.0,
) -> HealthCheckFn:
    """Create an external API health check."""

    async def check() -> ComponentHealth:
        try:
            import httpx
        except ImportError:
            return ComponentHealth(
                name=name,
                component_type=ComponentType.EXTERNAL_API,
                status=HealthStatus.UNKNOWN,
                message="httpx not installed",
            )

        start = time.monotonic()
        try:
            async with httpx.AsyncClient() as client:
                response = await client.get(url, timeout=timeout_seconds)
                latency = (time.monotonic() - start) * 1000

                if response.status_code < 400:
                    return ComponentHealth(
                        name=name,
                        component_type=ComponentType.EXTERNAL_API,
                        status=HealthStatus.HEALTHY,
                        latency_ms=latency,
                        message=f"API responding (HTTP {response.status_code})",
                    )
                elif response.status_code < 500:
                    return ComponentHealth(
                        name=name,
                        component_type=ComponentType.EXTERNAL_API,
                        status=HealthStatus.DEGRADED,
                        latency_ms=latency,
                        message=f"API client error (HTTP {response.status_code})",
                    )
                else:
                    return ComponentHealth(
                        name=name,
                        component_type=ComponentType.EXTERNAL_API,
                        status=HealthStatus.UNHEALTHY,
                        latency_ms=latency,
                        message=f"API server error (HTTP {response.status_code})",
                    )
        except httpx.TimeoutException:
            return ComponentHealth(
                name=name,
                component_type=ComponentType.EXTERNAL_API,
                status=HealthStatus.UNHEALTHY,
                message=f"API timeout after {timeout_seconds}s",
            )
        except Exception as e:
            return ComponentHealth(
                name=name,
                component_type=ComponentType.EXTERNAL_API,
                status=HealthStatus.UNHEALTHY,
                message=f"API error: {e}",
            )

    return check


def create_websocket_check(
    ws_manager: Any,
    name: str = "websocket",
) -> HealthCheckFn:
    """Create a WebSocket manager health check."""

    async def check() -> ComponentHealth:
        try:
            # Get connection stats from WebSocket manager
            stats = ws_manager.get_stats() if hasattr(ws_manager, "get_stats") else {}
            connection_count = stats.get("connections", 0)

            return ComponentHealth(
                name=name,
                component_type=ComponentType.WEBSOCKET,
                status=HealthStatus.HEALTHY,
                message=f"{connection_count} active connections",
                metadata=stats,
            )
        except Exception as e:
            return ComponentHealth(
                name=name,
                component_type=ComponentType.WEBSOCKET,
                status=HealthStatus.UNHEALTHY,
                message=f"WebSocket error: {e}",
            )

    return check
