from __future__ import annotations

import hashlib
import json
import zipfile
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo


ROOT = Path(__file__).resolve().parent
META = json.loads((ROOT / "paper-metadata.json").read_text(encoding="utf-8"))
VERSION = META["version"]
ZIP = ROOT / f"SHWRP-2026-030-v{VERSION}.zip"
MANIFEST = ROOT / "release-manifest.json"

PAYLOAD_FILES = [
    "README.md",
    "paper.md",
    "paper.pdf",
    "paper-content.json",
    "paper-metadata.json",
    "CITATION.cff",
    "LICENSE.txt",
    "RIGHTS-NOTICE.txt",
    "source-registry.csv",
    "source-log.csv",
    "source-evidence.json",
    "codebook.csv",
    "cleaning-rules.md",
    "requirements.txt",
    "fetch_sources.py",
    "prepare_data.py",
    "analysis.py",
    "build_artifacts.py",
    "validate.py",
    "verify_reproducibility.py",
    "package_release.py",
    "validate_package.py",
    "pdf-visual-qa.json",
    "validation-report.json",
    "reproducibility-check.json",
    "data/energy-monthly-generation-2016-2025.csv",
    "data/taipower-net-generation-purchases-2016-2025.csv",
    "data/factor-observations.csv",
    "outputs/analysis-results.json",
    "outputs/annual-generation-mix.csv",
    "outputs/monthly-generation-shares-2025.csv",
    "outputs/taipower-scope-cross-check.csv",
    "outputs/electricity-factor-comparison.csv",
    "outputs/figures/figure-1-generation-mix-2016-2025.png",
    "outputs/figures/figure-2-mix-change-2024-2025.png",
    "outputs/figures/figure-3-monthly-generation-shares-2025.png",
    "outputs/figures/figure-4-factor-scope-comparison-2025.png",
    "assets/fonts/NotoSansTC-Regular.ttf",
    "assets/fonts/OFL.txt",
    "assets/schemas/cff-1.2.0-schema.json",
]


def now_taipei() -> str:
    return datetime.now(ZoneInfo("Asia/Taipei")).replace(microsecond=0).isoformat()


def sha256(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:
    payload: list[Path] = []
    for relative in PAYLOAD_FILES:
        path = ROOT / relative
        if not path.is_file() or path.stat().st_size == 0:
            raise FileNotFoundError(f"Missing or empty allowlisted payload file: {relative}")
        payload.append(path)

    manifest = {
        "schemaVersion": 2,
        "reportNumber": "SHWRP-2026-030",
        "version": VERSION,
        "createdAt": now_taipei(),
        "archiveTimestampPolicy": "Every ZIP member uses the normalized DOS timestamp 1980-01-01 00:00:00; this is not a publication event timestamp.",
        "licenseScope": "CC BY 4.0 applies only to author-licensable original content; third-party and government-source content retains its original terms.",
        "excluded": [
            "private-source-cache/**",
            "complete official PDF and web pages",
            "complete raw third-party downloads",
            "credentials, cookies, tokens, private keys, non-consented personal data, caches and bytecode",
            "the archive itself and post-package attestations",
        ],
        "files": [
            {
                "path": path.relative_to(ROOT).as_posix(),
                "bytes": path.stat().st_size,
                "sha256": sha256(path),
            }
            for path in payload
        ],
    }
    MANIFEST.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")

    archive_files = sorted([*payload, MANIFEST], key=lambda item: item.relative_to(ROOT).as_posix())
    with zipfile.ZipFile(ZIP, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9, strict_timestamps=True) as archive:
        for path in archive_files:
            relative = path.relative_to(ROOT).as_posix()
            info = zipfile.ZipInfo(relative, date_time=(1980, 1, 1, 0, 0, 0))
            info.create_system = 3
            info.compress_type = zipfile.ZIP_DEFLATED
            info.external_attr = 0o100644 << 16
            archive.writestr(info, path.read_bytes(), compress_type=zipfile.ZIP_DEFLATED, compresslevel=9)

    print(json.dumps({"zip": str(ZIP), "entries": len(archive_files), "bytes": ZIP.stat().st_size, "sha256": sha256(ZIP)}, ensure_ascii=False))


if __name__ == "__main__":
    main()
