"""Assemble explicit local release artifacts without publishing or discovering credentials."""

from __future__ import annotations

import hashlib
import json
import shutil
import tempfile
from pathlib import Path


def sha256(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 bundle(
    output_dir: str | Path,
    *,
    model_card: str | Path,
    report: str | Path,
    artifacts: list[str | Path],
    training_manifest: str | Path | None = None,
) -> dict:
    """Copy only named inputs into a new directory and record their content identities.

    This packages evidence; it does not certify release readiness. Model weights,
    datasets, code, and teacher outputs require their own permissions review.
    """
    output = Path(output_dir).resolve()
    if output.exists():
        raise ValueError(f"Output already exists: {output}")
    card, evaluation = Path(model_card).resolve(), Path(report).resolve()
    paths = [card, evaluation, *(Path(p).resolve() for p in artifacts)]
    if not artifacts:
        raise ValueError("At least one explicit model or adapter artifact is required")
    if training_manifest:
        paths.append(Path(training_manifest).resolve())
    for path in paths:
        if not path.is_file():
            raise ValueError(f"Expected an existing file: {path}")
    report_bytes, card_bytes = evaluation.read_bytes(), card.read_bytes()
    data = json.loads(report_bytes)
    if data.get("status") != "complete":
        raise ValueError("Evaluation must be complete before bundling")
    from .evaluation import _verify_report

    try:
        _verify_report(data)
    except (ValueError, KeyError, TypeError) as error:
        raise ValueError(f"Evaluation evidence is invalid: {error}") from error
    if "TODO" in card_bytes.decode("utf-8"):
        raise ValueError("Complete the TODO fields in the model card before bundling")
    entries = [(card, "README.md"), (evaluation, "evaluation/report.json")]
    entries.extend((Path(p).resolve(), f"artifacts/{Path(p).name}") for p in artifacts)
    if training_manifest:
        entries.append((Path(training_manifest).resolve(), "training/manifest.json"))
    names = [name for _, name in entries]
    if len(set(names)) != len(names):
        raise ValueError("Artifact basenames must be unique")
    output.parent.mkdir(parents=True, exist_ok=True)
    with tempfile.TemporaryDirectory(prefix=".bundle-", dir=output.parent) as temporary:
        staging = Path(temporary) / "bundle"
        staging.mkdir()
        files = []
        snapshots = {"README.md": card_bytes, "evaluation/report.json": report_bytes}
        for source, name in entries:
            destination = staging / name
            destination.parent.mkdir(parents=True, exist_ok=True)
            if name in snapshots:
                destination.write_bytes(snapshots[name])
            else:
                shutil.copyfile(source, destination)
            files.append({"path": name, "sha256": sha256(destination), "size_bytes": destination.stat().st_size})
        manifest = {
            "schema_version": 1,
            "status": "candidate",
            "published": False,
            "release_eligible": False,
            "reason": "Local evidence bundle; requires scientific and permissions review before publication.",
            "evaluation_release_eligible": data.get("release_eligible", False),
            "files": files,
        }
        (staging / "bundle.json").write_text(
            json.dumps(manifest, indent=2, allow_nan=False) + "\n", encoding="utf-8"
        )
        # Rename within the same filesystem so partial bundles never appear as completed outputs.
        staging.rename(output)
    return manifest
