from __future__ import annotations

import hashlib
import json
from pathlib import Path
import sys
import tempfile
import unittest

ROOT = Path(__file__).parents[1]
sys.path.insert(0, str(ROOT / "tools"))
from verify_release import verify


COMMIT = "1" * 40
SOURCE_DIGEST = "2" * 64


def _page(revision: str = COMMIT, digest: str = SOURCE_DIGEST) -> str:
    return (
        '<!doctype html><meta name="policy-source-revision" '
        f'content="{revision}"><meta name="policy-source-digest" content="{digest}">'
    )


def _release(root: Path) -> Path:
    build = root / "build"
    canonical = build / "standards/example/v1/index.html"
    revision = build / "standards/example/v1/revisions/draft-1/index.html"
    canonical.parent.mkdir(parents=True)
    revision.parent.mkdir(parents=True)
    canonical.write_text(_page(), encoding="utf-8")
    revision.write_text(_page(), encoding="utf-8")
    (build / "index.html").write_text("<!doctype html>", encoding="utf-8")
    manifest = {
        "schema_version": 1,
        "generated_as_of": "2026-08-18",
        "documents": [
            {
                "id": "example",
                "title": "Example Standard",
                "status": "proposed",
                "revision": "draft-1",
                "owner": "example-owner",
                "last_reviewed": "2026-08-18",
                "review_due": "2027-02-18",
                "canonical_path": "standards/example/v1/index.html",
                "revision_path": "standards/example/v1/revisions/draft-1/index.html",
                "source_repo": "canon",
                "source_path": "canon/standards/example.md",
                "source_revision": COMMIT,
                "source_digest": SOURCE_DIGEST,
            }
        ],
    }
    (build / "publication-manifest.json").write_text(
        json.dumps(manifest, sort_keys=True) + "\n", encoding="utf-8"
    )
    (build / "source-inventory.json").write_text(
        json.dumps(
            {
                "schema_version": "policy-nexus-source-inventory/v1",
                "source_set_digest": "3" * 64,
                "repositories": [
                    {"name": "canon", "revision": COMMIT, "source_count": 1}
                ],
                "sources": [
                    {
                        "source_repo": "canon",
                        "source_path": "canon/standards/example.md",
                        "disposition": "published",
                        "reason": "test",
                    }
                ],
            },
            sort_keys=True,
        )
        + "\n",
        encoding="utf-8",
    )
    return build


class ReleaseVerificationTest(unittest.TestCase):
    def test_release_pipeline_does_not_delete_immutable_revision_tree(self) -> None:
        makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
        containerfile = (ROOT / "Containerfile").read_text(encoding="utf-8")
        self.assertIn("release-build: build release-check", makefile)
        self.assertNotIn("RUN rm -rf build", containerfile)

    def test_accepts_clean_provenance_and_returns_manifest_digest(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            build = _release(Path(directory))
            evidence = verify(build)
            expected = hashlib.sha256(
                (build / "publication-manifest.json").read_bytes()
            ).hexdigest()
            self.assertEqual(expected, evidence["publication_manifest_digest"])
            self.assertEqual(["example"], evidence["documents"])
            self.assertEqual("3" * 64, evidence["source_set_digest"])

    def test_rejects_working_tree_source_revision(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            build = _release(Path(directory))
            manifest_path = build / "publication-manifest.json"
            manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
            dirty = COMMIT + "+working-tree." + SOURCE_DIGEST[:12]
            manifest["documents"][0]["source_revision"] = dirty
            manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
            canonical = build / manifest["documents"][0]["canonical_path"]
            canonical.write_text(_page(revision=dirty), encoding="utf-8")

            with self.assertRaisesRegex(ValueError, "clean 40-hex Git commit"):
                verify(build)

    def test_rejects_immutable_revision_built_from_dirty_source(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            build = _release(Path(directory))
            revision = build / "standards/example/v1/revisions/draft-1/index.html"
            revision.write_text(
                _page(revision=COMMIT + "+working-tree." + SOURCE_DIGEST[:12]),
                encoding="utf-8",
            )
            with self.assertRaisesRegex(ValueError, "immutable revision page"):
                verify(build)

    def test_rejects_page_digest_mismatch(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            build = _release(Path(directory))
            canonical = build / "standards/example/v1/index.html"
            canonical.write_text(_page(digest="3" * 64), encoding="utf-8")
            with self.assertRaisesRegex(ValueError, "provenance differs"):
                verify(build)


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