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"
REPORT = ROOT / "package-validation-report.json"
FORBIDDEN = (
    "__pycache__", ".pyc", ".pytest_cache", "draft-manifest", "v0.1-draft", "zenodo-request.json",
    "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",
)


def digest_bytes(value: bytes) -> str:
    return hashlib.sha256(value).hexdigest()


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 main() -> None:
    errors: list[str] = []
    for path in (ARCHIVE, MANIFEST, HASH_FILE):
        if not path.is_file():
            errors.append(f"Missing package artifact: {path.name}")
    if errors:
        raise FileNotFoundError("; ".join(errors))

    manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
    identity_ok = all((
        manifest.get("reportNumber") == "SHWRP-2026-033",
        manifest.get("version") == "1.0",
        manifest.get("doi") == "10.5281/zenodo.21787704",
        manifest.get("status") == "PUBLIC_RELEASE",
        manifest.get("license") == "CC BY 4.0",
        manifest.get("publicLicenseGranted") is True,
        manifest.get("thirdPartyRawDataIncluded") is False,
        manifest.get("fieldPerformanceEstablished") is False,
        manifest.get("modelPromotionStatus") == "RESEARCH_ONLY",
        manifest.get("syntheticMetricsAreFieldAccuracy") is False,
    ))
    if not identity_ok:
        errors.append("Release manifest identity, rights, or claim-boundary fields are inconsistent")

    records = manifest.get("files", [])
    if manifest.get("fileCount") != len(records):
        errors.append("Manifest fileCount does not match files")
    expected = {record["path"]: record for record in records}
    expected_names = set(expected) | {"release-manifest.json"}

    with zipfile.ZipFile(ARCHIVE) as archive:
        names = archive.namelist()
        if len(names) != len(set(names)):
            errors.append("Archive contains duplicate member names")
        if set(names) != expected_names:
            errors.append("Archive member list differs from release manifest")
        forbidden = [name for name in names if any(token.lower() in name.lower() for token in FORBIDDEN)]
        if forbidden:
            errors.append("Forbidden archive members: " + ", ".join(forbidden))
        bad_member = archive.testzip()
        if bad_member is not None:
            errors.append(f"Corrupt archive member: {bad_member}")
        embedded = json.loads(archive.read("release-manifest.json"))
        if embedded != manifest:
            errors.append("Embedded release manifest differs from external manifest")
        for name, record in expected.items():
            value = archive.read(name)
            if len(value) != record["bytes"] or digest_bytes(value) != record["sha256"]:
                errors.append(f"Size or SHA-256 mismatch: {name}")

    archive_hash = digest_file(ARCHIVE)
    expected_hash_line = f"{archive_hash}  {ARCHIVE.name}"
    if HASH_FILE.read_text(encoding="utf-8").strip() != expected_hash_line:
        errors.append("Archive checksum sidecar is inconsistent")

    report = {
        "schemaVersion": 1,
        "validatedOn": "2026-08-04",
        "status": "PASSED" if not errors else "FAILED",
        "archive": ARCHIVE.name,
        "archiveBytes": ARCHIVE.stat().st_size,
        "archiveSha256": archive_hash,
        "manifest": MANIFEST.name,
        "manifestFileCount": len(records),
        "archiveMemberCount": len(expected_names),
        "identityAndRightsBoundaryPassed": identity_ok,
        "thirdPartyRawDataIncluded": False,
        "fieldPerformanceEstablished": False,
        "errors": errors,
    }
    REPORT.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps({"status": report["status"], "errors": len(errors), "sha256": archive_hash}, ensure_ascii=False))
    if errors:
        raise SystemExit(1)


if __name__ == "__main__":
    main()
