import uuid
from django.core.exceptions import PermissionDenied
from django.test import TransactionTestCase
from django.urls import reverse
from apps.organization import test_assignment_concurrency as concurrency_helpers
from apps.parts import services as parts
from apps.access.services import set_role_permissions
from .tests import make_inventory_fixture, grant
from .test_control import ControlFixture
from . import services as stock
from .models import StockMovement


class AuditConcurrencyTests(ControlFixture, TransactionTestCase):
    run_concurrent = concurrency_helpers.AssignmentConcurrencyTests.run_concurrent

    def setUp(self):
        make_inventory_fixture(self)

    def receive_as_staff(self):
        return stock.receive_stock(actor=self.staff, destination=self.location, spare_part=self.part,
            quantity=1, reference="AUDIT-RACE", idempotency_key=uuid.uuid4())

    def test_policy_change_first_blocks_incompatible_first_adjustment(self):
        self.run_concurrent(lambda: parts.update_spare_part(spare_part=self.part, serialization_policy="REQUIRED_SERIAL"),
            lambda: self.adjust(1), expected="validation")
        self.assertFalse(StockMovement.objects.exists())

    def test_first_adjustment_locks_policy_permanently(self):
        self.run_concurrent(lambda: self.adjust(1),
            lambda: parts.update_spare_part(spare_part=self.part, serialization_policy="OPTIONAL_SERIAL"), expected="validation")
        self.assertEqual(StockMovement.objects.count(), 1)

    def test_permission_revocation_first_blocks_posting(self):
        role = grant(self.staff, self.company, permission_names=["receive_stock"])
        def attempt():
            with self.assertRaises(PermissionDenied):
                self.receive_as_staff()
        self.run_concurrent(lambda: set_role_permissions(role=role, permissions=[]), attempt, expected="success")
        self.assertFalse(StockMovement.objects.exists())

    def test_posting_first_completes_before_permission_revocation(self):
        role = grant(self.staff, self.company, permission_names=["receive_stock"])
        self.run_concurrent(self.receive_as_staff, lambda: set_role_permissions(role=role, permissions=[]), expected="success")
        self.assertEqual(StockMovement.objects.count(), 1)
        with self.assertRaises(PermissionDenied):
            self.receive_as_staff()

    def test_location_confirmation_rechecks_revision_after_real_lock_wait(self):
        self.client.force_login(self.actor)
        url = reverse("admin:inventory_inventorylocation_changelist")
        data = {"action": "deactivate_selected", "_selected_action": str(self.location.pk)}
        preview = self.client.post(url, data)
        confirmation = data | {"confirm_transition": "1", "transition_revision": preview.context_data["transition_revision"]}
        self.run_concurrent(lambda: stock.update_location(actor=self.actor, location=self.location, name="Concurrent change", expected_revision=self.location.updated_at.isoformat()),
            lambda: self.client.post(url, confirmation), expected="success")
        self.location.refresh_from_db()
        self.assertTrue(self.location.is_active)
        self.assertEqual(self.location.name, "Concurrent change")
