import importlib.util
from pathlib import Path
import sys
from unittest import TestCase
from unittest.mock import patch


ROOT = Path(__file__).resolve().parents[2]
SCRIPT = ROOT / "deploy" / "phase1_e2e_provision.py"
SPEC = importlib.util.spec_from_file_location("phase1_e2e_provision", SCRIPT)
MODULE = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = MODULE
SPEC.loader.exec_module(MODULE)


class Phase1E2EProvisionTests(TestCase):
    def config(self):
        return MODULE.ProvisionConfig(
            run_id="phase1-e2e-20260807-01",
            environment="staging",
            source_scope="default",
            timetabler_base_url="https://ttdev.mayvin.io",
            timetabler_email="masked@example.invalid",
            timetabler_password="masked",
            expected_sha="a" * 40,
            github_run_id="31180000000",
        )

    def facts(self):
        return MODULE.ReferenceFacts(
            academic_term_id=10,
            activity_template_id=20,
            booking_type_id=30,
            week_ids=(40, 41),
            booking_from_date="2026-08-10",
            booking_to_date="2026-08-16",
            minute_per_slot=30,
            slots=(1, 2, 3, 4, 5, 6),
        )

    def test_manifest_is_approved_but_conflict_remains_unarmed(self):
        manifest = MODULE.build_manifest(
            config=self.config(), facts=self.facts(), watermark=100
        )
        self.assertTrue(manifest["approved"])
        self.assertFalse(manifest["conflict_armed"])
        self.assertFalse(manifest["load_enabled"])
        self.assertFalse(manifest["dr_enabled"])
        self.assertEqual(manifest["expected_stage_start_watermarks"]["entry"], 100)
        self.assertEqual(manifest["expected_stage_start_watermarks"]["functional"], 100)
        self.assertEqual(manifest["expected_stage_start_watermarks"]["redelivery"], 119)
        self.assertEqual(manifest["expected_stage_start_watermarks"]["conflict"], 119)
        self.assertEqual(manifest["expected_stage_start_watermarks"]["cleanup"], 120)
        self.assertEqual(manifest["expected_stage_start_watermarks"]["final"], 123)

    def test_manifest_has_complete_supported_cleanup(self):
        manifest = MODULE.build_manifest(
            config=self.config(), facts=self.facts(), watermark=100
        )
        cleanup = next(case for case in manifest["cases"] if case["id"] == "E2E-13")[
            "cleanup_steps"
        ]
        self.assertEqual(
            [step["path"] for step in cleanup],
            [
                "/api/admin/activity/delete",
                "/api/admin/staff/delete",
                "/api/admin/location/delete",
            ],
        )
        self.assertTrue(
            all(step["expected_source_transactions"] == 1 for step in cleanup)
        )

    def test_provisioner_contains_no_direct_domain_write_or_rb_client(self):
        source = SCRIPT.read_text()
        for forbidden in (
            ".objects.create(",
            ".objects.update(",
            ".objects.delete(",
            ".save(",
            "ResourceBookingClient",
            "RB_PHASE1_E2E",
            "resourcebooking",
        ):
            self.assertNotIn(forbidden, source)
        self.assertIn('path="/api/admin/staff/create"', source)
        self.assertIn('path="/api/admin/location/create"', source)
        self.assertIn('path="/api/admin/activity/create"', source)
        self.assertIn('path="/api/admin/resources/update-requirement"', source)

    def test_workflow_provision_is_separately_confirmed_and_tt_only(self):
        workflow = (ROOT / ".github/workflows/deploy.yml").read_text()
        job = workflow.split("  phase1-e2e:\n", 1)[1]
        provision = job.split("              provision)\n", 1)[1].split(
            "              entry)\n", 1
        )[0]
        self.assertIn('test "$TT_PHASE1_E2E_CONFIRM_MUTATION" = "True"', provision)
        self.assertIn("unset TT_PHASE1_E2E_FIXTURE_JSON", provision)
        self.assertNotIn('test -z "${TT_PHASE1_E2E_FIXTURE_JSON:-}"', provision)
        self.assertIn("phase1_e2e_provision.py", provision)
        self.assertNotIn("RB_PHASE1_E2E", provision)

    def test_workflow_has_read_only_partial_provision_inspection(self):
        workflow = (ROOT / ".github/workflows/deploy.yml").read_text()
        job = workflow.split("  phase1-e2e:\n", 1)[1]
        inspection = job.split("              provision-inspect)\n", 1)[1].split(
            "              provision)\n", 1
        )[0]
        self.assertIn(
            'test "$TT_PHASE1_E2E_CONFIRM_MUTATION" = "False"', inspection
        )
        self.assertIn("phase1_e2e_provision_inspect.py", inspection)
        self.assertIn("TT_PHASE1_E2E_EXPECTED_DEPLOYED_SHA", inspection)
        self.assertNotIn("phase1_e2e_provision.py\n", inspection)

    def test_partial_provision_inspector_is_read_only_and_tt_only(self):
        inspector = (ROOT / "deploy" / "phase1_e2e_provision_inspect.py").read_text()
        for forbidden in (
            ".objects.create(",
            ".objects.update(",
            ".objects.delete(",
            ".save(",
            "ResourceBookingClient",
            "RB_PHASE1_E2E",
            "resourcebooking",
        ):
            self.assertNotIn(forbidden, inspector)
        self.assertIn("public_source_watermark", inspector)
        self.assertIn("first_mutation_committed_without_source_transaction", inspector)

    def test_requirement_configuration_requires_zero_outbound_transactions(self):
        source = SCRIPT.read_text()
        requirement_call = source.split(
            'path="/api/admin/resources/update-requirement"', 1
        )[1].split(")", 1)[0]
        self.assertIn("expected_source_transactions=0", requirement_call)

    def test_deployed_script_bootstrap_adds_repository_root(self):
        repository_root = str(ROOT)
        original_path = list(sys.path)
        fake_django = type("FakeDjango", (), {"setup": staticmethod(lambda: None)})
        try:
            sys.path[:] = [entry for entry in sys.path if entry != repository_root]
            with patch.dict(sys.modules, {"django": fake_django}):
                MODULE.gate.bootstrap_django_runtime()
            self.assertEqual(sys.path[0], repository_root)
        finally:
            sys.path[:] = original_path

    def test_bootstrap_failure_reports_only_exception_class(self):
        fake_django = type(
            "FakeDjango",
            (),
            {
                "setup": staticmethod(
                    lambda: (_ for _ in ()).throw(
                        RuntimeError("secret-bearing configuration detail")
                    )
                )
            },
        )
        with patch.dict(sys.modules, {"django": fake_django}):
            with self.assertRaisesRegex(
                MODULE.gate.EntryGateError,
                r"could not be initialized \(RuntimeError\)$",
            ) as raised:
                MODULE.gate.bootstrap_django_runtime()
        self.assertNotIn("secret-bearing", str(raised.exception))
