from __future__ import annotations

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


ROOT = Path(__file__).resolve().parents[1]
OUTPUT = (
    ROOT.parent / "PolicyFaultBench_v0.6.0_POLICY_GENERALIZATION_V1.zip"
)
FREEZE = ROOT / "config" / "policy_generalization_freeze_v1.json"

INCLUDE = (
    "src",
    "policies",
    "scenarios",
    "schemas",
    "config",
    "manifests",
    "external_sources",
    "tests",
    "scripts",
    "analysis",
    "results/generalization_v1",
    "study/final",
    "study/confirmatory_v2",
    "study/generalization_v1",
    "README.md",
    "SAFETY.md",
    "SECURITY.md",
    "INSTALL_AR.md",
    "pyproject.toml",
)

EXCLUDED_NAMES = {
    ".env",
    ".env.local",
    "__pycache__",
    ".pytest_cache",
    ".git",
}

SECRET_MARKERS = (b"sk-proj-", b"sk-ant-", b"aiza")


def sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def verify_freeze() -> dict:
    freeze = json.loads(FREEZE.read_text(encoding="utf-8"))
    for relative, expected in freeze["frozen_file_sha256"].items():
        path = ROOT / relative
        if not path.is_file() or sha256(path) != expected:
            raise RuntimeError(f"Frozen component changed: {relative}")
    return freeze


def copy_selected(staging_root: Path) -> None:
    project = staging_root / "PolicyFaultBench"
    project.mkdir(parents=True)
    for relative in INCLUDE:
        source = ROOT / relative
        if not source.exists():
            continue
        target = project / relative
        if source.is_dir():
            shutil.copytree(
                source,
                target,
                ignore=shutil.ignore_patterns(*EXCLUDED_NAMES, "*.pyc"),
            )
        else:
            target.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(source, target)


def build_manifest(staging_root: Path, freeze: dict) -> Path:
    project = staging_root / "PolicyFaultBench"
    files = sorted(path for path in project.rglob("*") if path.is_file())
    manifest = {
        "schema_version": "1.0",
        "package": "PolicyFaultBench v0.6.0 policy generalization",
        "protocol_id": freeze["protocol_id"],
        "schedule_hash": freeze["schedule_hash"],
        "file_count": len(files),
        "files": {
            str(path.relative_to(project)): sha256(path) for path in files
        },
        "api_keys_included": False,
    }
    path = project / "PACKAGE_MANIFEST.json"
    path.write_text(
        json.dumps(manifest, indent=2, sort_keys=True) + "\n",
        encoding="utf-8",
    )
    return path


def scan_for_secrets(staging_root: Path) -> None:
    for path in staging_root.rglob("*"):
        if not path.is_file():
            continue
        if path.name in EXCLUDED_NAMES or path.name.startswith(".env"):
            raise RuntimeError(f"Secret-bearing filename included: {path}")
        if path.suffix.lower() not in {".json", ".jsonl", ".csv"}:
            continue
        data = path.read_bytes().lower()
        if any(marker in data for marker in SECRET_MARKERS):
            raise RuntimeError(f"Credential-like marker found in {path}")


def main() -> None:
    freeze = verify_freeze()
    with tempfile.TemporaryDirectory(prefix="pfb-generalization-package-") as raw:
        staging = Path(raw)
        copy_selected(staging)
        build_manifest(staging, freeze)
        scan_for_secrets(staging)
        with zipfile.ZipFile(OUTPUT, "w", zipfile.ZIP_DEFLATED) as archive:
            for path in sorted(staging.rglob("*")):
                if path.is_file():
                    archive.write(path, arcname=str(path.relative_to(staging)))
    with zipfile.ZipFile(OUTPUT) as archive:
        bad = archive.testzip()
        if bad is not None:
            raise RuntimeError(f"Archive CRC failure: {bad}")
        names = archive.namelist()
        if any(
            Path(name).name.startswith(".env")
            or Path(name).name in EXCLUDED_NAMES
            for name in names
        ):
            raise RuntimeError("Archive contains a prohibited secret path")
    print(
        json.dumps(
            {
                "archive": str(OUTPUT),
                "sha256": sha256(OUTPUT),
                "size_bytes": OUTPUT.stat().st_size,
                "api_keys_included": False,
            },
            indent=2,
            sort_keys=True,
        )
    )


if __name__ == "__main__":
    main()
