"""Regression Store — manages storage and deduplication of regression eval cases."""
from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from abi_evals.failure_distiller import RegressionEvalCase


class RegressionStore:
    """Manages storage of regression eval cases with deduplication."""

    def __init__(self, store_dir: str | Path = "evals/regression") -> None:
        """Initialize the regression store.

        Args:
            store_dir: Directory to store regression eval cases (default: evals/regression)
        """
        self.store_dir = Path(store_dir)
        self.index_path = self.store_dir / "index.json"
        self.cases_dir = self.store_dir / "cases"

        # Load existing index
        self.index: dict[str, dict[str, Any]] = {}
        if self.index_path.exists():
            self._load_index()

    def _load_index(self) -> None:
        """Load the index from disk."""
        with open(self.index_path, encoding="utf-8") as f:
            data = json.load(f)
            self.index = data.get("cases", {})

    def _save_index(self) -> None:
        """Save the index to disk."""
        self.index_path.parent.mkdir(parents=True, exist_ok=True)

        data = {
            "version": "1.0",
            "total_cases": len(self.index),
            "cases": self.index,
        }

        with open(self.index_path, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False, sort_keys=True)

    def _compute_signature(self, eval_case: RegressionEvalCase) -> str:
        """Compute a signature for deduplication.

        The signature is based on the failure class, check type, and target files.
        This allows us to detect duplicate eval cases.

        Args:
            eval_case: The eval case to compute a signature for

        Returns:
            A signature string
        """
        # Sort target files for deterministic signature
        sorted_files = sorted(eval_case.target_files)
        files_str = "|".join(sorted_files)

        # Extract failure class from source_failure_id or name
        failure_class = eval_case.source_failure_id.split("_")[0] if "_" in eval_case.source_failure_id else "unknown"

        return f"{failure_class}:{eval_case.check_type}:{files_str}"

    def is_duplicate(self, eval_case: RegressionEvalCase) -> bool:
        """Check if an eval case is a duplicate.

        Args:
            eval_case: The eval case to check

        Returns:
            True if the eval case is a duplicate, False otherwise
        """
        signature = self._compute_signature(eval_case)

        for existing_case in self.index.values():
            # Check if signature matches
            existing_signature = existing_case.get("_signature", "")
            if existing_signature == signature:
                return True

        return False

    def add(self, eval_case: RegressionEvalCase, force: bool = False) -> bool:
        """Add a regression eval case to the store.

        Args:
            eval_case: The eval case to add
            force: If True, add even if it's a duplicate

        Returns:
            True if the case was added, False if it was a duplicate and not forced

        Raises:
            ValueError: If the eval_id already exists with different content
        """
        # Check for duplicates
        if not force and self.is_duplicate(eval_case):
            return False

        # Check if eval_id already exists
        if eval_case.eval_id in self.index:
            existing = self.index[eval_case.eval_id]
            # Allow overwrite if same signature or force=True
            signature = self._compute_signature(eval_case)
            if not force and existing.get("_signature") != signature:
                msg = f"Eval ID {eval_case.eval_id} already exists with different content"
                raise ValueError(msg)

        # Store the case
        case_file = self.cases_dir / f"{eval_case.eval_id}.json"
        case_file.parent.mkdir(parents=True, exist_ok=True)

        case_data = eval_case.to_dict()
        with open(case_file, "w", encoding="utf-8") as f:
            json.dump(case_data, f, indent=2, ensure_ascii=False, sort_keys=True)

        # Update index
        signature = self._compute_signature(eval_case)
        self.index[eval_case.eval_id] = {
            "eval_id": eval_case.eval_id,
            "name": eval_case.name,
            "check_type": eval_case.check_type,
            "source_failure_id": eval_case.source_failure_id,
            "file_path": str(case_file.relative_to(self.store_dir.parent)),
            "_signature": signature,
        }

        self._save_index()
        return True

    def add_many(self, eval_cases: list[RegressionEvalCase], force: bool = False) -> dict[str, int]:
        """Add multiple eval cases to the store.

        Args:
            eval_cases: List of eval cases to add
            force: If True, add even if duplicates

        Returns:
            Dictionary with counts: {"added": N, "skipped": M}
        """
        added = 0
        skipped = 0

        for case in eval_cases:
            was_added = self.add(case, force=force)
            if was_added:
                added += 1
            else:
                skipped += 1

        return {"added": added, "skipped": skipped}

    def get(self, eval_id: str) -> RegressionEvalCase | None:
        """Retrieve an eval case by its ID.

        Args:
            eval_id: The eval ID to look up

        Returns:
            The RegressionEvalCase if found, None otherwise
        """
        if eval_id not in self.index:
            return None

        case_info = self.index[eval_id]
        case_file = self.store_dir.parent / case_info["file_path"]

        if not case_file.exists():
            return None

        with open(case_file, encoding="utf-8") as f:
            data = json.load(f)

        return RegressionEvalCase(
            eval_id=data["eval_id"],
            name=data["name"],
            source_failure_id=data["source_failure_id"],
            check_type=data["check_type"],
            target_files=data["target_files"],
            expected_outcome=data.get("expected_outcome", "pass"),
            config=data.get("config", {}),
            tags=data.get("tags", []),
        )

    def list_cases(self) -> list[dict[str, Any]]:
        """List all eval cases in the store.

        Returns:
            List of case metadata dictionaries from the index
        """
        return list(self.index.values())

    def get_by_check_type(self, check_type: str) -> list[str]:
        """Get eval IDs filtered by check type.

        Args:
            check_type: The check type to filter by

        Returns:
            List of eval IDs matching the check type
        """
        return [
            eval_id
            for eval_id, case_info in self.index.items()
            if case_info["check_type"] == check_type
        ]

    def get_by_source_failure(self, source_failure_id: str) -> list[str]:
        """Get eval IDs that originated from a specific failure.

        Args:
            source_failure_id: The source failure ID to filter by

        Returns:
            List of eval IDs linked to the source failure
        """
        return [
            eval_id
            for eval_id, case_info in self.index.items()
            if case_info["source_failure_id"] == source_failure_id
        ]

    def remove(self, eval_id: str) -> bool:
        """Remove an eval case from the store.

        Args:
            eval_id: The eval ID to remove

        Returns:
            True if the case was removed, False if it didn't exist
        """
        if eval_id not in self.index:
            return False

        # Remove the case file
        case_info = self.index[eval_id]
        case_file = self.store_dir.parent / case_info["file_path"]
        if case_file.exists():
            case_file.unlink()

        # Remove from index
        del self.index[eval_id]
        self._save_index()

        return True

    def clear(self) -> int:
        """Clear all cases from the store.

        Returns:
            The number of cases removed
        """
        count = len(self.index)

        # Remove all case files
        if self.cases_dir.exists():
            for case_file in self.cases_dir.glob("*.json"):
                case_file.unlink()

        # Clear index
        self.index.clear()
        self._save_index()

        return count

    def __len__(self) -> int:
        """Return the number of cases in the store."""
        return len(self.index)
