"""Tests for WeightRegistry."""

from __future__ import annotations

import datetime
from pathlib import Path  # noqa: TC003 — needed at runtime for pytest fixtures

import pytest

from llmhosts.training.registry import WeightManifest, WeightRegistry, _make_version
from llmhosts.training.trainer import TrainingResult


def _make_result(tmp_path: Path, n_samples: int = 1000, accuracy: float = 0.85) -> TrainingResult:
    """Helper: create a minimal TrainingResult pointing at tmp_path."""
    adapter_dir = tmp_path / "adapter"
    adapter_dir.mkdir(parents=True, exist_ok=True)
    # Create a dummy file so the path is not empty
    (adapter_dir / "adapter_model.bin").write_bytes(b"fake_weights" * 100)
    return TrainingResult(
        weights_path=adapter_dir,
        accuracy=accuracy,
        latency_ms=22.5,
        n_samples=n_samples,
        epochs_completed=3,
        delta_size_mb=0.0,  # actual files are small in tests
    )


class TestWeightRegistry:
    def test_register_creates_manifest_file(self, tmp_path: Path) -> None:
        """register() should create the manifest JSON file."""
        registry = WeightRegistry(registry_dir=tmp_path)
        result = _make_result(tmp_path)
        registry.register(result)
        manifest_file = tmp_path / "manifest.json"
        assert manifest_file.exists(), "manifest.json was not created"

    def test_register_returns_weight_manifest(self, tmp_path: Path) -> None:
        """register() should return a WeightManifest instance."""
        registry = WeightRegistry(registry_dir=tmp_path)
        result = _make_result(tmp_path)
        manifest = registry.register(result)
        assert isinstance(manifest, WeightManifest)

    def test_register_stores_accuracy(self, tmp_path: Path) -> None:
        """Registered manifest should preserve the accuracy from TrainingResult."""
        registry = WeightRegistry(registry_dir=tmp_path)
        result = _make_result(tmp_path, accuracy=0.91)
        manifest = registry.register(result)
        assert manifest.accuracy == pytest.approx(0.91)

    def test_list_versions_empty_when_no_manifest(self, tmp_path: Path) -> None:
        """list_versions() should return [] when no manifest file exists."""
        registry = WeightRegistry(registry_dir=tmp_path)
        assert registry.list_versions() == []

    def test_list_versions_returns_all_registered(self, tmp_path: Path) -> None:
        """list_versions() should return all registered entries."""
        registry = WeightRegistry(registry_dir=tmp_path)

        # Register three distinct versions
        for i, version in enumerate(["v20260101-5k", "v20260201-8k", "v20260224-10k"]):
            subdir = tmp_path / f"adapter_{i}"
            subdir.mkdir()
            (subdir / "weights.bin").write_bytes(b"x")
            result = TrainingResult(
                weights_path=subdir,
                accuracy=0.80 + i * 0.01,
                latency_ms=20.0,
                n_samples=(i + 1) * 1000,
                epochs_completed=3,
                delta_size_mb=1.0,
            )
            registry.register(result, version=version)

        versions = registry.list_versions()
        assert len(versions) == 3
        version_tags = {m.version for m in versions}
        assert version_tags == {"v20260101-5k", "v20260201-8k", "v20260224-10k"}

    def test_get_active_returns_none_initially(self, tmp_path: Path) -> None:
        """get_active() should return None when no version is active."""
        registry = WeightRegistry(registry_dir=tmp_path)
        assert registry.get_active() is None

    def test_get_active_returns_none_before_activation(self, tmp_path: Path) -> None:
        """Registering a version does not automatically activate it."""
        registry = WeightRegistry(registry_dir=tmp_path)
        result = _make_result(tmp_path)
        registry.register(result, version="v20260224-1k")
        assert registry.get_active() is None

    def test_activate_sets_correct_version_active(self, tmp_path: Path) -> None:
        """activate() should mark exactly the specified version as active."""
        registry = WeightRegistry(registry_dir=tmp_path)

        for i, version in enumerate(["v20260101-5k", "v20260224-10k"]):
            subdir = tmp_path / f"adapter_{i}"
            subdir.mkdir()
            (subdir / "w.bin").write_bytes(b"x")
            result = TrainingResult(
                weights_path=subdir,
                accuracy=0.80,
                latency_ms=20.0,
                n_samples=1000,
                epochs_completed=3,
                delta_size_mb=1.0,
            )
            registry.register(result, version=version)

        registry.activate("v20260224-10k")
        active = registry.get_active()
        assert active is not None
        assert active.version == "v20260224-10k"

    def test_activate_deactivates_previous(self, tmp_path: Path) -> None:
        """Activating a new version should deactivate the previously active one."""
        registry = WeightRegistry(registry_dir=tmp_path)

        for i, version in enumerate(["v20260101-5k", "v20260224-10k"]):
            subdir = tmp_path / f"adapter_{i}"
            subdir.mkdir()
            (subdir / "w.bin").write_bytes(b"x")
            result = TrainingResult(
                weights_path=subdir,
                accuracy=0.80,
                latency_ms=20.0,
                n_samples=1000,
                epochs_completed=3,
                delta_size_mb=1.0,
            )
            registry.register(result, version=version)

        registry.activate("v20260101-5k")
        registry.activate("v20260224-10k")

        versions = registry.list_versions()
        active_versions = [m for m in versions if m.active]
        assert len(active_versions) == 1
        assert active_versions[0].version == "v20260224-10k"

    def test_activate_unknown_version_raises(self, tmp_path: Path) -> None:
        """activate() with an unknown version should raise ValueError."""
        registry = WeightRegistry(registry_dir=tmp_path)
        with pytest.raises(ValueError, match="not found"):
            registry.activate("v99991231-999k")

    def test_manifest_persists_across_instances(self, tmp_path: Path) -> None:
        """Registrations should persist when a new registry instance is created."""
        registry_a = WeightRegistry(registry_dir=tmp_path)
        result = _make_result(tmp_path)
        registry_a.register(result, version="v20260224-1k")

        registry_b = WeightRegistry(registry_dir=tmp_path)
        versions = registry_b.list_versions()
        assert len(versions) == 1
        assert versions[0].version == "v20260224-1k"

    def test_make_version_format(self) -> None:
        """_make_version should produce correct format for known sample counts."""
        version = _make_version(10_000)
        date_str = datetime.date.today().strftime("%Y%m%d")
        assert version == f"v{date_str}-10k"

    def test_make_version_500_samples(self) -> None:
        """_make_version with 500 samples should produce Xk suffix of 0."""
        version = _make_version(500)
        assert version.endswith("-0k")

    def test_register_duplicate_version_replaces(self, tmp_path: Path) -> None:
        """Registering the same version twice should replace the earlier entry."""
        registry = WeightRegistry(registry_dir=tmp_path)

        def _register(accuracy: float) -> None:
            subdir = tmp_path / f"adapter_{int(accuracy * 100)}"
            subdir.mkdir(exist_ok=True)
            (subdir / "w.bin").write_bytes(b"x")
            result = TrainingResult(
                weights_path=subdir,
                accuracy=accuracy,
                latency_ms=20.0,
                n_samples=1000,
                epochs_completed=3,
                delta_size_mb=1.0,
            )
            registry.register(result, version="v20260224-1k")

        _register(0.80)
        _register(0.90)

        versions = registry.list_versions()
        assert len(versions) == 1
        assert versions[0].accuracy == pytest.approx(0.90)
