from django.contrib.auth import get_user_model
from django.test import TransactionTestCase

from apps.organization import assignment_services, services as organization_services
from apps.organization.models import Company
from apps.organization.test_lifecycle import make_tree
from apps.organization import test_assignment_concurrency as concurrency_helpers
from .models import Role, UserRoleAssignment
from . import services


class AccessConcurrencyTests(TransactionTestCase):
    run_concurrent = concurrency_helpers.AssignmentConcurrencyTests.run_concurrent

    def setUp(self):
        self.company, self.region, self.center, self.department = make_tree("CCARE")
        self.user = get_user_model().objects.create_user(username="access-concurrent")
        self.other_user = get_user_model().objects.create_user(username="access-concurrent-other")
        self.scope = assignment_services.create_assignment(user=self.user, company=self.company)
        self.other_scope = assignment_services.create_assignment(user=self.other_user, company=self.company)
        self.role = Role.objects.create(code="SAMPLE", name="Sample")

    def create(self):
        return services.create_role_assignment(user=self.user, role=self.role, organization_assignment=self.scope)

    def test_duplicate_role_assignment_race_database_constraint(self):
        def insert():
            UserRoleAssignment.objects.bulk_create([
                UserRoleAssignment(user=self.user, role=self.role, organization_assignment=self.scope)])
        self.run_concurrent(insert, insert, expected="integrity")
        self.assertEqual(UserRoleAssignment.objects.active().count(), 1)

    def test_role_deactivation_then_assignment_creation(self):
        self.run_concurrent(lambda: services.deactivate_role(role=self.role), self.create, expected="validation")
        self.assertEqual(UserRoleAssignment.objects.count(), 0)

    def test_assignment_creation_then_role_deactivation(self):
        self.run_concurrent(self.create, lambda: services.deactivate_role(role=self.role), expected="success")
        self.assertFalse(UserRoleAssignment.objects.get().is_active)

    def test_organization_assignment_deactivation_then_role_creation(self):
        self.run_concurrent(lambda: assignment_services.deactivate_assignment(assignment=self.scope),
                            self.create, expected="validation")
        self.assertEqual(UserRoleAssignment.objects.count(), 0)

    def test_role_creation_then_organization_assignment_deactivation(self):
        self.run_concurrent(self.create, lambda: assignment_services.deactivate_assignment(assignment=self.scope),
                            expected="success")
        self.assertFalse(UserRoleAssignment.objects.get().is_active)

    def test_company_cascade_then_role_creation(self):
        self.run_concurrent(lambda: organization_services.deactivate_company(company=self.company),
                            self.create, expected="validation")
        self.assertEqual(UserRoleAssignment.objects.count(), 0)

    def test_role_creation_then_company_cascade(self):
        self.run_concurrent(self.create, lambda: organization_services.deactivate_company(company=self.company),
                            expected="success")
        self.assertFalse(UserRoleAssignment.objects.get().is_active)

    def test_different_users_can_share_role_concurrently(self):
        self.run_concurrent(self.create, lambda: services.create_role_assignment(
            user=self.other_user, role=self.role, organization_assignment=self.other_scope),
            expected="success", should_block=False)
        self.assertEqual(UserRoleAssignment.objects.active().count(), 2)

    def test_role_creation_does_not_lock_company(self):
        self.run_concurrent(lambda: Company.objects.select_for_update().get(pk=self.company.pk),
                            self.create, expected="success", should_block=False)
        self.assertEqual(UserRoleAssignment.objects.active().count(), 1)
