# SPDX-License-Identifier: MIT
import copy
import csv
import hashlib
import io
import json
import tempfile
import subprocess
import sys
import unittest
from pathlib import Path
import build_publication as publication


class PublicationTests(unittest.TestCase):
    def setUp(self):
        self.receipt = json.loads((publication.ROOT / "results.json").read_text())

    def test_all_rows_and_domains(self):
        rows, data = publication.derive(self.receipt)
        self.assertEqual(len(list(csv.DictReader(io.StringIO(data.decode())))), 12)
        by_id = {r["case"]: r for r in rows}
        self.assertEqual(by_id["resume-sparse-reused"]["finished_bytes"], 4096)
        self.assertEqual(by_id["resume-sparse-reused"]["matching_source_bytes"], 3584)
        self.assertEqual(by_id["fresh-bad-data"]["done_status"], 1)
        self.assertEqual(by_id["fresh-bad-data"]["first_sector_done_status"], 0)

    def test_rejects_false_completeness(self):
        data = copy.deepcopy(self.receipt)
        data["cases"][0]["finishedBytes"] = 4096
        with self.assertRaises(ValueError):
            publication.derive(data)

    def test_rejects_false_protocol_metadata(self):
        mutations = [
            (["schemaVersion"], 2), (["experiment"], "hardware-recovery"),
            (["evidenceType"], "field-study"), (["source", "sectorBytes"], 4096),
            (["upstream", "version"], "1.29"), (["upstream", "archiveSha256"], "0" * 64),
            (["cases", 0, "mode"], "hardware-recovery"),
            (["cases", 0, "destinationInitially"], "Z-filled"),
            (["cases", 0, "simulatedBadSector"], None),
            (["cases", 0, "sparse"], True),
            (["cases", 0, "operation", "argv"], ["ddrescue", "--version"]),
            (["cases", 0, "doneStatus", "argv"], ["ddrescuelog", "--done-status", "--size=512", "fresh-bad-data.map"]),
            (["cases", 0, "firstSectorDoneStatus", "argv"], ["ddrescuelog", "--done-status", "fresh-bad-data.map"]),
            (["cases", 7, "parentCase"], "fresh-healthy"),
            (["cases", 8, "generatedMapIsApproximate"], False),
            (["cases", 8, "parentCase"], "fresh-healthy"),
        ]
        for path, value in mutations:
            data = copy.deepcopy(self.receipt)
            target = data
            for key in path[:-1]:
                target = target[key]
            target[path[-1]] = value
            with self.subTest(path=path), self.assertRaises(ValueError):
                publication.derive(data)

    def test_rejects_mutated_replay_protocol(self):
        from unittest.mock import patch
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            for name in publication.BUNDLE:
                (root / name).write_bytes((publication.ROOT / name).read_bytes())
            replay = json.loads((root / "replay-20261018.json").read_text())
            replay["source"]["sectorBytes"] = 4096
            (root / "replay-20261018.json").write_text(json.dumps(replay))
            with patch.object(publication, "ROOT", root), self.assertRaisesRegex(ValueError, "sector size"):
                publication.build(root / "output")

    def test_repeatable_build_and_hashes(self):
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            self.assertEqual(publication.build(root), publication.build(root))
            public = root / "sites/hesela-dev/public"
            meta = json.loads((public / "data/datasets/ddrescue-image-provenance.json").read_text())
            for name, digest in meta["sha256"].items():
                self.assertEqual(hashlib.sha256((public / "research/ddrescue-provenance" / name).read_bytes()).hexdigest(), digest)
            self.assertEqual(hashlib.sha256((public / meta["downloadPath"].lstrip("/")).read_bytes()).hexdigest(), meta["csvSha256"])

    def test_downloaded_bundle_runs_in_fresh_directory(self):
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            publication.build(root)
            bundle = root / "sites/hesela-dev/public/research/ddrescue-provenance"
            target = root / "rebuilt"
            result = subprocess.run([sys.executable, "-B", "-OO", str(bundle / "build_publication.py"),
                                     "--dev-root", str(target)], capture_output=True, text=True)
            self.assertEqual(result.returncode, 0, result.stderr)
            name = "sites/hesela-dev/public/data/datasets/ddrescue-image-provenance.json"
            self.assertEqual((root / name).read_bytes(), (target / name).read_bytes())

    def test_manuscript_generation_without_third_party_packages(self):
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            publication.build(root)
            bundle = root / "sites/hesela-dev/public/research/ddrescue-provenance"
            result = subprocess.run([sys.executable, "-B", "-S", str(bundle / "build_report.py"),
                                     "--markdown-only"], capture_output=True, text=True)
            self.assertEqual(result.returncode, 0, result.stderr)
            self.assertEqual((bundle / "report.md").read_bytes(), (publication.ROOT / "report.md").read_bytes())

    def test_replay_is_preserved_and_equivalent(self):
        replay = json.loads((publication.ROOT / "replay-20261018.json").read_text())
        self.assertNotEqual(replay["generatedAt"], self.receipt["generatedAt"])
        self.assertEqual(publication.observe.stable_observations(replay), publication.observe.stable_observations(self.receipt))


if __name__ == "__main__":
    unittest.main()
