"""
cortex/api/health.py — HealthChecker infrastructure for Issue #456.

Provides rich health monitoring during active plugin operation:
  - Storage connectivity (read/write probe)
  - Plugin initialization status
  - Error rate over the last 5 minutes (from MetricsCollector when available)
  - Memory usage (RSS via resource module)
  - Uptime

Usage::

    from cortex.api.health import HealthChecker
    report = await HealthChecker.check(plugin)
    report_detail = await HealthChecker.check(plugin, detail=True)
"""
from __future__ import annotations

import time
import logging
from dataclasses import dataclass, field
from typing import Any, Dict, Optional

logger = logging.getLogger(__name__)

# Module-level start time for uptime calculation
_SERVER_START_TIME: float = time.time()

# Cortex version constant (mirrors http_server.py app version)
_VERSION = "1.2.0"


@dataclass
class HealthReport:
    """Structured health report returned by HealthChecker.check().

    Attributes:
        status: "healthy" | "degraded" | "unhealthy"
        checks: Per-subsystem check results dict
        uptime_seconds: Seconds since the server started
        version: Cortex version string
        detail: Optional extended info (storage stats, error rates) when
                ``detail=True`` was requested
    """
    status: str  # healthy | degraded | unhealthy
    checks: Dict[str, Any] = field(default_factory=dict)
    uptime_seconds: float = 0.0
    version: str = _VERSION
    detail: Optional[Dict[str, Any]] = None


class HealthChecker:
    """Health check infrastructure for Cortex plugin operation.

    All methods are class-level (no instantiation required) so the checker
    is stateless and safe to call from any async context.
    """

    @classmethod
    async def check(
        cls,
        plugin: Any,
        *,
        detail: bool = False,
    ) -> HealthReport:
        """Run all health checks and return a HealthReport.

        Args:
            plugin:  CortexPlugin instance to inspect.
            detail:  When True, include extended info (storage stats,
                     error rates, memory usage) in report.detail.

        Returns:
            HealthReport with status of "healthy", "degraded", or "unhealthy".
        """
        checks: Dict[str, Any] = {}
        degraded_reasons: list = []
        unhealthy_reasons: list = []

        # ── 1. Plugin initialization ────────────────────────────────────────
        init_errors = getattr(plugin, "_init_errors", [])
        if init_errors:
            checks["plugin_initialized"] = {
                "status": "degraded",
                "errors": list(init_errors),
            }
            degraded_reasons.append("plugin has init errors")
        else:
            checks["plugin_initialized"] = {"status": "ok"}

        # ── 2. Storage connectivity ──────────────────────────────────────────
        storage = getattr(plugin, "storage", None)
        if storage is None:
            checks["storage"] = {"status": "unhealthy", "error": "storage not available"}
            unhealthy_reasons.append("storage unavailable")
        else:
            storage_ok, storage_err = await cls._check_storage(storage)
            if storage_ok:
                checks["storage"] = {"status": "ok"}
            else:
                checks["storage"] = {"status": "unhealthy", "error": storage_err}
                unhealthy_reasons.append(f"storage: {storage_err}")

        # ── 3. Error rate (last 5 minutes from MetricsCollector) ────────────
        metrics = getattr(plugin, "_metrics", None)
        error_rate = await cls._get_error_rate(metrics)
        if error_rate is None:
            checks["error_rate"] = {"status": "unknown", "rate_per_min": None}
        elif error_rate > 10.0:
            checks["error_rate"] = {
                "status": "degraded",
                "rate_per_min": round(error_rate, 2),
                "threshold": 10.0,
            }
            degraded_reasons.append(f"high error rate: {error_rate:.1f}/min")
        else:
            checks["error_rate"] = {
                "status": "ok",
                "rate_per_min": round(error_rate, 2),
            }

        # ── 4. Memory usage (RSS) ────────────────────────────────────────────
        rss_mb = cls._get_rss_mb()
        checks["memory_rss_mb"] = rss_mb

        # ── 5. Uptime ────────────────────────────────────────────────────────
        uptime = time.time() - _SERVER_START_TIME

        # ── Overall status ───────────────────────────────────────────────────
        if unhealthy_reasons:
            status = "unhealthy"
        elif degraded_reasons:
            status = "degraded"
        else:
            status = "healthy"

        # ── Optional detail block ────────────────────────────────────────────
        detail_data: Optional[Dict[str, Any]] = None
        if detail:
            detail_data = await cls._build_detail(plugin, metrics, uptime)

        return HealthReport(
            status=status,
            checks=checks,
            uptime_seconds=round(uptime, 2),
            version=_VERSION,
            detail=detail_data,
        )

    # ── Private helpers ──────────────────────────────────────────────────────

    @classmethod
    async def _check_storage(cls, storage: Any) -> tuple:
        """Probe storage connectivity. Returns (ok: bool, error: str)."""
        try:
            # Prefer ping() if available (quick liveness check)
            if hasattr(storage, "ping"):
                await storage.ping()
                return True, ""
            # Fallback: check connection attribute
            if hasattr(storage, "_conn") and storage._conn is not None:
                return True, ""
            return False, "no connection"
        except Exception as exc:
            return False, str(exc)

    @classmethod
    async def _get_error_rate(cls, metrics: Any) -> Optional[float]:
        """Estimate error rate per minute from metrics counters.

        Returns None when metrics are unavailable.
        """
        if metrics is None:
            return None
        try:
            summary = await metrics.get_summary()
            counters = summary.get("counters", {})
            uptime = summary.get("uptime_seconds", 0.0)
            if uptime <= 0:
                return 0.0

            # Sum any counter that looks like an error counter
            error_total = 0.0
            for name, val in counters.items():
                if "error" in name.lower() or "fail" in name.lower():
                    if isinstance(val, (int, float)):
                        error_total += val
                    elif isinstance(val, dict):
                        error_total += val.get("total", 0.0)

            # Rate per minute
            minutes_up = uptime / 60.0
            if minutes_up <= 0:
                return 0.0
            rate = error_total / minutes_up
            return rate
        except Exception as exc:
            logger.debug("Error reading metrics for health check: %s", exc)
            return None

    @classmethod
    def _get_rss_mb(cls) -> float:
        """Get current process RSS memory in MB."""
        try:
            import resource
            ru = resource.getrusage(resource.RUSAGE_SELF)
            # ru_maxrss is in bytes on Linux, kilobytes on macOS
            import sys
            if sys.platform == "darwin":
                return round(ru.ru_maxrss / (1024 * 1024), 2)
            else:
                return round(ru.ru_maxrss / 1024, 2)
        except Exception:
            return 0.0

    @classmethod
    async def _build_detail(
        cls,
        plugin: Any,
        metrics: Any,
        uptime: float,
    ) -> Dict[str, Any]:
        """Build the extended detail block for ?detail=true requests."""
        detail: Dict[str, Any] = {
            "uptime_seconds": round(uptime, 2),
            "memory_rss_mb": cls._get_rss_mb(),
        }

        # Storage stats
        storage = getattr(plugin, "storage", None)
        if storage is not None:
            storage_stats: Dict[str, Any] = {}
            try:
                owner_id = getattr(getattr(plugin, "config", None), "owner_id", "default")
                if hasattr(storage, "count_claims"):
                    storage_stats["claims_count"] = await storage.count_claims(owner_id=owner_id)
                if hasattr(storage, "count_sessions"):
                    storage_stats["sessions_count"] = await storage.count_sessions(owner_id=owner_id)
                storage_stats["connected"] = True
            except Exception as exc:
                storage_stats["error"] = str(exc)
                storage_stats["connected"] = False
            detail["storage"] = storage_stats

        # Metrics / error rates
        if metrics is not None:
            try:
                summary = await metrics.get_summary()
                detail["metrics"] = {
                    "counters": summary.get("counters", {}),
                    "uptime_seconds": summary.get("uptime_seconds", 0.0),
                }
            except Exception as exc:
                detail["metrics"] = {"error": str(exc)}

        return detail
