from __future__ import annotations

import importlib.util
import json
import datetime as dt
import os
from pathlib import Path
import sys
import tempfile
import unittest
from unittest import mock


ROOT = Path(__file__).parents[1]
sys.path.insert(0, str(ROOT / "tools"))
SPEC = importlib.util.spec_from_file_location("build_site", ROOT / "tools/build_site.py")
build_site = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
SPEC.loader.exec_module(build_site)


def _fixture(tmp_path: Path) -> Path:
    repo = tmp_path / "canon-repo"
    source = repo / "canon/standards/example.md"
    source.parent.mkdir(parents=True)
    source.write_text(
        """---
id: example
title: "Example Standard"
status: proposed
revision: "draft-1"
owner: example-owner
last_reviewed: "2026-08-18"
review_interval: 6m
---

# Example

## 1. Rule

| Level | Meaning |
| --- | --- |
| **V0** | No position. |
| **V1** | Restart recovery. |
""",
        encoding="utf-8",
    )
    manifest = tmp_path / "publication.json"
    manifest.write_text(
        json.dumps(
            {
                "schema_version": 1,
                "site": {"title": "Test", "base_url": "https://example.invalid"},
                "repositories": {"canon": {"path": "canon-repo"}},
                "documents": [
                    {
                        "id": "example",
                        "source_repo": "canon",
                        "source_path": "canon/standards/example.md",
                        "canonical_path": "standards/example/v1/index.html",
                        "revision_path": "standards/example/v1/revisions/{revision}/index.html",
                        "legacy_paths": ["example.html"],
                        "review_interval": "6m",
                    }
                ],
            }
        ),
        encoding="utf-8",
    )
    return manifest


class PublicationTest(unittest.TestCase):
    def test_archive_build_accepts_only_exact_source_revision_override(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            repo = root / "net-kingdom"
            source = repo / "canon/example.md"
            source.parent.mkdir(parents=True)
            source.write_text("example", encoding="utf-8")
            key = "POLICY_NEXUS_SOURCE_REVISION_NET_KINGDOM"
            with mock.patch.dict(os.environ, {key: "a" * 40}):
                self.assertEqual("a" * 40, build_site._source_revision(repo, source))
            with mock.patch.dict(os.environ, {key: "main"}):
                with self.assertRaisesRegex(ValueError, "clean 40-hex Git commit"):
                    build_site._source_revision(repo, source)

    def test_manifest_builds_index_current_revision_and_legacy_alias(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            manifest = _fixture(root)
            output = root / "site"
            records = build_site.build(manifest, output, as_of=dt.date(2026, 8, 18))

            self.assertEqual("2027-02-18", records[0]["review_due"])
            self.assertTrue((output / "index.html").is_file())
            current = (output / "standards/example/v1/index.html").read_text(
                encoding="utf-8"
            )
            revision = output / "standards/example/v1/revisions/draft-1/index.html"
            self.assertIn("Availability", current)
            self.assertIn("policy-source-revision", current)
            self.assertIn("policy-source-digest", current)
            self.assertIn("Review due: 2027-02-18", current)
            self.assertEqual(revision.read_text(encoding="utf-8"), current)
            self.assertIn(
                "/standards/example/v1/index.html",
                (output / "example.html").read_text(encoding="utf-8"),
            )

    def test_revision_address_refuses_changed_source_under_same_revision(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            manifest = _fixture(root)
            output = root / "site"
            build_site.build(manifest, output, as_of=dt.date(2026, 8, 18))
            source = root / "canon-repo/canon/standards/example.md"
            source.write_text(
                source.read_text(encoding="utf-8") + "\nChanged.\n", encoding="utf-8"
            )

            with self.assertRaisesRegex(RuntimeError, "immutable revision"):
                build_site.build(manifest, output, as_of=dt.date(2026, 8, 18))

    def test_same_content_can_move_from_worktree_to_commit_provenance(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            manifest = _fixture(root)
            output = root / "site"
            build_site.build(manifest, output, as_of=dt.date(2026, 8, 18))
            revision = output / "standards/example/v1/revisions/draft-1/index.html"
            original = revision.read_text(encoding="utf-8")

            original_source_revision = build_site._source_revision
            try:
                build_site._source_revision = lambda _repo, _source: "new-commit"
                build_site.build(manifest, output, as_of=dt.date(2026, 8, 18))
            finally:
                build_site._source_revision = original_source_revision

            self.assertEqual(original, revision.read_text(encoding="utf-8"))
            current = (output / "standards/example/v1/index.html").read_text(
                encoding="utf-8"
            )
            self.assertIn('policy-source-revision" content="new-commit', current)

    def test_stale_and_superseded_notices_do_not_mutate_revision_page(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            manifest = _fixture(root)
            document = json.loads(manifest.read_text(encoding="utf-8"))
            document["documents"][0]["lifecycle"] = "superseded"
            document["documents"][0]["successor"] = "/standards/example/v2/"
            manifest.write_text(json.dumps(document), encoding="utf-8")
            output = root / "site"

            build_site.build(manifest, output, as_of=dt.date(2027, 2, 19))

            current = (output / "standards/example/v1/index.html").read_text(
                encoding="utf-8"
            )
            revision = (
                output / "standards/example/v1/revisions/draft-1/index.html"
            ).read_text(encoding="utf-8")
            self.assertIn("Superseded.", current)
            self.assertIn("Review overdue.", current)
            self.assertNotIn("Superseded.", revision)
            self.assertNotIn("Review overdue.", revision)

    def test_manifest_rejects_unsafe_publication_path(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            manifest = _fixture(root)
            document = json.loads(manifest.read_text(encoding="utf-8"))
            document["documents"][0]["canonical_path"] = "../escape.html"
            manifest.write_text(json.dumps(document), encoding="utf-8")

            with self.assertRaisesRegex(ValueError, "unsafe publication path"):
                build_site.load_manifest(manifest)

    def test_build_rejects_missing_publication_owner(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            manifest = _fixture(root)
            source = root / "canon-repo/canon/standards/example.md"
            source.write_text(
                source.read_text(encoding="utf-8").replace(
                    "owner: example-owner\n", ""
                ),
                encoding="utf-8",
            )
            with self.assertRaisesRegex(ValueError, "owner is required"):
                build_site.build(manifest, root / "site")


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