"""Tests for TelemetryCollector."""

from __future__ import annotations

import time
from collections import deque
from unittest.mock import MagicMock, patch


class TestTelemetryCollector:
    """Test telemetry calculations in isolation (no Qt event loop required)."""

    def _make_collector(self, cpu_count: int = 8):
        """Construct a TelemetryCollector bypassing Qt machinery."""
        from app.telemetry.telemetry_collector import TelemetryCollector

        with patch.object(TelemetryCollector, "__init__", return_value=None):
            col = TelemetryCollector.__new__(TelemetryCollector)

        col._frame_times = deque(maxlen=120)
        col._last_frame_time = 0.0
        col._total_frames = 0
        col._dropped_frames = 0
        col._fps_window = deque()
        col._fps_window_size_s = 1.0
        col._target_fps = None
        col._cpu_count = cpu_count

        col._process = MagicMock()
        # Simulate Windows: wset takes priority over rss
        mem_info = MagicMock()
        mem_info.wset = 50 * 1024 * 1024   # 50 MB private working set
        mem_info.rss = 70 * 1024 * 1024    # RSS (larger, includes shared)
        col._process.memory_info.return_value = mem_info
        col._process.cpu_percent.return_value = 0.0
        # Inference stats — None by default (inference disabled)
        col._inference_device = None
        col._inference_time_ms = None
        return col

    def test_initial_snapshot_has_zero_fps(self):
        col = self._make_collector()
        snap = col._compute_snapshot()
        assert snap.fps == 0.0

    def test_fps_counts_frames_in_window(self):
        col = self._make_collector()
        now = time.perf_counter()
        for i in range(30):
            col._fps_window.append(now - 0.9 + i * 0.03)
        snap = col._compute_snapshot()
        assert snap.fps == 30.0

    def test_fps_excludes_old_frames(self):
        col = self._make_collector()
        now = time.perf_counter()
        # 10 frames older than 1 second — should not count
        for i in range(10):
            col._fps_window.append(now - 2.0 + i * 0.05)
        # 5 frames within the last second
        for i in range(5):
            col._fps_window.append(now - 0.4 + i * 0.05)
        snap = col._compute_snapshot()
        assert snap.fps == 5.0

    def test_frame_time_average(self):
        col = self._make_collector()
        interval = 0.0333
        for _ in range(10):
            col._frame_times.append(interval)
        snap = col._compute_snapshot()
        assert abs(snap.frame_time_ms - interval * 1000) < 0.1

    def test_drop_detection(self):
        col = self._make_collector()
        normal_interval = 0.016
        now = time.perf_counter()
        col._last_frame_time = now - normal_interval * 10
        for _ in range(9):
            col._last_frame_time += normal_interval
            col._frame_times.append(normal_interval)

        col._last_frame_time = now
        with patch("app.telemetry.telemetry_collector.time") as mock_time:
            col._last_frame_time = now
            big_delta = normal_interval * 5  # 5× average → drop
            mock_time.perf_counter.return_value = now + big_delta
            delta = big_delta
            col._frame_times.append(delta)
            avg = sum(col._frame_times) / len(col._frame_times)
            if delta > avg * 2.5:
                col._dropped_frames += 1

        assert col._dropped_frames == 1

    def test_reset_counters(self):
        col = self._make_collector()
        col._total_frames = 100
        col._dropped_frames = 5
        col._frame_times.append(0.016)
        col._fps_window.append(time.perf_counter())
        col.reset_counters()
        assert col._total_frames == 0
        assert col._dropped_frames == 0
        assert len(col._frame_times) == 0
        assert len(col._fps_window) == 0

    def test_snapshot_memory_mb(self):
        col = self._make_collector()
        snap = col._compute_snapshot()
        assert snap.memory_mb == 50.0

    def test_cpu_sys_is_core_divided_by_cpu_count(self):
        col = self._make_collector(cpu_count=8)
        col._process.cpu_percent.return_value = 80.0  # 80% of one core
        snap = col._compute_snapshot()
        assert snap.cpu_percent_core == 80.0
        assert snap.cpu_percent_sys == round(80.0 / 8, 1)

    def test_cpu_sys_never_exceeds_100_on_single_core_machine(self):
        col = self._make_collector(cpu_count=1)
        col._process.cpu_percent.return_value = 95.0
        snap = col._compute_snapshot()
        assert snap.cpu_percent_sys == snap.cpu_percent_core == 95.0

    def test_cpu_sys_le_cpu_core(self):
        """cpu_percent_sys must always be <= cpu_percent_core."""
        col = self._make_collector(cpu_count=4)
        col._process.cpu_percent.return_value = 150.0
        snap = col._compute_snapshot()
        assert snap.cpu_percent_sys <= snap.cpu_percent_core

    def test_target_fps_none_by_default(self):
        col = self._make_collector()
        snap = col._compute_snapshot()
        assert snap.target_fps is None

    def test_set_target_fps_reflected_in_snapshot(self):
        col = self._make_collector()
        col.set_target_fps(60.0)
        snap = col._compute_snapshot()
        assert snap.target_fps == 60.0
