import uuid
from django.test import TransactionTestCase
from apps.organization import test_assignment_concurrency as concurrency_helpers
from apps.service import test_repair as repair
from apps.service.handover_services import close_service_case
from apps.parts import services as parts
from .test_usage import UsageFixture, setup_usage, deliver_case
from . import usage_services as s, services as stock
from .models import PartsDisposition, DefectiveRecovery


class UsageConcurrencyTests(UsageFixture, TransactionTestCase):
    run_concurrent = concurrency_helpers.AssignmentConcurrencyTests.run_concurrent

    def setUp(self):
        setup_usage(self)

    def test_double_issue(self):
        self.receive()
        reservation = self.reserve(self.approve(self.request()))
        self.run_concurrent(lambda: self.issue(reservation), lambda: self.issue(reservation), expected="validation")

    def test_issue_first_release_rejected(self):
        self.receive()
        reservation = self.reserve(self.approve(self.request()))
        self.run_concurrent(lambda: self.issue(reservation), lambda: self.release(reservation), expected="validation")

    def test_release_first_issue_rejected(self):
        self.receive()
        reservation = self.reserve(self.approve(self.request()))
        self.run_concurrent(lambda: self.release(reservation), lambda: self.issue(reservation), expected="validation")

    def test_issue_first_transfer_cannot_spend_same_stock(self):
        self.receive(quantity=1)
        reservation = self.reserve(self.approve(self.request()))
        self.run_concurrent(lambda: self.issue(reservation), self.move, expected="validation")

    def test_consumption_wins_over_unused_return(self):
        issue = self.prepared_issue(quantity=1)
        _, action = repair.prepared(self)
        self.run_concurrent(lambda: self.consume(issue, action), lambda: self.return_unused(issue), expected="validation")

    def test_return_wins_over_consumption(self):
        issue = self.prepared_issue(quantity=1)
        _, action = repair.prepared(self)
        self.run_concurrent(lambda: self.return_unused(issue), lambda: self.consume(issue, action), expected="validation")

    def test_duplicate_consumption(self):
        issue = self.prepared_issue(quantity=1)
        _, action = repair.prepared(self)
        self.run_concurrent(lambda: self.consume(issue, action), lambda: self.consume(issue, action), expected="validation")
        self.assertEqual(PartsDisposition.objects.count(), 1)

    def test_serial_return_race(self):
        parts.update_spare_part(spare_part=self.part, serialization_policy="REQUIRED_SERIAL")
        unit = stock.register_serialized_unit(actor=self.actor, company=self.company, spare_part=self.part, identifier="RETURN-RACE")
        issue = self.prepared_issue(quantity=1, units=[unit])
        self.run_concurrent(lambda: self.return_unused(issue, units=[unit]), lambda: self.return_unused(issue, units=[unit]), expected="validation")

    def test_recovery_duplicate_command_race(self):
        _, action = repair.prepared(self)
        location = stock.create_location(actor=self.actor, company=self.company, service_center=self.center, code="DEFECTIVE", name="Defective", location_type="DEFECTIVE")
        key = uuid.uuid4()
        def recover():
            return s.recover_defective_component(actor=self.engineer, repair_action=action, location=location,
                component_description="Removed screen", command_key=key)
        self.run_concurrent(recover, recover, expected="validation")
        self.assertEqual(DefectiveRecovery.objects.count(), 1)

    def test_return_first_allows_waiting_closure(self):
        issue = self.prepared_issue(quantity=1)
        deliver_case(self)
        self.run_concurrent(lambda: self.return_unused(issue), lambda: close_service_case(service_case=self.case, actor=self.actor), expected="success")

    def test_partial_return_first_blocks_waiting_closure(self):
        issue = self.prepared_issue(quantity=2)
        deliver_case(self)
        self.run_concurrent(lambda: self.return_unused(issue), lambda: close_service_case(service_case=self.case, actor=self.actor), expected="validation")

    def test_stale_partial_disposition_race(self):
        issue = self.prepared_issue(quantity=2)
        revision = s.usage_revision(issue)
        self.run_concurrent(lambda: self.return_unused(issue, expected_revision=revision), lambda: self.return_unused(issue, expected_revision=revision), expected="validation")
