from __future__ import annotations

import hashlib
import json
from pathlib import Path

from epochler.contract.models import PhaseArtifact
from epochler.storage import store


def _hash_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def _hash_directory(path: Path) -> str:
    digest = hashlib.sha256()
    for child in sorted(p for p in path.rglob("*") if p.is_file()):
        digest.update(str(child.relative_to(path)).encode("utf-8"))
        digest.update(_hash_file(child).encode("utf-8"))
    return digest.hexdigest()


def fingerprint_path(path: Path) -> str:
    resolved = path.expanduser().resolve()
    if not resolved.exists():
        raise FileNotFoundError(f"Artifact path does not exist: {resolved}")
    if resolved.is_dir():
        return _hash_directory(resolved)
    return _hash_file(resolved)


def ensure_allowed_root(path: Path, allowed_root: Path) -> Path:
    resolved = path.expanduser().resolve()
    root = allowed_root.expanduser().resolve()
    if not resolved.is_relative_to(root):
        raise ValueError(f"Artifact path must stay within {root}: {resolved}")
    return resolved


def materialize_artifact(
    contract_id: str,
    phase_name: str,
    artifact: PhaseArtifact,
    allowed_root: Path,
) -> PhaseArtifact:
    resolved = ensure_allowed_root(Path(artifact.path), allowed_root)
    fingerprint = artifact.fingerprint or fingerprint_path(resolved)
    versioned = artifact.model_copy(
        update={
            "path": str(resolved),
            "fingerprint": fingerprint,
            "metadata": {
                **artifact.metadata,
                "version_id": fingerprint,
                "allowed_root": str(allowed_root.resolve()),
            },
        }
    )
    store.save_artifact_version(contract_id, phase_name, versioned)
    return versioned


def write_phase_payload(output_dir: Path, payload: dict) -> Path:
    output_dir.mkdir(parents=True, exist_ok=True)
    path = output_dir / "phase_payload.json"
    path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    return path
