from __future__ import annotations

import importlib.util
import json
import subprocess
import tempfile
import unittest
from datetime import UTC, datetime
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
SPEC = importlib.util.spec_from_file_location(
    "wp0024_owner_review", ROOT / "scripts" / "wp0024-owner-review.py"
)
assert SPEC and SPEC.loader
module = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(module)


class OwnerReviewTests(unittest.TestCase):
    def setUp(self) -> None:
        self.contract = module.load_contract()

    def test_contract_has_required_owners_and_only_known_checks(self) -> None:
        self.assertEqual(
            {
                "audit-core",
                "rapp-postgres",
                "railiance-cluster",
                "railiance-infra",
                "railiance-platform",
            },
            set(self.contract["owners"]),
        )
        checks = {
            check
            for review in self.contract["owners"].values()
            for check in review["checks"]
        }
        self.assertLessEqual(checks, module.KNOWN_CHECKS)
        self.assertEqual(
            {
                "RAILIANCE-WP-0024-T02": [
                    "audit-core",
                    "rapp-postgres",
                    "railiance-platform",
                ],
                "RAILIANCE-WP-0024-T03": [
                    "audit-core",
                    "rapp-postgres",
                    "railiance-cluster",
                    "railiance-infra",
                    "railiance-platform",
                ],
            },
            self.contract["task_owners"],
        )
        self.assertEqual(64, len(module.contract_digest(self.contract)))

    def test_current_artifacts_match_contract(self) -> None:
        for owner in self.contract["owners"]:
            state = module.artifact_state(self.contract, owner)
            self.assertTrue(all(item["matches"] for item in state.values()))

    def test_artifact_change_prevents_checks_and_approval(self) -> None:
        contract = json.loads(json.dumps(self.contract))
        owner = "rapp-postgres"
        review = contract["owners"][owner]
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            for relative in review["artifacts"]:
                target = root / relative
                target.parent.mkdir(parents=True, exist_ok=True)
                target.write_bytes((ROOT / relative).read_bytes())
            (root / review["artifacts"][0]).write_text("changed", encoding="utf-8")
            result = module.verify(
                contract,
                owner,
                root=root,
                check_runner=lambda _: self.fail("checks must not run after hash mismatch"),
            )
        self.assertFalse(result["passed"])
        self.assertEqual([], result["checks"])
        with self.assertRaises(module.ReviewError):
            module.build_receipt(
                contract, owner, "approve", owner, verification=result
            )

    def test_named_preflight_requires_safe_machine_result(self) -> None:
        def runner(*args, **kwargs):
            return subprocess.CompletedProcess(
                args[0],
                0,
                stdout=json.dumps(
                    {
                        "automated_checks_passed": True,
                        "ready_for_live_execution": False,
                        "secret_values_observed": False,
                    }
                ),
                stderr="",
            )

        result = module.run_named_check("database-lease-preflight", runner=runner)
        self.assertTrue(result["passed"])
        self.assertFalse(result["ready_for_live_execution"])
        self.assertFalse(result["secret_values_observed"])

    def test_receipt_round_trip_and_stale_digest_invalidation(self) -> None:
        owner = "audit-core"
        verification = {
            "passed": True,
            "checks": [
                {"id": check, "passed": True}
                for check in self.contract["owners"][owner]["checks"]
            ],
        }
        receipt = module.build_receipt(
            self.contract,
            owner,
            "approve",
            owner,
            verification=verification,
            now=datetime(2026, 8, 22, 12, 0, tzinfo=UTC),
        )
        message = {
            "from_agent": owner,
            "subject": module.receipt_subject(receipt),
            "body": module.canonical_json(receipt),
        }
        self.assertEqual(receipt, module.parse_receipt_message(message))
        current = module.aggregate_status(self.contract, [message])
        self.assertEqual("approve", current["owners"][owner]["decision"])
        self.assertTrue(current["contract_artifacts_current"])
        self.assertFalse(current["tasks"]["RAILIANCE-WP-0024-T02"]["all_approved"])
        self.assertFalse(current["all_approved"])

        stale = json.loads(json.dumps(receipt))
        stale["contract_digest"] = "0" * 64
        stale_message = {
            "from_agent": owner,
            "subject": module.receipt_subject(stale),
            "body": module.canonical_json(stale),
        }
        status = module.aggregate_status(self.contract, [stale_message])
        self.assertEqual("missing", status["owners"][owner]["decision"])
        self.assertEqual(1, status["owners"][owner]["stale_receipt_count"])

    def test_incomplete_approval_is_not_counted(self) -> None:
        owner = "rapp-postgres"
        verification = {
            "passed": True,
            "checks": [
                {"id": check, "passed": True}
                for check in self.contract["owners"][owner]["checks"]
            ],
        }
        receipt = module.build_receipt(
            self.contract, owner, "approve", owner, verification=verification
        )
        receipt["checks"].pop()
        message = {
            "from_agent": owner,
            "subject": module.receipt_subject(receipt),
            "body": module.canonical_json(receipt),
        }
        status = module.aggregate_status(self.contract, [message])
        self.assertEqual("missing", status["owners"][owner]["decision"])
        self.assertEqual(1, status["owners"][owner]["stale_receipt_count"])

    def test_malformed_receipt_message_is_ignored(self) -> None:
        message = {
            "from_agent": "audit-core",
            "subject": module.RECEIPT_PREFIX + "/invalid",
            "body": "{}",
        }
        self.assertIsNone(module.parse_receipt_message(message))

    def test_request_changes_requires_safe_note(self) -> None:
        receipt = module.build_receipt(
            self.contract,
            "railiance-infra",
            "request-changes",
            "infra-owner",
            note="Name the provider-console operator.",
        )
        self.assertEqual("request-changes", receipt["decision"])
        with self.assertRaises(module.ReviewError):
            module.build_receipt(
                self.contract,
                "railiance-infra",
                "request-changes",
                "infra-owner",
                note="",
            )


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