"""Challenge loading, validation, and challenge-set hashing.

A challenge is one JSON file in challenges/:

    {
      "id": "kebab-case-id",
      "title": "...",
      "spec": "prompt text given to the model",
      "starter_files": {"path": "content", ...},        # optional
      "tests": {"test_x.py": "pytest content", ...},
      "expected_files": ["solution.py", ...],           # deliverables
      "content_checks": {"path": ["substring", ...]},   # optional
      "reference_solution": {"path": "content", ...}    # must pass tests
    }

Results are only comparable within one challenge-set version, so a sha256
over the sorted ids + specs + tests is stamped onto every run.
"""

from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass, field
from pathlib import Path

from .config import REPO_ROOT

CHALLENGES_DIR = REPO_ROOT / "challenges"
REQUIRED_KEYS = {"id", "title", "spec", "tests", "expected_files", "reference_solution"}


@dataclass(frozen=True)
class Challenge:
    id: str
    title: str
    spec: str
    tests: dict[str, str]
    expected_files: list[str]
    starter_files: dict[str, str] = field(default_factory=dict)
    content_checks: dict[str, list[str]] = field(default_factory=dict)
    reference_solution: dict[str, str] = field(default_factory=dict)


def load_challenge(path: Path) -> Challenge:
    raw = json.loads(path.read_text())
    missing = REQUIRED_KEYS - raw.keys()
    if missing:
        raise ValueError(f"{path.name}: missing keys {sorted(missing)}")
    if raw["id"] != path.stem:
        raise ValueError(f"{path.name}: id {raw['id']!r} must match filename")
    if not raw["tests"]:
        raise ValueError(f"{path.name}: challenge has no tests")
    return Challenge(
        id=raw["id"],
        title=raw["title"],
        spec=raw["spec"],
        tests=raw["tests"],
        expected_files=raw["expected_files"],
        starter_files=raw.get("starter_files", {}),
        content_checks=raw.get("content_checks", {}),
        reference_solution=raw["reference_solution"],
    )


def load_all(directory: Path = CHALLENGES_DIR) -> list[Challenge]:
    challenges = [load_challenge(p) for p in sorted(directory.glob("*.json"))]
    if not challenges:
        raise ValueError(f"no challenges found in {directory}")
    return challenges


def set_hash(challenges: list[Challenge]) -> str:
    h = hashlib.sha256()
    for c in sorted(challenges, key=lambda c: c.id):
        h.update(c.id.encode())
        h.update(c.spec.encode())
        h.update(json.dumps(c.tests, sort_keys=True).encode())
    return h.hexdigest()[:16]
