"""Server-side ring buffer for system and per-target metrics."""

import asyncio
import os
from collections import deque
from datetime import datetime, timezone
from typing import Dict, Optional

import psutil

from wled_controller.utils import get_logger
from wled_controller.utils.gpu import (
    nvml_available as _nvml_available,
    nvml as _nvml,
    nvml_handle as _nvml_handle,
)

logger = get_logger(__name__)

MAX_SAMPLES = 120  # ~2 minutes at 1-second interval
SAMPLE_INTERVAL = 1.0  # seconds


_process = psutil.Process(os.getpid())
_process.cpu_percent(interval=None)  # prime process-level counter


def _collect_system_snapshot() -> dict:
    """Collect CPU/RAM/GPU metrics (blocking — run in thread pool).

    Returns a dict suitable for direct JSON serialization.
    """
    mem = psutil.virtual_memory()
    proc_mem = _process.memory_info()
    snapshot = {
        "t": datetime.now(timezone.utc).isoformat(),
        "cpu": psutil.cpu_percent(interval=None),
        "ram_pct": mem.percent,
        "ram_used": round(mem.used / 1024 / 1024, 1),
        "ram_total": round(mem.total / 1024 / 1024, 1),
        # Process.cpu_percent() is per-core (0–N*100%); normalize to 0–100%
        "app_cpu": _process.cpu_percent(interval=None) / (psutil.cpu_count(logical=True) or 1),
        "app_ram": round(proc_mem.rss / 1024 / 1024, 1),
        "gpu_util": None,
        "gpu_temp": None,
        "app_gpu_mem": None,
    }

    try:
        if _nvml_available:
            util = _nvml.nvmlDeviceGetUtilizationRates(_nvml_handle)
            temp = _nvml.nvmlDeviceGetTemperature(_nvml_handle, _nvml.NVML_TEMPERATURE_GPU)
            snapshot["gpu_util"] = float(util.gpu)
            snapshot["gpu_temp"] = float(temp)
            try:
                pid = os.getpid()
                for proc_info in _nvml.nvmlDeviceGetComputeRunningProcesses(_nvml_handle):
                    if proc_info.pid == pid and proc_info.usedGpuMemory:
                        snapshot["app_gpu_mem"] = round(proc_info.usedGpuMemory / 1024 / 1024, 1)
                        break
            except Exception:
                pass
    except Exception as e:
        logger.debug("GPU metrics collection failed: %s", e)
        pass

    return snapshot


class MetricsHistory:
    """In-memory ring buffer collecting system and per-target metrics."""

    def __init__(self, processor_manager):
        self._manager = processor_manager
        self._system: deque = deque(maxlen=MAX_SAMPLES)
        self._targets: Dict[str, deque] = {}
        self._task: Optional[asyncio.Task] = None

    async def start(self):
        """Start the background sampling loop."""
        if self._task and not self._task.done():
            return
        self._task = asyncio.create_task(self._sample_loop())
        logger.info("Metrics history sampling started")

    async def stop(self):
        """Stop the background sampling loop."""
        if self._task:
            self._task.cancel()
            try:
                await self._task
            except asyncio.CancelledError:
                logger.debug("Metrics history collection task cancelled")
                pass
            self._task = None
        logger.info("Metrics history sampling stopped")

    async def _sample_loop(self):
        """Sample system + target metrics every SAMPLE_INTERVAL seconds."""
        while True:
            try:
                await self._sample()
            except asyncio.CancelledError:
                raise
            except Exception as e:
                logger.warning(f"Metrics sampling error: {e}")
            await asyncio.sleep(SAMPLE_INTERVAL)

    async def _sample(self):
        """Collect one snapshot of system and target metrics."""
        # System metrics (blocking psutil/nvml calls in thread pool)
        sys_snap = await asyncio.to_thread(_collect_system_snapshot)
        self._system.append(sys_snap)

        # Per-target metrics from processor states
        try:
            all_states = self._manager.get_all_target_states()
        except Exception as e:
            logger.error("Failed to get target states: %s", e)
            all_states = {}

        now = datetime.now(timezone.utc).isoformat()
        active_ids = set()
        for target_id, state in all_states.items():
            active_ids.add(target_id)
            if target_id not in self._targets:
                self._targets[target_id] = deque(maxlen=MAX_SAMPLES)
            if state.get("processing"):
                self._targets[target_id].append(
                    {
                        "t": now,
                        "fps": state.get("fps_actual"),
                        "fps_current": state.get("fps_current"),
                        "fps_target": state.get("fps_target"),
                        "timing": state.get("timing_total_ms"),
                        "errors": state.get("errors_count", 0),
                    }
                )

        # Prune deques for targets no longer registered
        for tid in list(self._targets.keys()):
            if tid not in active_ids:
                del self._targets[tid]

    def get_history(self) -> dict:
        """Return all history for the API response."""
        return {
            "system": list(self._system),
            "targets": {tid: list(dq) for tid, dq in self._targets.items()},
        }
