"""Tests for llmhosts.router.latency -- latency tracker and percentile math."""

from __future__ import annotations

import pytest

from llmhosts.router.latency import LatencyStats, LatencyTracker, _percentile

# ---------------------------------------------------------------------------
# LatencyStats model
# ---------------------------------------------------------------------------


class TestLatencyStats:
    def test_model_creation(self) -> None:
        s = LatencyStats(
            backend_type="ollama",
            sample_count=10,
            avg_ms=45.0,
            p50_ms=42.0,
            p95_ms=78.0,
            p99_ms=95.0,
            min_ms=20.0,
            max_ms=100.0,
            trend="stable",
        )
        assert s.backend_type == "ollama"
        assert s.trend == "stable"


# ---------------------------------------------------------------------------
# _percentile helper
# ---------------------------------------------------------------------------


class TestPercentile:
    def test_empty_list(self) -> None:
        assert _percentile([], 50) == 0.0

    def test_single_element(self) -> None:
        assert _percentile([42.0], 50) == 42.0
        assert _percentile([42.0], 99) == 42.0

    def test_two_elements_median(self) -> None:
        result = _percentile([10.0, 20.0], 50)
        assert result == pytest.approx(15.0)

    def test_p0_returns_min(self) -> None:
        result = _percentile([1.0, 2.0, 3.0, 4.0, 5.0], 0)
        assert result == pytest.approx(1.0)

    def test_p100_returns_max(self) -> None:
        result = _percentile([1.0, 2.0, 3.0, 4.0, 5.0], 100)
        assert result == pytest.approx(5.0)

    def test_p50_of_even_list(self) -> None:
        result = _percentile([1.0, 2.0, 3.0, 4.0], 50)
        assert result == pytest.approx(2.5)

    def test_p95_interpolation(self) -> None:
        data = list(range(1, 101))  # 1.0 .. 100.0
        data_f = [float(x) for x in data]
        result = _percentile(data_f, 95)
        assert result > 94.0
        assert result <= 96.0


# ---------------------------------------------------------------------------
# LatencyTracker
# ---------------------------------------------------------------------------


class TestLatencyTracker:
    def test_init_default_window(self) -> None:
        tracker = LatencyTracker()
        assert tracker.tracked_backends == []

    def test_init_custom_window(self) -> None:
        tracker = LatencyTracker(window_size=10)
        assert repr(tracker) == "LatencyTracker(window=10, backends=0)"

    def test_invalid_window_size(self) -> None:
        with pytest.raises(ValueError, match="window_size must be >= 1"):
            LatencyTracker(window_size=0)

    def test_record_and_get_stats(self) -> None:
        tracker = LatencyTracker()
        tracker.record("ollama", 45.0)
        tracker.record("ollama", 50.0)
        tracker.record("ollama", 55.0)

        stats = tracker.get_stats("ollama")
        assert stats is not None
        assert stats.sample_count == 3
        assert stats.avg_ms == pytest.approx(50.0)
        assert stats.min_ms == 45.0
        assert stats.max_ms == 55.0

    def test_get_stats_no_samples(self) -> None:
        tracker = LatencyTracker()
        assert tracker.get_stats("unknown") is None

    def test_negative_latency_ignored(self) -> None:
        tracker = LatencyTracker()
        tracker.record("ollama", -5.0)
        assert tracker.get_stats("ollama") is None

    def test_window_eviction(self) -> None:
        tracker = LatencyTracker(window_size=3)
        tracker.record("ollama", 10.0)
        tracker.record("ollama", 20.0)
        tracker.record("ollama", 30.0)
        tracker.record("ollama", 100.0)  # pushes out 10.0

        stats = tracker.get_stats("ollama")
        assert stats is not None
        assert stats.sample_count == 3
        assert stats.min_ms == 20.0

    def test_get_all_stats(self) -> None:
        tracker = LatencyTracker()
        tracker.record("ollama", 45.0)
        tracker.record("openai", 200.0)

        all_stats = tracker.get_all_stats()
        assert "ollama" in all_stats
        assert "openai" in all_stats
        assert all_stats["ollama"].avg_ms == pytest.approx(45.0)

    def test_get_fastest(self) -> None:
        tracker = LatencyTracker()
        tracker.record("ollama", 45.0)
        tracker.record("openai", 200.0)
        tracker.record("openrouter", 150.0)

        fastest = tracker.get_fastest(["ollama", "openai", "openrouter"])
        assert fastest == "ollama"

    def test_get_fastest_no_data(self) -> None:
        tracker = LatencyTracker()
        assert tracker.get_fastest(["ollama", "openai"]) is None

    def test_get_fastest_partial_data(self) -> None:
        tracker = LatencyTracker()
        tracker.record("openai", 200.0)
        fastest = tracker.get_fastest(["ollama", "openai"])
        assert fastest == "openai"

    def test_should_prefer_for_interactive_fast(self) -> None:
        tracker = LatencyTracker()
        for _ in range(20):
            tracker.record("ollama", 45.0)
        assert tracker.should_prefer_for_interactive("ollama", threshold_ms=500.0)

    def test_should_prefer_for_interactive_slow(self) -> None:
        tracker = LatencyTracker()
        for _ in range(20):
            tracker.record("openai", 800.0)
        assert not tracker.should_prefer_for_interactive("openai", threshold_ms=500.0)

    def test_should_prefer_for_interactive_no_data(self) -> None:
        tracker = LatencyTracker()
        assert not tracker.should_prefer_for_interactive("unknown")

    def test_trend_stable(self) -> None:
        tracker = LatencyTracker()
        for _ in range(30):
            tracker.record("ollama", 50.0)
        stats = tracker.get_stats("ollama")
        assert stats is not None
        assert stats.trend == "stable"

    def test_trend_degrading(self) -> None:
        tracker = LatencyTracker()
        # Start fast, end slow
        for i in range(30):
            latency = 50.0 + (i * 5)  # 50 -> 195
            tracker.record("ollama", latency)
        stats = tracker.get_stats("ollama")
        assert stats is not None
        assert stats.trend == "degrading"

    def test_trend_improving(self) -> None:
        tracker = LatencyTracker()
        # Start slow, end fast
        for i in range(30):
            latency = 200.0 - (i * 5)  # 200 -> 55
            tracker.record("ollama", latency)
        stats = tracker.get_stats("ollama")
        assert stats is not None
        assert stats.trend == "improving"

    def test_trend_insufficient_samples(self) -> None:
        tracker = LatencyTracker()
        for _ in range(5):
            tracker.record("ollama", 50.0)
        stats = tracker.get_stats("ollama")
        assert stats is not None
        assert stats.trend == "stable"

    def test_reset_specific_backend(self) -> None:
        tracker = LatencyTracker()
        tracker.record("ollama", 45.0)
        tracker.record("openai", 200.0)
        tracker.reset("ollama")
        assert tracker.get_stats("ollama") is None
        assert tracker.get_stats("openai") is not None

    def test_reset_all(self) -> None:
        tracker = LatencyTracker()
        tracker.record("ollama", 45.0)
        tracker.record("openai", 200.0)
        tracker.reset()
        assert tracker.tracked_backends == []

    def test_tracked_backends(self) -> None:
        tracker = LatencyTracker()
        tracker.record("ollama", 45.0)
        tracker.record("openai", 200.0)
        assert set(tracker.tracked_backends) == {"ollama", "openai"}

    def test_repr(self) -> None:
        tracker = LatencyTracker(window_size=50)
        tracker.record("ollama", 45.0)
        r = repr(tracker)
        assert "window=50" in r
        assert "backends=1" in r
