from __future__ import annotations

import hashlib
import importlib.metadata
import json
import os
import platform
import shutil
import subprocess
import sys
import tempfile
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo


ROOT = Path(__file__).resolve().parent
REPORT = ROOT / "reproducibility-check.json"
META = json.loads((ROOT / "paper-metadata.json").read_text(encoding="utf-8"))

INPUT_FILES = [
    "analysis.py",
    "build_artifacts.py",
    "paper.md",
    "paper-metadata.json",
    "data/energy-monthly-generation-2016-2025.csv",
    "data/taipower-net-generation-purchases-2016-2025.csv",
    "data/factor-observations.csv",
    "assets/fonts/NotoSansTC-Regular.ttf",
    "assets/fonts/OFL.txt",
]

OUTPUT_FILES = [
    "paper-content.json",
    "paper.pdf",
    "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",
]


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 package_version(name: str) -> str:
    try:
        return importlib.metadata.version(name)
    except importlib.metadata.PackageNotFoundError:
        return "not-installed"


def main() -> None:
    errors: list[str] = []
    comparisons: list[dict[str, object]] = []
    stdout_tail: list[str] = []

    for relative in INPUT_FILES + OUTPUT_FILES:
        if not (ROOT / relative).is_file():
            errors.append(f"Missing required reproducibility file: {relative}")

    if not errors:
        with tempfile.TemporaryDirectory(prefix="shwrp-2026-030-repro-") as temp_name:
            temp_root = Path(temp_name)
            for relative in INPUT_FILES:
                source = ROOT / relative
                destination = temp_root / relative
                destination.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(source, destination)

            environment = os.environ.copy()
            environment["PYTHONHASHSEED"] = "0"
            for command in ([sys.executable, "analysis.py"], [sys.executable, "build_artifacts.py"]):
                completed = subprocess.run(
                    command,
                    cwd=temp_root,
                    env=environment,
                    capture_output=True,
                    text=True,
                    encoding="utf-8",
                    errors="replace",
                    timeout=300,
                    check=False,
                )
                stdout_tail.extend((completed.stdout + "\n" + completed.stderr).splitlines()[-8:])
                if completed.returncode != 0:
                    errors.append(f"Reproduction command failed ({completed.returncode}): {' '.join(command[1:])}")
                    break

            if not errors:
                for relative in OUTPUT_FILES:
                    expected = ROOT / relative
                    actual = temp_root / relative
                    if not actual.is_file():
                        errors.append(f"Reproduction did not create: {relative}")
                        continue
                    expected_hash = sha256(expected)
                    actual_hash = sha256(actual)
                    match = expected_hash == actual_hash
                    comparisons.append(
                        {
                            "path": relative,
                            "expectedBytes": expected.stat().st_size,
                            "actualBytes": actual.stat().st_size,
                            "expectedSha256": expected_hash,
                            "actualSha256": actual_hash,
                            "bitwiseMatch": match,
                        }
                    )
                    if not match:
                        errors.append(f"Bitwise reproduction mismatch: {relative}")

    report = {
        "schemaVersion": 2,
        "reportNumber": "SHWRP-2026-030",
        "version": META["version"],
        "reproducedAt": now_taipei(),
        "status": "PASSED" if not errors else "FAILED",
        "scope": "Controlled-environment bitwise rerun on the publishing host; this does not promise identical rendering across operating systems.",
        "commands": ["python analysis.py", "python build_artifacts.py"],
        "environment": {
            "python": platform.python_version(),
            "platform": platform.platform(),
            "matplotlib": package_version("matplotlib"),
            "pillow": package_version("Pillow"),
            "pypdf": package_version("pypdf"),
            "pyyaml": package_version("PyYAML"),
            "reportlab": package_version("reportlab"),
        },
        "files": comparisons,
        "errors": errors,
        "commandOutputTail": stdout_tail[-12:],
    }
    REPORT.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps({"status": report["status"], "files": len(comparisons), "report": str(REPORT)}, ensure_ascii=False))
    if errors:
        raise SystemExit(1)


if __name__ == "__main__":
    main()
