"""Tests for DataCurator."""

from __future__ import annotations

import sqlite3
from pathlib import Path  # noqa: TC003 — pytest resolves fixture types via name, needs runtime import

import pytest

from llmhosts.training.curator import DataCurator


@pytest.fixture()
def curator() -> DataCurator:
    return DataCurator()


@pytest.fixture()
def empty_db(tmp_path: Path) -> Path:
    """Return path to a freshly initialised (empty) telemetry DB."""
    db_path = tmp_path / "telemetry.db"
    with sqlite3.connect(db_path) as conn:
        conn.executescript(
            """
            CREATE TABLE routing_events (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                query_hash TEXT NOT NULL,
                tier_used TEXT NOT NULL,
                model TEXT NOT NULL,
                latency_ms REAL,
                input_tokens INTEGER,
                output_tokens INTEGER,
                cache_hit INTEGER,
                privacy_level TEXT,
                node_id TEXT,
                timestamp TEXT NOT NULL
            );
            """
        )
    return db_path


@pytest.fixture()
def populated_db(tmp_path: Path) -> Path:
    """Return path to a telemetry DB with 200 routing events."""
    db_path = tmp_path / "telemetry_populated.db"
    with sqlite3.connect(db_path) as conn:
        conn.executescript(
            """
            CREATE TABLE routing_events (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                query_hash TEXT NOT NULL,
                tier_used TEXT NOT NULL,
                model TEXT NOT NULL,
                latency_ms REAL,
                input_tokens INTEGER,
                output_tokens INTEGER,
                cache_hit INTEGER,
                privacy_level TEXT,
                node_id TEXT,
                timestamp TEXT NOT NULL
            );
            """
        )
        # Insert 200 rows with varied models and tiers
        for i in range(200):
            model = ["llama3.2:3b", "gpt-4o", "mistral:7b", "qwen2.5:7b"][i % 4]
            tier = ["knn", "modernbert", "rule_based", "local"][i % 4]
            privacy = ["external", "private", "personal"][i % 3]
            conn.execute(
                """
                INSERT INTO routing_events
                    (query_hash, tier_used, model, latency_ms, input_tokens,
                     output_tokens, cache_hit, privacy_level, node_id, timestamp)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (f"hash_{i:04d}", tier, model, 12.5, 100, 200, 0, privacy, "", "2026-01-01T00:00:00Z"),
            )
    return db_path


class TestCurateFromTelemetry:
    def test_empty_db_returns_empty_examples(self, curator: DataCurator, empty_db: Path) -> None:
        """Empty DB with min_samples=0 should return [] examples and stats."""
        examples, _stats = curator.curate_from_telemetry(empty_db, min_samples=0)
        assert examples == []

    def test_empty_db_below_min_samples_returns_none_stats(self, curator: DataCurator, empty_db: Path) -> None:
        """Empty DB (0 events) with min_samples=100 should return None stats."""
        examples, stats = curator.curate_from_telemetry(empty_db, min_samples=100)
        assert examples == []
        assert stats is None

    def test_missing_db_returns_empty(self, curator: DataCurator, tmp_path: Path) -> None:
        """Non-existent DB path should return ([], None) without raising."""
        missing_path = tmp_path / "does_not_exist.db"
        examples, stats = curator.curate_from_telemetry(missing_path)
        assert examples == []
        assert stats is None

    def test_populated_db_returns_stats(self, curator: DataCurator, populated_db: Path) -> None:
        """DB with 200 events (>= min_samples=100) should return non-None stats."""
        examples, stats = curator.curate_from_telemetry(populated_db, min_samples=100)
        assert examples == [], "Privacy constraint: examples must always be empty"
        assert stats is not None

    def test_populated_db_stats_total_events(self, curator: DataCurator, populated_db: Path) -> None:
        """Stats should report the correct total event count."""
        _, stats = curator.curate_from_telemetry(populated_db, min_samples=1)
        assert stats is not None
        assert stats.total_events == 200

    def test_populated_db_stats_model_counts(self, curator: DataCurator, populated_db: Path) -> None:
        """Stats should correctly count per-model routing decisions."""
        _, stats = curator.curate_from_telemetry(populated_db, min_samples=1)
        assert stats is not None
        # Each of the 4 models has 50 rows
        assert "llama3.2:3b" in stats.model_counts
        assert stats.model_counts["llama3.2:3b"] == 50

    def test_populated_db_stats_tier_counts(self, curator: DataCurator, populated_db: Path) -> None:
        """Stats should correctly count per-tier routing decisions."""
        _, stats = curator.curate_from_telemetry(populated_db, min_samples=1)
        assert stats is not None
        assert "knn" in stats.tier_counts
        assert stats.tier_counts["knn"] == 50

    def test_populated_db_stats_privacy_counts(self, curator: DataCurator, populated_db: Path) -> None:
        """Stats should correctly count per-privacy-level routing decisions."""
        _, stats = curator.curate_from_telemetry(populated_db, min_samples=1)
        assert stats is not None
        assert "external" in stats.privacy_counts

    def test_examples_never_contain_training_examples(self, curator: DataCurator, populated_db: Path) -> None:
        """The examples list must always be empty regardless of DB size."""
        examples, _ = curator.curate_from_telemetry(populated_db, min_samples=1)
        assert examples == [], "Privacy constraint violated: examples list must be empty"

    def test_examples_type_annotation(self, curator: DataCurator, populated_db: Path) -> None:
        """curate_from_telemetry must return a list (not None) for examples."""
        result = curator.curate_from_telemetry(populated_db)
        assert isinstance(result, tuple)
        examples, _stats = result
        assert isinstance(examples, list)

    def test_db_path_stored_in_stats(self, curator: DataCurator, populated_db: Path) -> None:
        """TelemetryStats.db_path should equal the path passed in."""
        _, stats = curator.curate_from_telemetry(populated_db, min_samples=1)
        assert stats is not None
        assert stats.db_path == populated_db
