from __future__ import annotations

import hashlib
import json
import zipfile
from pathlib import Path


ROOT = Path(__file__).resolve().parent
ARCHIVE = ROOT / "SHWRP-2026-033-v1.0.zip"
MANIFEST = ROOT / "release-manifest.json"
HASH_FILE = ROOT / "SHWRP-2026-033-v1.0.zip.sha256"

ROOT_FILES = {
    "README.md", "LICENSE.txt", "RIGHTS-NOTICE.txt", "CITATION.cff", "paper.md", "paper.pdf",
    "paper-metadata.json", "paper-content.json", "pyproject.toml", "requirements.txt",
    "source-registry.csv", "dataset-audit.md", "study-protocol.md", "data-management-plan.md",
    "mavic3m-capture-and-qc.md", "field-label-form.csv", "flight-safety-and-consent.md",
    "audit_external_blb_sample.py", "generate_figures.py", "build_pdf.py", "render_pdf.py",
    "validate_study.py", "verify_reproducibility.py", "package_release.py", "validate_package.py",
    "update_zenodo_request.py",
    "validation-report.json", "pdf-render-report.json", "pdf-visual-qa.json",
    "reproducibility-check.json",
}
PREFIXES = (
    "m3m_disease/", "tests/", "schemas/", "assets/fonts/", "outputs/figures/",
    "outputs/demo/", "outputs/external-blb-sample-audit.json",
)
FORBIDDEN = (
    "__pycache__", ".pyc", ".pytest_cache", "BLB_prj_source_code.ipynb", "D1-test-image_patch",
    "D1-test-label_patch", "GD-RGBMS-DB.zip", "precise_latitude", "precise_longitude",
    "full_html", "raw_html", "page_text", "full_description", "review_text", "screenshot",
    "image_binary", "customer_name", "phone", "address",
)


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


def selected_files() -> list[Path]:
    result: list[Path] = []
    for path in ROOT.rglob("*"):
        if not path.is_file():
            continue
        relative = path.relative_to(ROOT).as_posix()
        selected = relative in ROOT_FILES or any(
            relative == prefix or relative.startswith(prefix) for prefix in PREFIXES
        )
        if selected and not any(token.lower() in relative.lower() for token in FORBIDDEN):
            result.append(path)
    return sorted(result, key=lambda item: item.relative_to(ROOT).as_posix())


def main() -> None:
    missing = sorted(name for name in ROOT_FILES if not (ROOT / name).is_file())
    if missing:
        raise FileNotFoundError("Release package root files are missing: " + ", ".join(missing))

    files = selected_files()
    records = [
        {
            "path": path.relative_to(ROOT).as_posix(),
            "bytes": path.stat().st_size,
            "sha256": digest_file(path),
        }
        for path in files
    ]
    payload = {
        "schemaVersion": 1,
        "createdOn": "2026-08-04",
        "reportNumber": "SHWRP-2026-033",
        "version": "1.0",
        "doi": "10.5281/zenodo.21787704",
        "status": "PUBLIC_RELEASE",
        "license": "CC BY 4.0",
        "licenseUrl": "https://creativecommons.org/licenses/by/4.0/",
        "licenseScope": "Author-owned original analysis, text, research software, and original figures only; third-party content retains its original rights and terms.",
        "publicLicenseGranted": True,
        "thirdPartyRawDataIncluded": False,
        "personalDataIncluded": False,
        "precisePrivateFieldLocationsIncluded": False,
        "fieldPerformanceEstablished": False,
        "modelPromotionStatus": "RESEARCH_ONLY",
        "syntheticMetricsAreFieldAccuracy": False,
        "fileCount": len(records),
        "files": records,
    }
    MANIFEST.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")

    if ARCHIVE.exists():
        ARCHIVE.unlink()
    archive_files = [*files, MANIFEST]
    with zipfile.ZipFile(ARCHIVE, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
        for path in sorted(archive_files, key=lambda item: item.relative_to(ROOT).as_posix()):
            relative = path.relative_to(ROOT).as_posix()
            info = zipfile.ZipInfo(relative, date_time=(2026, 8, 4, 0, 0, 0))
            info.compress_type = zipfile.ZIP_DEFLATED
            info.external_attr = 0o644 << 16
            archive.writestr(info, path.read_bytes())

    archive_hash = digest_file(ARCHIVE)
    HASH_FILE.write_text(f"{archive_hash}  {ARCHIVE.name}\n", encoding="utf-8")
    print(json.dumps({
        "status": "PASSED",
        "archive": str(ARCHIVE),
        "sha256": archive_hash,
        "members": len(archive_files),
        "bytes": ARCHIVE.stat().st_size,
    }, ensure_ascii=False))


if __name__ == "__main__":
    main()
