"""Health check reporter module for OCLAWMA.

Provides health monitoring for the OCLAWMA framework with:
- Per-subsystem health checks (queue, storage, memory, database)
- Configurable thresholds for warnings and critical states
- Health history tracking
- Webhook notifications
- Background monitoring
"""

from __future__ import annotations

import logging
import shutil
import sqlite3
import threading
import time
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Callable

from oclawma.queue.queue import JobQueue

logger = logging.getLogger(__name__)


class HealthStatus(str, Enum):
    """Health status enumeration.

    Statuses are ordered by severity for comparison.
    """

    HEALTHY = "healthy"
    DEGRADED = "degraded"
    UNHEALTHY = "unhealthy"

    def __gt__(self, other: object) -> bool:
        if isinstance(other, HealthStatus):
            severity = {
                HealthStatus.HEALTHY: 0,
                HealthStatus.DEGRADED: 1,
                HealthStatus.UNHEALTHY: 2,
            }
            return severity[self] > severity[other]
        return NotImplemented

    def __lt__(self, other: object) -> bool:
        if isinstance(other, HealthStatus):
            severity = {
                HealthStatus.HEALTHY: 0,
                HealthStatus.DEGRADED: 1,
                HealthStatus.UNHEALTHY: 2,
            }
            return severity[self] < severity[other]
        return NotImplemented


@dataclass
class SubsystemHealth:
    """Health status for a single subsystem.

    Attributes:
        name: Name of the subsystem
        status: Current health status
        message: Human-readable status message
        response_time_ms: Response time in milliseconds
        details: Additional details about the subsystem
    """

    name: str
    status: HealthStatus
    message: str
    response_time_ms: float
    details: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "name": self.name,
            "status": self.status.value,
            "message": self.message,
            "response_time_ms": self.response_time_ms,
            "details": self.details,
        }


@dataclass
class HealthCheck:
    """Result of a complete health check.

    Attributes:
        status: Overall health status
        timestamp: When the check was performed
        subsystems: Health status of each subsystem
    """

    status: HealthStatus
    timestamp: datetime
    subsystems: list[SubsystemHealth]

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "status": self.status.value,
            "timestamp": self.timestamp.isoformat(),
            "subsystems": [s.to_dict() for s in self.subsystems],
        }


@dataclass
class HealthCheckConfig:
    """Configuration for health checks.

    Attributes:
        check_interval_seconds: Seconds between automatic checks
        history_size: Maximum number of check results to keep
        disk_warning_percent: Disk usage threshold for degraded status
        disk_critical_percent: Disk usage threshold for unhealthy status
        memory_warning_percent: Memory usage threshold for degraded status
        memory_critical_percent: Memory usage threshold for unhealthy status
        queue_timeout_seconds: Timeout for queue operations check
        webhook_url: Optional URL for webhook notifications
        webhook_timeout_seconds: Timeout for webhook requests
    """

    check_interval_seconds: int = 60
    history_size: int = 100
    disk_warning_percent: int = 80
    disk_critical_percent: int = 95
    memory_warning_percent: int = 80
    memory_critical_percent: int = 95
    queue_timeout_seconds: int = 5
    webhook_url: str | None = None
    webhook_timeout_seconds: int = 10


class HealthHistory:
    """Thread-safe history of health check results."""

    def __init__(self, max_size: int = 100) -> None:
        """Initialize health history.

        Args:
            max_size: Maximum number of entries to keep
        """
        self._max_size = max_size
        self._history: list[HealthCheck] = []
        self._lock = threading.Lock()

    def add(self, check: HealthCheck) -> None:
        """Add a health check result to history.

        Args:
            check: Health check result to add
        """
        with self._lock:
            self._history.append(check)
            # Trim to max size (FIFO)
            if len(self._history) > self._max_size:
                self._history = self._history[-self._max_size :]

    def get_recent(self, count: int) -> list[HealthCheck]:
        """Get the most recent health check results.

        Args:
            count: Number of results to return

        Returns:
            List of recent health checks (most recent last)
        """
        with self._lock:
            return self._history[-count:] if count > 0 else []

    def get_all(self) -> list[HealthCheck]:
        """Get all health check results.

        Returns:
            List of all health checks
        """
        with self._lock:
            return self._history.copy()

    def get_since(self, since: datetime) -> list[HealthCheck]:
        """Get health check results since a specific time.

        Args:
            since: Cutoff time

        Returns:
            List of health checks performed since the given time
        """
        with self._lock:
            return [check for check in self._history if check.timestamp >= since]

    def clear(self) -> None:
        """Clear all history."""
        with self._lock:
            self._history.clear()

    def __len__(self) -> int:
        """Return number of entries in history."""
        with self._lock:
            return len(self._history)


class HealthCheckError(Exception):
    """Exception raised during health checks."""

    def __init__(self, message: str, subsystem: str | None = None) -> None:
        """Initialize error.

        Args:
            message: Error message
            subsystem: Name of subsystem that failed
        """
        super().__init__(message)
        self.subsystem = subsystem


# Individual health check implementations


class DatabaseHealthCheck:
    """Health check for database connectivity."""

    def __init__(self, db_path: str) -> None:
        """Initialize database health check.

        Args:
            db_path: Path to the database file
        """
        self.db_path = db_path

    def check(self) -> SubsystemHealth:
        """Perform database health check.

        Returns:
            SubsystemHealth with database status
        """
        start_time = time.time()

        try:
            conn = sqlite3.connect(self.db_path, timeout=5.0)
            # Test both read and write operations
            conn.execute("SELECT 1")
            conn.execute("CREATE TABLE IF NOT EXISTS _health_check (id INTEGER PRIMARY KEY)")
            conn.execute(
                "INSERT INTO _health_check (id) VALUES (1) ON CONFLICT(id) DO UPDATE SET id=1"
            )
            conn.execute("DROP TABLE IF EXISTS _health_check")
            conn.close()

            response_time = (time.time() - start_time) * 1000

            return SubsystemHealth(
                name="database",
                status=HealthStatus.HEALTHY,
                message="Database connected and responding",
                response_time_ms=response_time,
            )
        except Exception as e:
            response_time = (time.time() - start_time) * 1000
            return SubsystemHealth(
                name="database",
                status=HealthStatus.UNHEALTHY,
                message=f"Database error: {e}",
                response_time_ms=response_time,
            )


class QueueOperationsCheck:
    """Health check for queue operations."""

    def __init__(self, db_path: str, timeout_seconds: int = 5) -> None:
        """Initialize queue operations check.

        Args:
            db_path: Path to the queue database
            timeout_seconds: Timeout for queue operations
        """
        self.db_path = db_path
        self.timeout_seconds = timeout_seconds

    def check(self) -> SubsystemHealth:
        """Perform queue operations health check.

        Returns:
            SubsystemHealth with queue status
        """
        start_time = time.time()

        try:
            queue = JobQueue(self.db_path)

            # Test enqueue
            job = queue.enqueue({"test": "health_check"})

            # Test dequeue
            dequeued = queue.dequeue()

            # Test complete
            if dequeued:
                queue.complete(dequeued.id)

            # Cleanup - delete the test job
            if job.id:
                queue.store.delete(job.id)

            queue.close()

            response_time = (time.time() - start_time) * 1000

            return SubsystemHealth(
                name="queue",
                status=HealthStatus.HEALTHY,
                message="Queue operations healthy",
                response_time_ms=response_time,
            )
        except Exception as e:
            response_time = (time.time() - start_time) * 1000
            return SubsystemHealth(
                name="queue",
                status=HealthStatus.UNHEALTHY,
                message=f"Queue error: {e}",
                response_time_ms=response_time,
            )


class DiskSpaceCheck:
    """Health check for disk space usage."""

    def __init__(
        self,
        warning_percent: int = 80,
        critical_percent: int = 95,
    ) -> None:
        """Initialize disk space check.

        Args:
            warning_percent: Usage threshold for degraded status
            critical_percent: Usage threshold for unhealthy status
        """
        self.warning_percent = warning_percent
        self.critical_percent = critical_percent

    def check(self) -> SubsystemHealth:
        """Perform disk space health check.

        Returns:
            SubsystemHealth with storage status
        """
        start_time = time.time()

        try:
            total, used, free = shutil.disk_usage("/")
            used_percent = (used / total) * 100

            response_time = (time.time() - start_time) * 1000

            details = {
                "total_bytes": total,
                "used_bytes": used,
                "free_bytes": free,
                "percent": used_percent,
            }

            if used_percent >= self.critical_percent:
                return SubsystemHealth(
                    name="storage",
                    status=HealthStatus.UNHEALTHY,
                    message=f"Disk critically full: {used_percent:.1f}% used",
                    response_time_ms=response_time,
                    details=details,
                )
            elif used_percent >= self.warning_percent:
                return SubsystemHealth(
                    name="storage",
                    status=HealthStatus.DEGRADED,
                    message=f"Disk usage high: {used_percent:.1f}% used",
                    response_time_ms=response_time,
                    details=details,
                )
            else:
                return SubsystemHealth(
                    name="storage",
                    status=HealthStatus.HEALTHY,
                    message=f"Disk healthy: {used_percent:.1f}% used",
                    response_time_ms=response_time,
                    details=details,
                )
        except Exception as e:
            response_time = (time.time() - start_time) * 1000
            return SubsystemHealth(
                name="storage",
                status=HealthStatus.UNHEALTHY,
                message=f"Disk check error: {e}",
                response_time_ms=response_time,
            )


class MemoryUsageCheck:
    """Health check for memory usage."""

    def __init__(
        self,
        warning_percent: int = 80,
        critical_percent: int = 95,
    ) -> None:
        """Initialize memory usage check.

        Args:
            warning_percent: Usage threshold for degraded status
            critical_percent: Usage threshold for unhealthy status
        """
        self.warning_percent = warning_percent
        self.critical_percent = critical_percent

    def check(self) -> SubsystemHealth:
        """Perform memory health check.

        Returns:
            SubsystemHealth with memory status
        """
        start_time = time.time()

        try:
            import psutil

            mem = psutil.virtual_memory()
            used_percent = mem.percent

            response_time = (time.time() - start_time) * 1000

            details = {
                "total_bytes": mem.total,
                "available_bytes": mem.available,
                "used_bytes": mem.used,
                "percent": used_percent,
            }

            if used_percent >= self.critical_percent:
                return SubsystemHealth(
                    name="memory",
                    status=HealthStatus.UNHEALTHY,
                    message=f"Memory critically high: {used_percent:.1f}% used",
                    response_time_ms=response_time,
                    details=details,
                )
            elif used_percent >= self.warning_percent:
                return SubsystemHealth(
                    name="memory",
                    status=HealthStatus.DEGRADED,
                    message=f"Memory usage high: {used_percent:.1f}% used",
                    response_time_ms=response_time,
                    details=details,
                )
            else:
                return SubsystemHealth(
                    name="memory",
                    status=HealthStatus.HEALTHY,
                    message=f"Memory healthy: {used_percent:.1f}% used",
                    response_time_ms=response_time,
                    details=details,
                )
        except ImportError:
            response_time = (time.time() - start_time) * 1000
            return SubsystemHealth(
                name="memory",
                status=HealthStatus.HEALTHY,
                message="Memory check skipped (psutil not installed)",
                response_time_ms=response_time,
                details={"percent": 0, "note": "psutil not installed"},
            )
        except Exception as e:
            response_time = (time.time() - start_time) * 1000
            return SubsystemHealth(
                name="memory",
                status=HealthStatus.UNHEALTHY,
                message=f"Memory check error: {e}",
                response_time_ms=response_time,
            )


class WebhookNotifier:
    """Notifier for sending health check results via webhook."""

    def __init__(self, webhook_url: str | None, timeout_seconds: int = 10) -> None:
        """Initialize webhook notifier.

        Args:
            webhook_url: URL to send notifications to
            timeout_seconds: Timeout for webhook requests
        """
        self.webhook_url = webhook_url
        self.timeout_seconds = timeout_seconds

    def notify(self, health_check: HealthCheck) -> bool:
        """Send health check result to webhook.

        Args:
            health_check: Health check result to send

        Returns:
            True if notification was sent successfully, False otherwise
        """
        if not self.webhook_url:
            return False

        try:
            import httpx

            payload = {
                "status": health_check.status.value,
                "timestamp": health_check.timestamp.isoformat(),
                "subsystems": [s.to_dict() for s in health_check.subsystems],
            }

            response = httpx.post(
                self.webhook_url,
                json=payload,
                timeout=self.timeout_seconds,
            )

            return response.status_code < 300
        except Exception as e:
            logger.warning(f"Failed to send webhook notification: {e}")
            return False


class HealthChecker:
    """Performs health checks for all subsystems."""

    def __init__(
        self,
        db_path: str = ":memory:",
        config: HealthCheckConfig | None = None,
    ) -> None:
        """Initialize health checker.

        Args:
            db_path: Path to the database
            config: Health check configuration
        """
        self.config = config or HealthCheckConfig()
        self.history = HealthHistory(max_size=self.config.history_size)
        self._db_path = db_path

        # Initialize checks
        self._checks: list[Callable[[], SubsystemHealth]] = [
            lambda: DatabaseHealthCheck(db_path).check(),
            lambda: QueueOperationsCheck(db_path, self.config.queue_timeout_seconds).check(),
            lambda: DiskSpaceCheck(
                self.config.disk_warning_percent,
                self.config.disk_critical_percent,
            ).check(),
            lambda: MemoryUsageCheck(
                self.config.memory_warning_percent,
                self.config.memory_critical_percent,
            ).check(),
        ]

    def check_all(self) -> HealthCheck:
        """Run all health checks.

        Returns:
            HealthCheck with overall status and all subsystem results
        """
        timestamp = datetime.utcnow()
        subsystems: list[SubsystemHealth] = []

        for check_func in self._checks:
            try:
                result = check_func()
                subsystems.append(result)
            except Exception as e:
                subsystems.append(
                    SubsystemHealth(
                        name="unknown",
                        status=HealthStatus.UNHEALTHY,
                        message=f"Check failed: {e}",
                        response_time_ms=0,
                    )
                )

        # Determine overall status (worst of all subsystems)
        overall_status = HealthStatus.HEALTHY
        for subsystem in subsystems:
            if subsystem.status > overall_status:
                overall_status = subsystem.status

        health_check = HealthCheck(
            status=overall_status,
            timestamp=timestamp,
            subsystems=subsystems,
        )

        self.history.add(health_check)

        return health_check

    def get_health_report(self) -> dict[str, Any]:
        """Get a comprehensive health report.

        Returns:
            Dictionary with health status and history summary
        """
        if not self.history.get_all():
            self.check_all()

        recent = self.history.get_all()
        latest = recent[-1] if recent else None

        return {
            "status": latest.status.value if latest else "unknown",
            "timestamp": latest.timestamp.isoformat() if latest else None,
            "subsystems": [s.to_dict() for s in (latest.subsystems if latest else [])],
            "history_summary": {
                "total_checks": len(self.history),
                "recent_statuses": [check.status.value for check in self.history.get_recent(10)],
            },
        }


class HealthReporter:
    """Health reporter with background monitoring capabilities."""

    def __init__(
        self,
        db_path: str = ":memory:",
        config: HealthCheckConfig | None = None,
    ) -> None:
        """Initialize health reporter.

        Args:
            db_path: Path to the database
            config: Health check configuration
        """
        self.config = config or HealthCheckConfig()
        self.checker = HealthChecker(db_path=db_path, config=self.config)
        self._notifier = WebhookNotifier(
            self.config.webhook_url,
            self.config.webhook_timeout_seconds,
        )
        self._running = False
        self._thread: threading.Thread | None = None
        self._stop_event = threading.Event()
        self._lock = threading.Lock()
        self._last_check: HealthCheck | None = None
        self._check_count = 0

    @property
    def running(self) -> bool:
        """Check if background monitoring is running."""
        with self._lock:
            return self._running

    def check_now(self) -> HealthCheck:
        """Run a health check immediately.

        Returns:
            HealthCheck result
        """
        result = self.checker.check_all()

        with self._lock:
            self._last_check = result
            self._check_count += 1

        # Send webhook notification if configured
        self._notifier.notify(result)

        return result

    def get_status(self) -> dict[str, Any]:
        """Get current reporter status.

        Returns:
            Dictionary with reporter status
        """
        with self._lock:
            return {
                "running": self._running,
                "last_check": self._last_check.timestamp.isoformat() if self._last_check else None,
                "check_count": self._check_count,
            }

    def get_endpoint_response(self) -> dict[str, Any]:
        """Get response suitable for a health endpoint.

        Returns:
            Dictionary with health status for endpoint
        """
        if not self._last_check:
            self.check_now()

        latest = self._last_check

        return {
            "status": latest.status.value if latest else "unknown",
            "timestamp": latest.timestamp.isoformat() if latest else datetime.utcnow().isoformat(),
            "subsystems": [s.to_dict() for s in (latest.subsystems if latest else [])],
        }

    def start(self) -> None:
        """Start background health monitoring."""
        with self._lock:
            if self._running:
                return

            self._running = True
            self._stop_event.clear()
            self._thread = threading.Thread(target=self._monitor_loop, daemon=True)
            self._thread.start()

    def stop(self) -> None:
        """Stop background health monitoring."""
        with self._lock:
            if not self._running:
                return

            self._stop_event.set()
            self._running = False

        if self._thread and self._thread.is_alive():
            self._thread.join(timeout=5.0)

    def _monitor_loop(self) -> None:
        """Background monitoring loop."""
        while not self._stop_event.is_set():
            try:
                self.check_now()
            except Exception as e:
                logger.error(f"Health check failed: {e}")

            # Wait for next check interval
            self._stop_event.wait(self.config.check_interval_seconds)

    def __enter__(self) -> HealthReporter:
        """Context manager entry."""
        return self

    def __exit__(self, *args: Any) -> None:
        """Context manager exit."""
        self.stop()


# Convenience exports
__all__ = [
    "HealthStatus",
    "SubsystemHealth",
    "HealthCheck",
    "HealthCheckConfig",
    "HealthHistory",
    "HealthChecker",
    "HealthReporter",
    "WebhookNotifier",
    "HealthCheckError",
    "DatabaseHealthCheck",
    "QueueOperationsCheck",
    "DiskSpaceCheck",
    "MemoryUsageCheck",
]
