"""System monitoring and health checking module.

This module provides the SystemMonitor class for collecting system metrics,
monitoring agent performance, and exporting Prometheus-compatible metrics.
"""

from __future__ import annotations

import threading
import time
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any

from loguru import logger

from openclaw.monitoring.metrics import Counter, Gauge, Histogram, MetricsCollector


try:
    import psutil

    PSUTIL_AVAILABLE = True
except ImportError:
    PSUTIL_AVAILABLE = False


@dataclass
class AgentPerformanceMetrics:
    """Performance metrics for a single agent.

    Attributes:
        agent_id: Unique identifier for the agent
        decision_count: Number of decisions made
        total_response_time: Sum of all response times
        avg_response_time: Average response time
        error_count: Number of errors encountered
        last_activity: Timestamp of last activity
    """

    agent_id: str
    decision_count: int = 0
    total_response_time: float = 0.0
    avg_response_time: float = 0.0
    error_count: int = 0
    last_activity: str = field(default_factory=lambda: datetime.now().isoformat())

    def record_decision(self, response_time: float) -> None:
        """Record a decision with its response time.

        Args:
            response_time: Time taken for the decision in seconds
        """
        self.decision_count += 1
        self.total_response_time += response_time
        self.avg_response_time = self.total_response_time / self.decision_count
        self.last_activity = datetime.now().isoformat()

    def record_error(self) -> None:
        """Record an error occurrence."""
        self.error_count += 1
        self.last_activity = datetime.now().isoformat()

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "agent_id": self.agent_id,
            "decision_count": self.decision_count,
            "total_response_time": self.total_response_time,
            "avg_response_time": self.avg_response_time,
            "error_count": self.error_count,
            "last_activity": self.last_activity,
        }


@dataclass
class SystemMetrics:
    """System-level metrics snapshot.

    Attributes:
        timestamp: ISO format timestamp
        cpu_percent: CPU usage percentage
        memory_percent: Memory usage percentage
        memory_used_mb: Used memory in MB
        memory_total_mb: Total memory in MB
        disk_percent: Disk usage percentage
        open_file_descriptors: Number of open file descriptors
        thread_count: Number of threads
    """

    timestamp: str
    cpu_percent: float
    memory_percent: float
    memory_used_mb: float
    memory_total_mb: float
    disk_percent: float
    open_file_descriptors: int = 0
    thread_count: int = 0

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "timestamp": self.timestamp,
            "cpu_percent": self.cpu_percent,
            "memory_percent": self.memory_percent,
            "memory_used_mb": self.memory_used_mb,
            "memory_total_mb": self.memory_total_mb,
            "disk_percent": self.disk_percent,
            "open_file_descriptors": self.open_file_descriptors,
            "thread_count": self.thread_count,
        }


@dataclass
class HealthStatus:
    """Health check status.

    Attributes:
        status: Health status (healthy, degraded, unhealthy)
        timestamp: ISO format timestamp
        checks: Dictionary of individual check results
        message: Optional status message
    """

    status: str
    timestamp: str
    checks: dict[str, dict[str, Any]]
    message: str = ""

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

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "status": self.status,
            "timestamp": self.timestamp,
            "checks": self.checks,
            "message": self.message,
        }


@dataclass
class AlertThresholds:
    """Configurable alert thresholds.

    Attributes:
        cpu_warning: CPU percentage for warning alert
        cpu_critical: CPU percentage for critical alert
        memory_warning: Memory percentage for warning alert
        memory_critical: Memory percentage for critical alert
        error_rate_threshold: Error rate percentage for alert
        response_time_threshold: Response time in seconds for alert
    """

    cpu_warning: float = 70.0
    cpu_critical: float = 90.0
    memory_warning: float = 80.0
    memory_critical: float = 95.0
    error_rate_threshold: float = 5.0
    response_time_threshold: float = 5.0

    def to_dict(self) -> dict[str, float]:
        """Convert to dictionary."""
        return {
            "cpu_warning": self.cpu_warning,
            "cpu_critical": self.cpu_critical,
            "memory_warning": self.memory_warning,
            "memory_critical": self.memory_critical,
            "error_rate_threshold": self.error_rate_threshold,
            "response_time_threshold": self.response_time_threshold,
        }


class SystemMonitor:
    """Monitor system health and agent performance.

    The SystemMonitor collects system metrics (CPU, memory), tracks
    agent performance (response times, error rates), and provides
    health check endpoints with Prometheus-compatible metrics export.

    Example:
        monitor = SystemMonitor()

        # Record agent decision
        monitor.record_agent_decision("trader_1", 0.5)  # 500ms response time

        # Get health status
        health = monitor.check_health()
        print(health.to_dict())

        # Export Prometheus metrics
        metrics = monitor.get_prometheus_metrics()
        print(metrics)
    """

    def __init__(
        self,
        thresholds: AlertThresholds | None = None,
        sampling_interval: float = 60.0,
    ) -> None:
        """Initialize the system monitor.

        Args:
            thresholds: Alert threshold configuration
            sampling_interval: System metrics sampling interval in seconds
        """
        self._thresholds = thresholds or AlertThresholds()
        self._sampling_interval = sampling_interval
        self._collector = MetricsCollector()

        # Agent tracking
        self._agent_metrics: dict[str, AgentPerformanceMetrics] = {}
        self._total_requests: int = 0
        self._total_errors: int = 0

        # System metrics history
        self._system_history: list[SystemMetrics] = []
        self._max_history_size: int = 1000

        # Threading
        self._lock = threading.Lock()
        self._sampling_thread: threading.Thread | None = None
        self._stop_sampling = threading.Event()

        # Initialize metrics
        self._init_metrics()

        if not PSUTIL_AVAILABLE:
            logger.warning(
                "psutil not available. System metrics will be limited. "
                "Install psutil for full system monitoring."
            )

    def _init_metrics(self) -> None:
        """Initialize Prometheus metrics."""
        # System metrics
        self._collector.gauge(
            "openclaw_system_cpu_percent",
            "CPU usage percentage",
        )
        self._collector.gauge(
            "openclaw_system_memory_percent",
            "Memory usage percentage",
        )
        self._collector.gauge(
            "openclaw_system_memory_used_bytes",
            "Used memory in bytes",
        )
        self._collector.gauge(
            "openclaw_system_disk_percent",
            "Disk usage percentage",
        )

        # Agent performance metrics
        self._collector.counter(
            "openclaw_agent_decisions_total",
            "Total number of agent decisions",
        )
        self._collector.histogram(
            "openclaw_agent_response_time_seconds",
            "Agent decision response time",
            buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0],
        )
        self._collector.counter(
            "openclaw_agent_errors_total",
            "Total number of agent errors",
        )
        self._collector.gauge(
            "openclaw_agent_error_rate",
            "Agent error rate percentage",
        )

    def start(self) -> None:
        """Start the background sampling thread."""
        if self._sampling_thread is not None and self._sampling_thread.is_alive():
            logger.warning("System monitor already running")
            return

        self._stop_sampling.clear()
        self._sampling_thread = threading.Thread(
            target=self._sampling_loop,
            daemon=True,
        )
        self._sampling_thread.start()
        logger.info("System monitor started")

    def stop(self) -> None:
        """Stop the background sampling thread."""
        if self._sampling_thread is None or not self._sampling_thread.is_alive():
            return

        self._stop_sampling.set()
        self._sampling_thread.join(timeout=5.0)
        logger.info("System monitor stopped")

    def _sampling_loop(self) -> None:
        """Background loop for collecting system metrics."""
        while not self._stop_sampling.is_set():
            try:
                self.collect_system_metrics()
            except Exception as e:
                logger.error(f"Error collecting system metrics: {e}")

            # Wait for next sampling interval
            self._stop_sampling.wait(self._sampling_interval)

    def collect_system_metrics(self) -> SystemMetrics | None:
        """Collect current system metrics.

        Returns:
            SystemMetrics snapshot or None if psutil is unavailable
        """
        if not PSUTIL_AVAILABLE:
            return None

        try:
            # CPU
            cpu_percent = psutil.cpu_percent(interval=None)

            # Memory
            memory = psutil.virtual_memory()
            memory_percent = memory.percent
            memory_used_mb = memory.used / (1024 * 1024)
            memory_total_mb = memory.total / (1024 * 1024)

            # Disk
            disk = psutil.disk_usage("/")
            disk_percent = (disk.used / disk.total) * 100

            # Process info
            process = psutil.Process()
            open_fds = process.num_fds() if hasattr(process, "num_fds") else 0
            thread_count = process.num_threads()

            metrics = SystemMetrics(
                timestamp=datetime.now().isoformat(),
                cpu_percent=cpu_percent,
                memory_percent=memory_percent,
                memory_used_mb=memory_used_mb,
                memory_total_mb=memory_total_mb,
                disk_percent=disk_percent,
                open_file_descriptors=open_fds,
                thread_count=thread_count,
            )

            # Update Prometheus metrics
            self._collector.gauge("openclaw_system_cpu_percent").set(cpu_percent)
            self._collector.gauge("openclaw_system_memory_percent").set(memory_percent)
            self._collector.gauge("openclaw_system_memory_used_bytes").set(
                memory_used_mb * 1024 * 1024
            )
            self._collector.gauge("openclaw_system_disk_percent").set(disk_percent)

            # Store in history
            with self._lock:
                self._system_history.append(metrics)
                if len(self._system_history) > self._max_history_size:
                    self._system_history = self._system_history[-self._max_history_size:]

            return metrics

        except Exception as e:
            logger.error(f"Failed to collect system metrics: {e}")
            return None

    def record_agent_decision(self, agent_id: str, response_time: float) -> None:
        """Record an agent decision with its response time.

        Args:
            agent_id: Unique identifier for the agent
            response_time: Time taken for the decision in seconds
        """
        with self._lock:
            if agent_id not in self._agent_metrics:
                self._agent_metrics[agent_id] = AgentPerformanceMetrics(agent_id=agent_id)

            self._agent_metrics[agent_id].record_decision(response_time)
            self._total_requests += 1

        # Update Prometheus metrics
        self._collector.counter("openclaw_agent_decisions_total").inc(
            labels={"agent_id": agent_id}
        )
        self._collector.histogram("openclaw_agent_response_time_seconds").observe(
            response_time, labels={"agent_id": agent_id}
        )

        # Check for slow response alert
        if response_time > self._thresholds.response_time_threshold:
            logger.warning(
                f"Agent {agent_id} slow response: {response_time:.3f}s "
                f"(threshold: {self._thresholds.response_time_threshold}s)"
            )

    def record_agent_error(self, agent_id: str) -> None:
        """Record an agent error.

        Args:
            agent_id: Unique identifier for the agent
        """
        with self._lock:
            if agent_id not in self._agent_metrics:
                self._agent_metrics[agent_id] = AgentPerformanceMetrics(agent_id=agent_id)

            self._agent_metrics[agent_id].record_error()
            self._total_errors += 1

        # Update Prometheus metrics
        self._collector.counter("openclaw_agent_errors_total").inc(
            labels={"agent_id": agent_id}
        )

    def check_health(self) -> HealthStatus:
        """Perform health checks.

        Returns:
            HealthStatus with detailed check results
        """
        checks: dict[str, dict[str, Any]] = {}
        timestamp = datetime.now().isoformat()

        # Collect current system metrics
        system_metrics = self.collect_system_metrics()

        # CPU check
        if system_metrics:
            cpu_ok = system_metrics.cpu_percent < self._thresholds.cpu_critical
            cpu_warning = system_metrics.cpu_percent >= self._thresholds.cpu_warning

            checks["cpu"] = {
                "status": HealthStatus.HEALTHY
                if cpu_ok
                else HealthStatus.UNHEALTHY,
                "value": system_metrics.cpu_percent,
                "threshold": self._thresholds.cpu_critical,
                "warning": cpu_warning,
            }

            # Memory check
            mem_ok = system_metrics.memory_percent < self._thresholds.memory_critical
            mem_warning = system_metrics.memory_percent >= self._thresholds.memory_warning

            checks["memory"] = {
                "status": HealthStatus.HEALTHY
                if mem_ok
                else HealthStatus.UNHEALTHY,
                "value": system_metrics.memory_percent,
                "threshold": self._thresholds.memory_critical,
                "warning": mem_warning,
            }

            # Disk check
            disk_ok = system_metrics.disk_percent < 90.0
            checks["disk"] = {
                "status": HealthStatus.HEALTHY
                if disk_ok
                else HealthStatus.UNHEALTHY,
                "value": system_metrics.disk_percent,
                "threshold": 90.0,
            }
        else:
            checks["system"] = {
                "status": HealthStatus.DEGRADED,
                "message": "System metrics unavailable (psutil not installed)",
            }

        # Error rate check
        total_requests = self._total_requests
        total_errors = self._total_errors
        error_rate = (total_errors / total_requests * 100) if total_requests > 0 else 0.0

        error_ok = error_rate < self._thresholds.error_rate_threshold
        checks["error_rate"] = {
            "status": HealthStatus.HEALTHY if error_ok else HealthStatus.UNHEALTHY,
            "value": error_rate,
            "threshold": self._thresholds.error_rate_threshold,
            "error_count": total_errors,
            "request_count": total_requests,
        }

        # Update error rate gauge
        for agent_id, metrics in self._agent_metrics.items():
            agent_error_rate = (
                (metrics.error_count / metrics.decision_count * 100)
                if metrics.decision_count > 0
                else 0.0
            )
            self._collector.gauge("openclaw_agent_error_rate").set(
                agent_error_rate, labels={"agent_id": agent_id}
            )

        # Determine overall status
        if all(c["status"] == HealthStatus.HEALTHY for c in checks.values()):
            status = HealthStatus.HEALTHY
            message = "All systems operational"
        elif any(c["status"] == HealthStatus.UNHEALTHY for c in checks.values()):
            status = HealthStatus.UNHEALTHY
            unhealthy = [
                name for name, c in checks.items() if c["status"] == HealthStatus.UNHEALTHY
            ]
            message = f"Unhealthy checks: {', '.join(unhealthy)}"
        else:
            status = HealthStatus.DEGRADED
            message = "Some checks degraded"

        return HealthStatus(
            status=status,
            timestamp=timestamp,
            checks=checks,
            message=message,
        )

    def get_agent_metrics(self, agent_id: str | None = None) -> list[AgentPerformanceMetrics]:
        """Get performance metrics for agents.

        Args:
            agent_id: Optional agent ID to filter by

        Returns:
            List of AgentPerformanceMetrics
        """
        with self._lock:
            if agent_id:
                metrics = self._agent_metrics.get(agent_id)
                return [metrics] if metrics else []
            return list(self._agent_metrics.values())

    def get_system_history(self, limit: int = 100) -> list[SystemMetrics]:
        """Get historical system metrics.

        Args:
            limit: Maximum number of records to return

        Returns:
            List of SystemMetrics snapshots
        """
        with self._lock:
            return self._system_history[-limit:]

    def get_prometheus_metrics(self) -> str:
        """Export all metrics in Prometheus format.

        Returns:
            Prometheus-formatted metrics string
        """
        return self._collector.to_prometheus()

    def get_agent_summary(self) -> dict[str, Any]:
        """Get summary of all agent metrics.

        Returns:
            Dictionary with agent summary statistics
        """
        with self._lock:
            agents = list(self._agent_metrics.values())

        if not agents:
            return {
                "total_agents": 0,
                "total_decisions": 0,
                "total_errors": 0,
                "avg_response_time": 0.0,
            }

        total_decisions = sum(a.decision_count for a in agents)
        total_errors = sum(a.error_count for a in agents)
        avg_response_time = (
            sum(a.avg_response_time for a in agents) / len(agents)
            if agents
            else 0.0
        )

        return {
            "total_agents": len(agents),
            "total_decisions": total_decisions,
            "total_errors": total_errors,
            "avg_response_time": round(avg_response_time, 4),
            "agents": [a.to_dict() for a in agents],
        }

    def unregister_agent(self, agent_id: str) -> None:
        """Unregister an agent and remove its metrics.

        Args:
            agent_id: Unique identifier for the agent
        """
        with self._lock:
            self._agent_metrics.pop(agent_id, None)

    def reset_agent_metrics(self, agent_id: str | None = None) -> None:
        """Reset metrics for an agent or all agents.

        Args:
            agent_id: Optional agent ID to reset. If None, resets all agents.
        """
        with self._lock:
            if agent_id:
                if agent_id in self._agent_metrics:
                    self._agent_metrics[agent_id] = AgentPerformanceMetrics(
                        agent_id=agent_id
                    )
            else:
                self._agent_metrics.clear()
                self._total_requests = 0
                self._total_errors = 0

    @property
    def thresholds(self) -> AlertThresholds:
        """Return current alert thresholds."""
        return self._thresholds

    @thresholds.setter
    def thresholds(self, thresholds: AlertThresholds) -> None:
        """Update alert thresholds."""
        self._thresholds = thresholds

    @property
    def is_running(self) -> bool:
        """Return True if background sampling is active."""
        return (
            self._sampling_thread is not None and self._sampling_thread.is_alive()
        )
