# platform/deskclaw/experiment/registry.py
# ═══════════════════════════════════════════════════════════════
# FileRegistry — Experiment artifact tracking for reproducibility
# ═══════════════════════════════════════════════════════════════

from __future__ import annotations

import hashlib
import json
import logging
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

from deskclaw.experiment.models import ExperimentError

logger = logging.getLogger("deskclaw.experiment.registry")


class RegistryError(ExperimentError):
    """FileRegistry operation failed."""


@dataclass(frozen=True)
class ArtifactRecord:
    path: str
    artifact_type: str
    checksum: str
    timestamp: str
    size_bytes: int = 0
    metadata: tuple[tuple[str, Any], ...] = ()

    def to_dict(self) -> dict[str, Any]:
        return {
            "path": self.path,
            "artifact_type": self.artifact_type,
            "checksum": self.checksum,
            "timestamp": self.timestamp,
            "size_bytes": self.size_bytes,
            "metadata": dict(self.metadata),
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> ArtifactRecord:
        meta = data.get("metadata", {})
        return cls(
            path=data["path"],
            artifact_type=data["artifact_type"],
            checksum=data.get("checksum", ""),
            timestamp=data.get("timestamp", ""),
            size_bytes=data.get("size_bytes", 0),
            metadata=tuple(sorted(meta.items())) if isinstance(meta, dict) else (),
        )


class FileRegistry:
    def __init__(self) -> None:
        self._artifacts: dict[str, ArtifactRecord] = {}

    def register(
        self,
        path: str | Path,
        artifact_type: str,
        metadata: dict[str, Any] | None = None,
    ) -> ArtifactRecord:
        path = Path(path)
        checksum = _compute_checksum(path) if path.is_file() else ""
        size = path.stat().st_size if path.exists() and path.is_file() else 0
        ts = datetime.now(UTC).isoformat()

        record = ArtifactRecord(
            path=str(path),
            artifact_type=artifact_type,
            checksum=checksum,
            timestamp=ts,
            size_bytes=size,
            metadata=tuple(sorted((metadata or {}).items())),
        )
        self._artifacts[str(path)] = record
        logger.info("Registered artifact: %s (%s)", path, artifact_type)
        return record

    def get(self, path: str | Path) -> ArtifactRecord | None:
        return self._artifacts.get(str(path))

    def list_artifacts(self, type_filter: str | None = None) -> list[ArtifactRecord]:
        records = list(self._artifacts.values())
        if type_filter:
            records = [r for r in records if r.artifact_type == type_filter]
        return sorted(records, key=lambda r: r.timestamp, reverse=True)

    def save(self, output_path: str | Path) -> Path:
        output_path = Path(output_path)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        data = {
            "artifacts": [r.to_dict() for r in self._artifacts.values()],
            "saved_at": datetime.now(UTC).isoformat(),
        }
        output_path.write_text(
            json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8"
        )
        logger.info("Registry saved to %s", output_path)
        return output_path

    def load(self, input_path: str | Path) -> int:
        input_path = Path(input_path)
        if not input_path.exists():
            raise RegistryError(f"Registry file not found: {input_path}")
        data = json.loads(input_path.read_text(encoding="utf-8"))
        artifacts = data.get("artifacts", [])
        for entry in artifacts:
            record = ArtifactRecord.from_dict(entry)
            self._artifacts[record.path] = record
        logger.info("Loaded %d artifact(s) from %s", len(artifacts), input_path)
        return len(artifacts)

    @property
    def count(self) -> int:
        return len(self._artifacts)


def _compute_checksum(path: Path) -> str:
    sha = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            sha.update(chunk)
    return sha.hexdigest()
