"""Unit tests for ChecksumRegistry (Phase 09: Verification Registry).

Tests cover: add, query, persistence, dedup, summary, and edge cases.
All persistence tests use tmp_path fixture -- never write to real cache dir.
"""

from __future__ import annotations

import json
from pathlib import Path

from opendeviationbar.checksum_registry import ChecksumRegistry, VerificationRecord


class TestVerificationRecord:
    """Test 11: VerificationRecord data model."""

    def test_stores_all_fields(self) -> None:
        """VerificationRecord stores symbol, date, sha256, verified_at, source."""
        rec = VerificationRecord(
            symbol="BTCUSDT",
            date="2024-01-15",
            sha256="abc123",
            verified_at="2026-03-26T05:00:00+00:00",
            source="seeder",
        )
        assert rec.symbol == "BTCUSDT"
        assert rec.date == "2024-01-15"
        assert rec.sha256 == "abc123"
        assert rec.verified_at == "2026-03-26T05:00:00+00:00"
        assert rec.source == "seeder"

    def test_round_trip_dict(self) -> None:
        """to_dict() and from_dict() round-trip correctly."""
        rec = VerificationRecord(
            symbol="ETHUSDT",
            date="2024-02-20",
            sha256="def456",
            verified_at="2026-03-26T06:00:00+00:00",
            source="sweep",
        )
        d = rec.to_dict()
        restored = VerificationRecord.from_dict(d)
        assert restored.symbol == rec.symbol
        assert restored.date == rec.date
        assert restored.sha256 == rec.sha256
        assert restored.verified_at == rec.verified_at
        assert restored.source == rec.source


class TestChecksumRegistryBasic:
    """Tests 1-3: Basic add, has_verified, query_by_symbol."""

    def test_add_and_get_records(self, tmp_path: Path) -> None:
        """Test 1: add() stores a record and query_by_symbol() returns it."""
        reg = ChecksumRegistry(path=tmp_path / "reg.json")
        reg.add("BTCUSDT", "2024-01-15", "abc123", "seeder")
        records = reg.query_by_symbol("BTCUSDT")
        assert len(records) == 1
        assert records[0].symbol == "BTCUSDT"
        assert records[0].date == "2024-01-15"
        assert records[0].sha256 == "abc123"
        assert records[0].source == "seeder"

    def test_has_verified(self, tmp_path: Path) -> None:
        """Test 2: has_verified() returns True after add(), False before."""
        reg = ChecksumRegistry(path=tmp_path / "reg.json")
        assert reg.has_verified("BTCUSDT", "2024-01-15") is False
        reg.add("BTCUSDT", "2024-01-15", "abc123", "seeder")
        assert reg.has_verified("BTCUSDT", "2024-01-15") is True

    def test_query_by_symbol_filters(self, tmp_path: Path) -> None:
        """Test 3: query_by_symbol() returns only records for that symbol."""
        reg = ChecksumRegistry(path=tmp_path / "reg.json")
        reg.add("BTCUSDT", "2024-01-15", "abc", "seeder")
        reg.add("ETHUSDT", "2024-01-15", "def", "seeder")
        reg.add("BTCUSDT", "2024-01-16", "ghi", "seeder")

        btc = reg.query_by_symbol("BTCUSDT")
        assert len(btc) == 2
        assert all(r.symbol == "BTCUSDT" for r in btc)

        eth = reg.query_by_symbol("ETHUSDT")
        assert len(eth) == 1
        assert eth[0].symbol == "ETHUSDT"


class TestChecksumRegistryDateRange:
    """Tests 4-5: query_by_date_range with and without symbol filter."""

    def test_query_by_date_range(self, tmp_path: Path) -> None:
        """Test 4: query_by_date_range() returns records within range (inclusive)."""
        reg = ChecksumRegistry(path=tmp_path / "reg.json")
        reg.add("BTCUSDT", "2024-01-10", "a", "seeder")
        reg.add("BTCUSDT", "2024-01-15", "b", "seeder")
        reg.add("BTCUSDT", "2024-01-20", "c", "seeder")
        reg.add("BTCUSDT", "2024-01-25", "d", "seeder")

        results = reg.query_by_date_range("2024-01-15", "2024-01-20")
        dates = [r.date for r in results]
        assert "2024-01-15" in dates
        assert "2024-01-20" in dates
        assert "2024-01-10" not in dates
        assert "2024-01-25" not in dates

    def test_query_by_date_range_with_symbol(self, tmp_path: Path) -> None:
        """Test 5: query_by_date_range with symbol filter narrows results."""
        reg = ChecksumRegistry(path=tmp_path / "reg.json")
        reg.add("BTCUSDT", "2024-01-15", "a", "seeder")
        reg.add("ETHUSDT", "2024-01-15", "b", "seeder")
        reg.add("BTCUSDT", "2024-01-16", "c", "seeder")
        reg.add("ETHUSDT", "2024-01-16", "d", "seeder")

        results = reg.query_by_date_range("2024-01-15", "2024-01-16", symbol="BTCUSDT")
        assert len(results) == 2
        assert all(r.symbol == "BTCUSDT" for r in results)


class TestChecksumRegistryUpsert:
    """Test 6: Duplicate add updates existing record."""

    def test_upsert_on_duplicate(self, tmp_path: Path) -> None:
        """Test 6: Duplicate add (same symbol+date) updates, not duplicates."""
        reg = ChecksumRegistry(path=tmp_path / "reg.json")
        reg.add("BTCUSDT", "2024-01-15", "old_hash", "seeder")
        reg.add("BTCUSDT", "2024-01-15", "new_hash", "sweep")

        assert len(reg) == 1
        records = reg.query_by_symbol("BTCUSDT")
        assert len(records) == 1
        assert records[0].sha256 == "new_hash"
        assert records[0].source == "sweep"


class TestChecksumRegistryPersistence:
    """Tests 7-9: save/load persistence."""

    def test_save_and_load(self, tmp_path: Path) -> None:
        """Test 7: save() writes JSON to disk, load() restores identical state."""
        path = tmp_path / "reg.json"
        reg = ChecksumRegistry(path=path)
        reg.add("BTCUSDT", "2024-01-15", "abc123", "seeder")
        reg.save()

        # File must exist on disk
        assert path.exists()

        # Load into new registry
        reg2 = ChecksumRegistry(path=path)
        assert len(reg2) == 1
        assert reg2.has_verified("BTCUSDT", "2024-01-15")

    def test_load_missing_file(self, tmp_path: Path) -> None:
        """Test 8: load() on missing file returns empty registry (no crash)."""
        reg = ChecksumRegistry(path=tmp_path / "nonexistent.json")
        assert len(reg) == 0

    def test_round_trip_multiple(self, tmp_path: Path) -> None:
        """Test 9: Registry round-trips through save/load with multiple symbols."""
        path = tmp_path / "reg.json"
        reg = ChecksumRegistry(path=path)
        reg.add("BTCUSDT", "2024-01-15", "abc", "seeder")
        reg.add("ETHUSDT", "2024-02-20", "def", "sweep")
        reg.add("BTCUSDT", "2024-03-10", "ghi", "seeder")
        reg.save()

        reg2 = ChecksumRegistry(path=path)
        assert len(reg2) == 3
        assert reg2.has_verified("BTCUSDT", "2024-01-15")
        assert reg2.has_verified("ETHUSDT", "2024-02-20")
        assert reg2.has_verified("BTCUSDT", "2024-03-10")

        btc = reg2.query_by_symbol("BTCUSDT")
        assert len(btc) == 2


class TestChecksumRegistrySummary:
    """Test 10: summary() returns aggregated stats."""

    def test_summary(self, tmp_path: Path) -> None:
        """Test 10: summary() returns total, per-symbol counts, latest_verified_at."""
        reg = ChecksumRegistry(path=tmp_path / "reg.json")
        reg.add("BTCUSDT", "2024-01-15", "a", "seeder")
        reg.add("BTCUSDT", "2024-01-16", "b", "seeder")
        reg.add("ETHUSDT", "2024-01-15", "c", "sweep")

        s = reg.summary()
        assert s["total_verified"] == 3
        assert s["by_symbol"]["BTCUSDT"] == 2
        assert s["by_symbol"]["ETHUSDT"] == 1
        assert s["latest_verified_at"] is not None

    def test_summary_empty(self, tmp_path: Path) -> None:
        """Empty registry summary has zero counts and None latest."""
        reg = ChecksumRegistry(path=tmp_path / "reg.json")
        s = reg.summary()
        assert s["total_verified"] == 0
        assert s["by_symbol"] == {}
        assert s["latest_verified_at"] is None


class TestChecksumRegistryEdgeCases:
    """Edge case tests."""

    def test_load_invalid_json(self, tmp_path: Path) -> None:
        """Invalid JSON file results in empty registry (log warning, no crash)."""
        path = tmp_path / "reg.json"
        path.write_text("this is not json{{{")
        reg = ChecksumRegistry(path=path)
        assert len(reg) == 0

    def test_len(self, tmp_path: Path) -> None:
        """__len__ returns number of records."""
        reg = ChecksumRegistry(path=tmp_path / "reg.json")
        assert len(reg) == 0
        reg.add("BTCUSDT", "2024-01-15", "abc", "seeder")
        assert len(reg) == 1

    def test_save_creates_parent_dirs(self, tmp_path: Path) -> None:
        """save() creates parent directories if they don't exist."""
        path = tmp_path / "sub" / "dir" / "reg.json"
        reg = ChecksumRegistry(path=path)
        reg.add("BTCUSDT", "2024-01-15", "abc", "seeder")
        reg.save()
        assert path.exists()

    def test_json_format_on_disk(self, tmp_path: Path) -> None:
        """JSON on disk has version and records keys."""
        path = tmp_path / "reg.json"
        reg = ChecksumRegistry(path=path)
        reg.add("BTCUSDT", "2024-01-15", "abc123", "seeder")
        reg.save()

        data = json.loads(path.read_text())
        assert data["version"] == 1
        assert "BTCUSDT:2024-01-15" in data["records"]
        rec = data["records"]["BTCUSDT:2024-01-15"]
        assert rec["symbol"] == "BTCUSDT"
        assert rec["sha256"] == "abc123"
