"""Resource monitor -- tracks CPU, memory, and network usage of the LLMHosts process.

Provides users with visibility into exactly what resources LLMHosts is consuming.
"""

from __future__ import annotations

import logging
import os
import time
from dataclasses import dataclass, field
from typing import Any

logger = logging.getLogger(__name__)


@dataclass
class ResourceSnapshot:
    """Point-in-time resource usage snapshot."""

    timestamp: float = field(default_factory=time.time)
    cpu_percent: float = 0.0
    memory_mb: float = 0.0
    memory_percent: float = 0.0
    disk_read_bytes: int = 0
    disk_write_bytes: int = 0
    network_sent_bytes: int = 0
    network_recv_bytes: int = 0
    open_connections: int = 0
    active_requests: int = 0

    def to_dict(self) -> dict[str, Any]:
        """Serialize snapshot to a dictionary."""
        return {
            "timestamp": self.timestamp,
            "cpu_percent": round(self.cpu_percent, 1),
            "memory_mb": round(self.memory_mb, 1),
            "memory_percent": round(self.memory_percent, 1),
            "disk_read_bytes": self.disk_read_bytes,
            "disk_write_bytes": self.disk_write_bytes,
            "network_sent_bytes": self.network_sent_bytes,
            "network_recv_bytes": self.network_recv_bytes,
            "open_connections": self.open_connections,
            "active_requests": self.active_requests,
        }


class ResourceMonitor:
    """Monitors LLMHosts process resource usage."""

    def __init__(self) -> None:
        self._history: list[ResourceSnapshot] = []
        self._max_history: int = 1440  # 24 hours at 1-minute intervals
        self._active_requests: int = 0

    def snapshot(self) -> ResourceSnapshot:
        """Take a resource usage snapshot of the current process."""
        snap = ResourceSnapshot()

        try:
            # Use /proc/self for Linux, psutil-free approach
            snap.memory_mb = self._get_memory_mb()
            snap.cpu_percent = self._get_cpu_percent()
            snap.open_connections = self._get_open_connections()
        except Exception as exc:
            logger.debug("Resource snapshot failed: %s", exc)

        snap.active_requests = self._active_requests

        self._history.append(snap)
        if len(self._history) > self._max_history:
            self._history = self._history[-self._max_history :]

        return snap

    def increment_requests(self) -> None:
        """Track active request count."""
        self._active_requests += 1

    def decrement_requests(self) -> None:
        """Track active request count."""
        self._active_requests = max(0, self._active_requests - 1)

    def get_history(self, minutes: int = 60) -> list[dict[str, Any]]:
        """Get resource history for the last N minutes."""
        cutoff = time.time() - (minutes * 60)
        return [s.to_dict() for s in self._history if s.timestamp >= cutoff]

    def get_summary(self) -> dict[str, Any]:
        """Get summary of resource usage."""
        if not self._history:
            return {"message": "No data collected yet"}
        recent = self._history[-60:] if len(self._history) > 60 else self._history
        return {
            "avg_cpu_percent": round(sum(s.cpu_percent for s in recent) / len(recent), 1),
            "avg_memory_mb": round(sum(s.memory_mb for s in recent) / len(recent), 1),
            "peak_memory_mb": round(max(s.memory_mb for s in recent), 1),
            "total_snapshots": len(self._history),
            "active_requests": self._active_requests,
        }

    @staticmethod
    def _get_memory_mb() -> float:
        """Get current process memory usage in MB (Linux /proc/self/status, fallback to resource module)."""
        try:
            # Try /proc/self/status (Linux)
            status_path = "/proc/self/status"
            if os.path.exists(status_path):
                with open(status_path) as f:
                    for line in f:
                        if line.startswith("VmRSS:"):
                            # VmRSS is in kB
                            return float(line.split()[1]) / 1024.0
        except Exception:
            pass

        try:
            import resource

            # getrusage returns max RSS in KB on Linux, bytes on macOS
            usage = resource.getrusage(resource.RUSAGE_SELF)
            import platform

            if platform.system() == "Darwin":
                return usage.ru_maxrss / (1024 * 1024)  # bytes to MB
            return usage.ru_maxrss / 1024  # KB to MB
        except Exception:
            return 0.0

    @staticmethod
    def _get_cpu_percent() -> float:
        """Get CPU usage estimate. Returns 0.0 if unavailable."""
        try:
            # Read /proc/self/stat for process CPU time
            if os.path.exists("/proc/self/stat"):
                with open("/proc/self/stat") as f:
                    fields = f.read().split()
                    utime = int(fields[13])
                    stime = int(fields[14])
                    total = utime + stime
                    # This is cumulative, not percentage. Return raw for now.
                    # A proper implementation would track delta between snapshots.
                    return float(total) / os.sysconf("SC_CLK_TCK")
        except Exception:
            pass
        return 0.0

    @staticmethod
    def _get_open_connections() -> int:
        """Count open network connections for this process."""
        try:
            pid = os.getpid()
            fd_dir = f"/proc/{pid}/fd"
            if os.path.exists(fd_dir):
                count = 0
                for fd in os.listdir(fd_dir):
                    try:
                        link = os.readlink(f"{fd_dir}/{fd}")
                        if "socket:" in link:
                            count += 1
                    except (OSError, PermissionError):
                        pass
                return count
        except Exception:
            pass
        return 0
