"""anu_v3.callback_track_registry — callback_track_record registry (9-R.2).

Authority: task-2553+17.md §4(2) + §12 9-R.2.

Canonical structure
--------------------
``callback_track_record`` : registry keyed by ``track_id``; each record has
**5 fields**::

    {track_id, task_id, dispatch_cron_id,
     normal_collector_cron_id, fallback_callback_cron_id}

The chair term "callback 4-tuple" = the **callback identity 4 fields**
``{task_id, dispatch_cron_id, normal_collector_cron_id,
fallback_callback_cron_id}`` (track_id is the registry key, not part of the
identity tuple). schema / fixture / regression all align to this single
definition (cardinality identical).
"""

from __future__ import annotations

from dataclasses import dataclass, asdict
from typing import Dict, List, Tuple

RECORD_FIELDS: Tuple[str, ...] = (
    "track_id",
    "task_id",
    "dispatch_cron_id",
    "normal_collector_cron_id",
    "fallback_callback_cron_id",
)

# callback identity 4-tuple (track_id is the registry key)
IDENTITY_FIELDS: Tuple[str, ...] = (
    "task_id",
    "dispatch_cron_id",
    "normal_collector_cron_id",
    "fallback_callback_cron_id",
)


@dataclass(frozen=True)
class CallbackTrackRecord:
    track_id: str
    task_id: str
    dispatch_cron_id: str
    normal_collector_cron_id: str
    fallback_callback_cron_id: str

    def identity_tuple(self) -> Tuple[str, str, str, str]:
        return (
            self.task_id,
            self.dispatch_cron_id,
            self.normal_collector_cron_id,
            self.fallback_callback_cron_id,
        )

    def to_dict(self) -> Dict[str, str]:
        return asdict(self)


class CallbackTrackRegistry:
    """track_id-keyed registry of CallbackTrackRecord (5 fields each)."""

    def __init__(self) -> None:
        self._records: Dict[str, CallbackTrackRecord] = {}

    def register(self, record: CallbackTrackRecord) -> None:
        if record.track_id in self._records:
            raise ValueError(f"duplicate track_id {record.track_id!r}")
        missing = [f for f in RECORD_FIELDS if not getattr(record, f)]
        if missing:
            raise ValueError(f"record {record.track_id!r} missing {missing}")
        self._records[record.track_id] = record

    def register_dict(self, d: Dict[str, str]) -> None:
        unknown = set(d) - set(RECORD_FIELDS)
        if unknown:
            raise ValueError(f"unknown record fields {sorted(unknown)}")
        self.register(CallbackTrackRecord(**{f: d[f] for f in RECORD_FIELDS}))

    def get(self, track_id: str) -> CallbackTrackRecord:
        return self._records[track_id]

    def track_ids(self) -> List[str]:
        return list(self._records)

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

    def __contains__(self, track_id: str) -> bool:
        return track_id in self._records

    # -- 4-tuple validation (9-R.6 TRACK_MISMATCH) ----------------------
    def validate_callback(
        self, track_id: str, observed_identity: Dict[str, str]
    ) -> Tuple[bool, str]:
        """Return (ok, classification).

        Compares an observed callback identity 4-tuple against the
        registered record. Any cron_id / task_id mismatch -> TRACK_MISMATCH.
        """
        if track_id not in self._records:
            return False, "TRACK_MISMATCH"  # unregistered track
        rec = self._records[track_id]
        for f in IDENTITY_FIELDS:
            if observed_identity.get(f) != getattr(rec, f):
                return False, "TRACK_MISMATCH"
        return True, "IDENTITY_MATCH"

    def to_dict(self) -> Dict[str, Dict[str, str]]:
        return {tid: r.to_dict() for tid, r in self._records.items()}
