"""
Resource Monitoring for groknroll.

Tracks system resource usage (memory, CPU, file handles, subprocesses) and
detects potential leaks.

Example:
    monitor = ResourceMonitor()

    # Start monitoring
    await monitor.start()

    # Check current usage
    usage = monitor.get_usage()
    print(f"Memory: {usage['memory_mb']}MB")

    # Detect leaks
    if monitor.has_leak():
        print("Warning: Possible resource leak detected!")

    # Stop monitoring
    await monitor.stop()
"""

import asyncio
import os
import time
from dataclasses import dataclass
from typing import Optional

import psutil


@dataclass
class ResourceUsage:
    """Snapshot of resource usage."""

    timestamp: float
    memory_mb: float
    cpu_percent: float
    open_files: int
    num_threads: int
    num_children: int


class ResourceMonitor:
    """
    Monitor system resource usage and detect leaks.

    Tracks:
    - Memory usage
    - CPU usage
    - Open file handles
    - Thread count
    - Child process count
    """

    def __init__(
        self,
        check_interval: float = 60.0,
        history_size: int = 100,
        leak_threshold: float = 1.5,  # 50% increase = potential leak
    ):
        """
        Initialize resource monitor.

        Args:
            check_interval: Seconds between resource checks
            history_size: Number of historical snapshots to keep
            leak_threshold: Multiplier for leak detection (e.g., 1.5 = 50% increase)
        """
        self.check_interval = check_interval
        self.history_size = history_size
        self.leak_threshold = leak_threshold

        self.process = psutil.Process()
        self.history: list[ResourceUsage] = []
        self._task: Optional[asyncio.Task] = None
        self._running = False

    async def start(self):
        """Start monitoring."""
        if self._running:
            return

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

    async def stop(self):
        """Stop monitoring."""
        self._running = False
        if self._task:
            self._task.cancel()
            try:
                await self._task
            except asyncio.CancelledError:
                pass

    async def _monitor_loop(self):
        """Background monitoring loop."""
        while self._running:
            try:
                # Collect resource snapshot
                snapshot = self._collect_snapshot()
                self.history.append(snapshot)

                # Keep history bounded
                if len(self.history) > self.history_size:
                    self.history.pop(0)

                await asyncio.sleep(self.check_interval)

            except asyncio.CancelledError:
                break
            except Exception:
                # Don't crash on monitoring errors
                pass

    def _collect_snapshot(self) -> ResourceUsage:
        """Collect current resource usage."""
        try:
            memory_info = self.process.memory_info()
            memory_mb = memory_info.rss / (1024 * 1024)  # Convert to MB

            # CPU percent (non-blocking)
            cpu_percent = self.process.cpu_percent(interval=None)

            # Open file handles
            try:
                open_files = len(self.process.open_files())
            except (psutil.AccessDenied, OSError):
                open_files = 0

            # Thread count
            num_threads = self.process.num_threads()

            # Child processes
            num_children = len(self.process.children(recursive=True))

            return ResourceUsage(
                timestamp=time.time(),
                memory_mb=memory_mb,
                cpu_percent=cpu_percent,
                open_files=open_files,
                num_threads=num_threads,
                num_children=num_children,
            )

        except Exception:
            # Return zero values on error
            return ResourceUsage(
                timestamp=time.time(),
                memory_mb=0.0,
                cpu_percent=0.0,
                open_files=0,
                num_threads=0,
                num_children=0,
            )

    def get_usage(self) -> dict[str, float]:
        """
        Get current resource usage.

        Returns:
            Dictionary with current usage metrics
        """
        snapshot = self._collect_snapshot()
        return {
            "memory_mb": snapshot.memory_mb,
            "cpu_percent": snapshot.cpu_percent,
            "open_files": snapshot.open_files,
            "num_threads": snapshot.num_threads,
            "num_children": snapshot.num_children,
        }

    def has_leak(self, metric: str = "memory_mb") -> bool:
        """
        Check if there's a potential resource leak.

        Compares recent average to baseline (first measurements).

        Args:
            metric: Resource metric to check (memory_mb, open_files, etc.)

        Returns:
            True if potential leak detected
        """
        if len(self.history) < 10:
            # Not enough data
            return False

        # Baseline: average of first 10 measurements
        baseline_snapshots = self.history[:10]
        baseline_values = [getattr(s, metric) for s in baseline_snapshots]
        baseline_avg = sum(baseline_values) / len(baseline_values)

        if baseline_avg == 0:
            return False

        # Recent: average of last 10 measurements
        recent_snapshots = self.history[-10:]
        recent_values = [getattr(s, metric) for s in recent_snapshots]
        recent_avg = sum(recent_values) / len(recent_values)

        # Check if recent average exceeds threshold
        return recent_avg > baseline_avg * self.leak_threshold

    def get_trend(self, metric: str = "memory_mb", window: int = 10) -> str:
        """
        Get trend direction for a metric.

        Args:
            metric: Resource metric to analyze
            window: Number of recent snapshots to analyze

        Returns:
            "increasing", "decreasing", or "stable"
        """
        if len(self.history) < window:
            return "unknown"

        values = [getattr(s, metric) for s in self.history[-window:]]

        # Simple linear regression slope
        n = len(values)
        x = list(range(n))
        mean_x = sum(x) / n
        mean_y = sum(values) / n

        numerator = sum((x[i] - mean_x) * (values[i] - mean_y) for i in range(n))
        denominator = sum((x[i] - mean_x) ** 2 for i in range(n))

        if denominator == 0:
            return "stable"

        slope = numerator / denominator

        # Determine trend based on slope
        if abs(slope) < 0.1:  # Threshold for "stable"
            return "stable"
        elif slope > 0:
            return "increasing"
        else:
            return "decreasing"

    def get_stats(self) -> dict[str, any]:
        """Get monitoring statistics."""
        if not self.history:
            return {
                "snapshots": 0,
                "monitoring_duration": 0,
                "current": {},
            }

        current = self.get_usage()
        duration = self.history[-1].timestamp - self.history[0].timestamp

        return {
            "snapshots": len(self.history),
            "monitoring_duration": duration,
            "current": current,
            "memory_trend": self.get_trend("memory_mb"),
            "file_handles_trend": self.get_trend("open_files"),
            "has_memory_leak": self.has_leak("memory_mb"),
            "has_file_leak": self.has_leak("open_files"),
        }


# Global monitor instance
_resource_monitor: Optional[ResourceMonitor] = None


def get_resource_monitor() -> ResourceMonitor:
    """Get or create global resource monitor."""
    global _resource_monitor
    if _resource_monitor is None:
        _resource_monitor = ResourceMonitor()
    return _resource_monitor
