from django.test import TransactionTestCase
from apps.organization import test_assignment_concurrency as concurrency_helpers
from apps.parts import services as parts
from apps.service import services as cases, test_diagnosis as diagnosis
from apps.service.test_engineer_assignment import assign
from apps.service.tests import intake
from . import request_services as s, services as stock
from .models import StockReservation
from .test_requests import RequestFixture, setup_requests


class RequestConcurrencyTests(RequestFixture, TransactionTestCase):
    run_concurrent = concurrency_helpers.AssignmentConcurrencyTests.run_concurrent

    def setUp(self):
        setup_requests(self)

    def second_request(self):
        case = intake(self, device=self.device2)
        assign(self, service_case=case)
        assessment = diagnosis.begin(self, service_case=case)
        diagnosis.add(self, assessment)
        diagnosis.complete(self, assessment)
        return self.approve(self.request(service_case=case))

    def test_two_jobs_reserve_last_unit(self):
        self.receive(quantity=1)
        first, second = self.approve(self.request()), self.second_request()
        self.run_concurrent(lambda: self.reserve(first), lambda: self.reserve(second), expected="validation")
        self.assertEqual(StockReservation.objects.filter(status="ACTIVE").count(), 1)

    def test_reservation_first_blocks_transfer(self):
        self.receive(quantity=1)
        row = self.approve(self.request())
        self.run_concurrent(lambda: self.reserve(row), self.move, expected="validation")

    def test_transfer_first_blocks_reservation(self):
        self.receive(quantity=1)
        row = self.approve(self.request())
        self.run_concurrent(self.move, lambda: self.reserve(row), expected="validation")

    def test_case_cancellation_first_blocks_reservation(self):
        self.receive()
        row = self.approve(self.request())
        self.run_concurrent(lambda: cases.cancel_service_case(service_case=self.case, cancelled_by=self.user, reason="Cancelled"), lambda: self.reserve(row), expected="validation")

    def test_reservation_first_case_cancellation_preserves_explicit_release(self):
        self.receive()
        row = self.approve(self.request())
        self.run_concurrent(lambda: self.reserve(row), lambda: cases.cancel_service_case(service_case=self.case, cancelled_by=self.user, reason="Cancelled"), expected="success")
        self.release(StockReservation.objects.get())

    def test_part_deactivation_first_blocks_reservation(self):
        self.receive()
        row = self.approve(self.request())
        self.run_concurrent(lambda: parts.deactivate_spare_part(spare_part=self.part), lambda: self.reserve(row), expected="validation")

    def test_reservation_first_part_deactivation_preserves_releasable_history(self):
        self.receive()
        row = self.approve(self.request())
        self.run_concurrent(lambda: self.reserve(row), lambda: parts.deactivate_spare_part(spare_part=self.part), expected="success")
        self.release(StockReservation.objects.get())

    def test_double_release(self):
        self.receive()
        row = self.reserve(self.approve(self.request()))
        self.run_concurrent(lambda: self.release(row), lambda: self.release(row), expected="validation")

    def test_cancel_request_first_blocks_reservation(self):
        self.receive()
        row = self.approve(self.request())
        self.run_concurrent(lambda: s.cancel_parts_request(actor=self.actor, request=row, reason="Cancelled", expected_revision=row.updated_at.isoformat()), lambda: self.reserve(row), expected="validation")

    def test_release_first_allows_request_cancellation(self):
        self.receive()
        request = self.approve(self.request())
        row = self.reserve(request)
        def cancel():
            request.refresh_from_db()
            return s.cancel_parts_request(actor=self.actor, request=request, reason="Cancelled", expected_revision=request.updated_at.isoformat())
        # The waiting submission captured an old revision; it must be reviewed again.
        self.run_concurrent(lambda: self.release(row), cancel, expected="validation")

    def test_serialized_unit_cannot_be_reserved_for_two_jobs(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="RESERVE-RACE")
        self.receive(quantity=1, units=[unit])
        first, second = self.approve(self.request()), self.second_request()
        self.run_concurrent(lambda: self.reserve(first, units=[unit]), lambda: self.reserve(second, units=[unit]), expected="validation")

    def test_serialized_reservation_blocks_direct_movement(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="RESERVE-MOVE")
        self.receive(quantity=1, units=[unit])
        row = self.approve(self.request())
        self.run_concurrent(lambda: self.reserve(row, units=[unit]), lambda: self.move(units=[unit]), expected="validation")
