"""Centralized checksum verification registry.

Tracks which Parquet cache files have been SHA-256 verified against their
Vision CHECKSUM files. Foundation layer for seeder (writes records), sweep
(reads to skip already-verified), and heartbeat (queries status).

Registry file: ``~/.cache/opendeviationbar/checksum-registry.json``
"""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path

from opendeviationbar.storage.parquet_io import get_cache_dir

logger = logging.getLogger(__name__)


@dataclass
class VerificationRecord:
    """A single file verification record."""

    symbol: str
    date: str  # ISO date YYYY-MM-DD
    sha256: str  # hex digest from Vision CHECKSUM
    verified_at: str  # ISO 8601 datetime with UTC timezone
    source: str  # "seeder" or "sweep"

    def to_dict(self) -> dict[str, str]:
        """Serialize to a plain dict for JSON storage."""
        return {
            "symbol": self.symbol,
            "date": self.date,
            "sha256": self.sha256,
            "verified_at": self.verified_at,
            "source": self.source,
        }

    @classmethod
    def from_dict(cls, d: dict[str, str]) -> VerificationRecord:
        """Deserialize from a plain dict."""
        return cls(
            symbol=d["symbol"],
            date=d["date"],
            sha256=d["sha256"],
            verified_at=d["verified_at"],
            source=d["source"],
        )


class ChecksumRegistry:
    """Per-file verification state store.

    Tracks which Parquet cache files have been SHA-256 verified against
    their Vision CHECKSUM files. Keyed by ``{symbol}:{date}`` to ensure
    upsert semantics on duplicate symbol+date pairs.

    Parameters
    ----------
    path
        Path to the JSON registry file. Defaults to
        ``~/.cache/opendeviationbar/checksum-registry.json``.
    """

    def __init__(self, path: Path | None = None) -> None:
        self._path = path or (get_cache_dir() / "checksum-registry.json")
        self._records: dict[str, VerificationRecord] = {}
        self.load()

    @staticmethod
    def _key(symbol: str, date: str) -> str:
        return f"{symbol}:{date}"

    def add(self, symbol: str, date: str, sha256: str, source: str) -> None:
        """Add or update a verification record.

        Does NOT auto-save -- callers batch writes then call :meth:`save`.
        """
        key = self._key(symbol, date)
        self._records[key] = VerificationRecord(
            symbol=symbol,
            date=date,
            sha256=sha256,
            verified_at=datetime.now(UTC).isoformat(),
            source=source,
        )

    def has_verified(self, symbol: str, date: str) -> bool:
        """Return True if a verification record exists for symbol+date."""
        return self._key(symbol, date) in self._records

    def query_by_symbol(self, symbol: str) -> list[VerificationRecord]:
        """Return all records for *symbol*, sorted by date."""
        return sorted(
            (r for r in self._records.values() if r.symbol == symbol),
            key=lambda r: r.date,
        )

    def query_by_date_range(
        self,
        start: str,
        end: str,
        symbol: str | None = None,
    ) -> list[VerificationRecord]:
        r"""Return records within *start*..\ *end* (inclusive).

        Optionally filter by *symbol*.
        """
        results = [
            r
            for r in self._records.values()
            if start <= r.date <= end and (symbol is None or r.symbol == symbol)
        ]
        return sorted(results, key=lambda r: (r.symbol, r.date))

    def summary(self) -> dict:
        """Aggregated stats: total, per-symbol counts, latest_verified_at."""
        by_symbol: dict[str, int] = {}
        latest: str | None = None
        for r in self._records.values():
            by_symbol[r.symbol] = by_symbol.get(r.symbol, 0) + 1
            if latest is None or r.verified_at > latest:
                latest = r.verified_at
        return {
            "total_verified": len(self._records),
            "by_symbol": by_symbol,
            "latest_verified_at": latest,
        }

    def save(self) -> None:
        """Atomic write: write to ``.tmp`` then ``os.replace()``."""
        self._path.parent.mkdir(parents=True, exist_ok=True)
        tmp_path = self._path.with_suffix(".json.tmp")
        data = {
            "version": 1,
            "records": {k: v.to_dict() for k, v in self._records.items()},
        }
        tmp_path.write_text(json.dumps(data, indent=2))
        tmp_path.replace(self._path)

    def load(self) -> None:
        """Load registry from disk. Missing file = empty. Invalid JSON = warning + empty."""
        if not self._path.exists():
            self._records = {}
            return
        try:
            data = json.loads(self._path.read_text())
            raw_records = data.get("records", {})
            self._records = {
                k: VerificationRecord.from_dict(v) for k, v in raw_records.items()
            }
        except (json.JSONDecodeError, KeyError, TypeError):
            logger.warning("Invalid registry file at %s, starting empty", self._path)
            self._records = {}

    def __len__(self) -> int:
        return len(self._records)
