"""Additional cross-phase adversarial PostgreSQL serialization scenarios."""
from django.core.exceptions import ValidationError, PermissionDenied
from django.test import TransactionTestCase

from apps.access import services as access
from apps.devices import services as devices
from apps.service_catalog import services as taxonomy
from apps.organization import test_assignment_concurrency as concurrency
from apps.service.models import ServiceCase, ServiceCaseDeliveryRelease, ServiceCaseHandover
from .test_phase3a_audit import setup_audit, reach, operate, invariants


class Phase3AConcurrencyTests(TransactionTestCase):
    run_concurrent = concurrency.AssignmentConcurrencyTests.run_concurrent

    def setUp(self):
        setup_audit(self)

    def race(self, first, second, expected="validation"):
        def attempt():
            try:
                second()
            except PermissionDenied as error:
                raise ValidationError("Scope revoked") from error
        self.run_concurrent(first, attempt, expected=expected)
        invariants(self)

    def op(self, name):
        return lambda: operate(self, name)

    def rejected_then_hold(self, action):
        with self.assertRaises(ValidationError):
            action()
        ServiceCase.objects.select_for_update().get(pk=self.case.pk)

    def test_assign_then_cancel_ends_new_assignment(self):
        self.race(self.op("assign"), self.op("cancel"), "success")

    def test_cancel_then_assign_cannot_resurrect(self):
        self.race(self.op("cancel"), self.op("assign"))

    def test_diagnosis_complete_then_engineer_revocation_preserves_history(self):
        reach(self, "DIAGNOSING")
        self.race(self.op("diagnosis_complete"), lambda: access.deactivate_role_assignment(assignment=self.role_assignment), "success")

    def test_engineer_revocation_then_diagnosis_complete_rejects(self):
        reach(self, "DIAGNOSING")
        self.race(lambda: access.deactivate_role_assignment(assignment=self.role_assignment), self.op("diagnosis_complete"))

    def test_successful_repair_then_cancel_prohibited(self):
        reach(self, "REPAIRING")
        self.race(self.op("repair_success"), self.op("cancel"))

    def test_cancel_then_repair_completion_cannot_resume(self):
        reach(self, "REPAIRING")
        self.race(self.op("cancel"), self.op("repair_success"))

    def test_repair_complete_then_taxonomy_deactivation_preserves_evidence(self):
        reach(self, "REPAIRING")
        self.race(self.op("repair_success"), lambda: taxonomy.deactivate_repair_action(repair_action=self.action_type), "success")

    def test_taxonomy_deactivation_then_repair_complete_rejects(self):
        reach(self, "REPAIRING")
        self.race(lambda: taxonomy.deactivate_repair_action(repair_action=self.action_type), self.op("repair_success"))

    def test_qc_start_then_assignment_mutation_rejects(self):
        reach(self, "QC_PENDING")
        self.race(self.op("qc_begin"), self.op("reassign"))

    def test_assignment_mutation_rejected_then_qc_start_succeeds(self):
        reach(self, "QC_PENDING")
        self.race(lambda: self.rejected_then_hold(self.op("reassign")), self.op("qc_begin"), "success")

    def test_qc_pass_then_fail_cannot_overwrite(self):
        reach(self, "QC_IN_PROGRESS")
        self.race(self.op("qc_pass"), self.op("qc_fail"))

    def test_qc_fail_then_pass_cannot_overwrite(self):
        reach(self, "QC_IN_PROGRESS")
        self.race(self.op("qc_fail"), self.op("qc_pass"))

    def test_qc_pass_then_device_deactivation_preserves_history(self):
        reach(self, "QC_IN_PROGRESS")
        self.race(self.op("qc_pass"), lambda: devices.deactivate_device(device=self.device), "success")

    def test_device_deactivation_then_qc_pass_rejects(self):
        reach(self, "QC_IN_PROGRESS")
        self.race(lambda: devices.deactivate_device(device=self.device), self.op("qc_pass"))

    def test_release_then_cancellation_remains_prohibited(self):
        reach(self, "QC_PASSED")
        self.race(self.op("release"), self.op("cancel"))

    def test_cancellation_rejected_then_release_succeeds(self):
        reach(self, "QC_PASSED")
        self.race(lambda: self.rejected_then_hold(self.op("cancel")), self.op("release"), "success")

    def stale_evidence_edit(self, model):
        # Private persistence is deliberately probed; ordinary save is also blocked.
        row = model.objects.get(service_case=self.case)
        row.note = "Synthetic forbidden overwrite"
        row._persist()

    def test_delivery_then_stale_release_edit_rejects(self):
        reach(self, "READY_FOR_DELIVERY")
        def stale():
            ServiceCase.objects.select_for_update().get(pk=self.case.pk)
            self.stale_evidence_edit(ServiceCaseDeliveryRelease)
        from django.db import transaction
        def contender():
            with transaction.atomic():
                stale()
        self.race(self.op("deliver"), contender)

    def test_stale_release_edit_rejected_then_delivery_succeeds(self):
        reach(self, "READY_FOR_DELIVERY")
        self.race(lambda: self.rejected_then_hold(lambda: self.stale_evidence_edit(ServiceCaseDeliveryRelease)), self.op("deliver"), "success")

    def test_delivery_then_assignment_mutation_rejects(self):
        reach(self, "READY_FOR_DELIVERY")
        self.race(self.op("deliver"), self.op("reassign"))

    def test_assignment_mutation_rejected_then_delivery_succeeds(self):
        reach(self, "READY_FOR_DELIVERY")
        self.race(lambda: self.rejected_then_hold(self.op("reassign")), self.op("deliver"), "success")

    def test_closure_then_duplicate_closure_rejects(self):
        reach(self, "DELIVERED")
        self.race(self.op("close"), self.op("close"))

    def test_closure_then_stale_handover_edit_rejects(self):
        from django.db import transaction
        reach(self, "DELIVERED")
        def contender():
            with transaction.atomic():
                ServiceCase.objects.select_for_update().get(pk=self.case.pk)
                self.stale_evidence_edit(ServiceCaseHandover)
        self.race(self.op("close"), contender)

    def test_stale_handover_edit_rejected_then_closure_succeeds(self):
        reach(self, "DELIVERED")
        self.race(lambda: self.rejected_then_hold(lambda: self.stale_evidence_edit(ServiceCaseHandover)), self.op("close"), "success")
