"""Golden file engine — snapshot management for eval response comparison."""

from __future__ import annotations

import difflib
import json
from pathlib import Path
from typing import Any

from hatchdx.eval.models import EvalError


def golden_dir(evals_dir: Path) -> Path:
    """Return the path to the golden files directory within the evals directory."""
    return evals_dir / ".golden"


def golden_file_path(
    evals_dir: Path,
    suite_name: str,
    eval_name: str,
) -> Path:
    """Return the expected golden file path for a given suite and eval."""
    safe_suite = _sanitize_filename(suite_name)
    safe_eval = _sanitize_filename(eval_name)
    return golden_dir(evals_dir) / safe_suite / f"{safe_eval}.golden.json"


def has_golden_file(
    evals_dir: Path,
    suite_name: str,
    eval_name: str,
) -> bool:
    """Check whether a golden file exists for this suite/eval."""
    return golden_file_path(evals_dir, suite_name, eval_name).is_file()


def save_golden(
    evals_dir: Path,
    suite_name: str,
    eval_name: str,
    data: Any,
) -> Path:
    """Save response data as a golden file. Creates directories as needed.

    Returns the path to the saved golden file.
    """
    path = golden_file_path(evals_dir, suite_name, eval_name)
    path.parent.mkdir(parents=True, exist_ok=True)

    serialized = _serialize(data)
    path.write_text(serialized, encoding="utf-8")
    return path


def load_golden(
    evals_dir: Path,
    suite_name: str,
    eval_name: str,
) -> Any:
    """Load a golden file and return its parsed content.

    Raises EvalError if the file does not exist or is malformed.
    """
    path = golden_file_path(evals_dir, suite_name, eval_name)
    if not path.is_file():
        raise EvalError(
            f"Golden file not found: {path}\n"
            "Run with --update-golden to create it."
        )

    try:
        raw = path.read_text(encoding="utf-8")
    except (PermissionError, OSError) as exc:
        raise EvalError(f"Cannot read golden file {path}: {exc}") from exc

    try:
        return json.loads(raw)
    except json.JSONDecodeError as exc:
        raise EvalError(f"Golden file {path} contains invalid JSON: {exc}") from exc


def compare_golden(
    evals_dir: Path,
    suite_name: str,
    eval_name: str,
    actual_data: Any,
) -> tuple[bool, str]:
    """Compare actual response data against the saved golden file.

    Returns ``(matched, diff_text)`` where *matched* is True if the data
    matches the golden file, and *diff_text* is a unified diff string
    showing the differences (empty if matched).
    """
    expected_data = load_golden(evals_dir, suite_name, eval_name)

    expected_text = _serialize(expected_data)
    actual_text = _serialize(actual_data)

    if expected_text == actual_text:
        return True, ""

    diff_lines = difflib.unified_diff(
        expected_text.splitlines(keepends=True),
        actual_text.splitlines(keepends=True),
        fromfile=f"golden/{_sanitize_filename(suite_name)}/{_sanitize_filename(eval_name)}.golden.json",
        tofile="actual response",
        lineterm="",
    )
    diff_text = "\n".join(diff_lines)

    return False, diff_text


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _serialize(data: Any) -> str:
    """Serialize data to a deterministic, human-readable JSON string."""
    return json.dumps(data, indent=2, sort_keys=True, ensure_ascii=False) + "\n"


def _sanitize_filename(name: str) -> str:
    """Convert a human-readable name into a safe filesystem name.

    Replaces spaces and special characters with underscores, lowercases.
    """
    safe = name.lower().strip()
    # Replace path traversal patterns before other replacements
    safe = safe.replace("..", "_")
    # Replace common separators and special chars with underscores
    for char in " /\\:*?\"<>|'":
        safe = safe.replace(char, "_")
    # Collapse multiple underscores
    while "__" in safe:
        safe = safe.replace("__", "_")
    # Strip leading/trailing underscores and dots
    safe = safe.strip("_.")
    # Guard against empty or unsafe result
    if not safe or safe == ".":
        safe = "_unnamed"
    return safe
