from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse

from apps.organization import assignment_admin
from apps.organization import assignment_services
from apps.organization import services as organization_services
from apps.organization.test_lifecycle import make_tree
from . import services
from .models import Role, UserRoleAssignment


class AdministrationTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.admin_user = get_user_model().objects.create_superuser(username="operations-admin", password="test-only-password")
        cls.user = get_user_model().objects.create_user(username="operator")
        cls.company, cls.region, cls.center, cls.department = make_tree("CCARE")
        cls.scope = assignment_services.create_assignment(user=cls.user, company=cls.company,
            region=cls.region, service_center=cls.center, department=cls.department, is_primary=True)
        cls.role = Role.objects.create(code="SAMPLE", name="Sample")
        cls.permission = Permission.objects.get(content_type__app_label="organization", codename="view_company")
        cls.role.permissions.add(cls.permission)
        cls.role_assignment = services.create_role_assignment(user=cls.user, role=cls.role, organization_assignment=cls.scope)

    def setUp(self):
        self.client.force_login(self.admin_user)

    def action(self, model_name, action, pk, follow=False):
        return self.client.post(reverse(f"admin:{model_name}_changelist"),
            {"action": action, "_selected_action": [str(pk)]}, follow=follow)

    def test_assignment_reactivation_uses_service_without_restoring_primary_or_roles(self):
        assignment_services.deactivate_assignment(assignment=self.scope)
        with patch.object(assignment_admin, "reactivate_assignment", wraps=assignment_services.reactivate_assignment) as operation:
            response = self.action("organization_userorganizationassignment", "reactivate_selected", self.scope.pk)
        self.assertEqual(response.status_code, 302)
        operation.assert_called_once()
        self.scope.refresh_from_db()
        self.role_assignment.refresh_from_db()
        self.assertTrue(self.scope.is_active)
        self.assertFalse(self.scope.is_primary)
        self.assertFalse(self.role_assignment.is_active)

    def test_assignment_reactivation_reports_inactive_parent(self):
        organization_services.deactivate_company(company=self.company)
        response = self.action("organization_userorganizationassignment", "reactivate_selected", self.scope.pk, follow=True)
        self.assertContains(response, "requires an active organizational unit")
        self.scope.refresh_from_db()
        self.assertFalse(self.scope.is_active)

    def test_assignment_reactivation_rejects_active_duplicate(self):
        assignment_services.deactivate_assignment(assignment=self.scope)
        replacement = assignment_services.create_assignment(user=self.user, company=self.company,
            region=self.region, service_center=self.center, department=self.department)
        response = self.action("organization_userorganizationassignment", "reactivate_selected", self.scope.pk, follow=True)
        self.assertEqual(response.status_code, 200)
        self.scope.refresh_from_db()
        replacement.refresh_from_db()
        self.assertFalse(self.scope.is_active)
        self.assertTrue(replacement.is_active)

    def test_role_permission_change_uses_service(self):
        other = Permission.objects.get(content_type__app_label="organization", codename="view_region")
        with patch.object(services, "set_role_permissions", wraps=services.set_role_permissions) as operation:
            response = self.client.post(reverse("admin:access_role_change", args=[self.role.pk]), {
                "code": self.role.code, "name": self.role.name, "description": "Changed",
                "is_active": "on", "permissions": [other.pk], "_save": "Save",
            })
        self.assertEqual(response.status_code, 302)
        operation.assert_called_once()
        self.assertEqual(list(self.role.permissions.all()), [other])

    def test_role_permission_creation_uses_service(self):
        with patch.object(services, "set_role_permissions", wraps=services.set_role_permissions) as operation:
            response = self.client.post(reverse("admin:access_role_add"), {
                "code": "NEW", "name": "New", "is_active": "on",
                "permissions": [self.permission.pk], "_save": "Save",
            })
        self.assertEqual(response.status_code, 302)
        operation.assert_called_once()
        self.assertEqual(list(Role.objects.get(code="NEW").permissions.all()), [self.permission])

    def test_role_change_rolls_back_if_permission_service_fails(self):
        with patch.object(services, "set_role_permissions", side_effect=RuntimeError("simulated failure")):
            with self.assertRaises(RuntimeError):
                self.client.post(reverse("admin:access_role_change", args=[self.role.pk]), {
                    "code": self.role.code, "name": "Must roll back", "is_active": "on",
                    "permissions": [], "_save": "Save",
                })
        self.role.refresh_from_db()
        self.assertEqual(self.role.name, "Sample")
        self.assertEqual(list(self.role.permissions.all()), [self.permission])

    def test_organization_admin_cascade_reaches_roles(self):
        response = self.action("organization_company", "deactivate_selected", self.company.pk)
        self.assertEqual(response.status_code, 302)
        for obj in [self.company, self.region, self.center, self.department, self.scope, self.role_assignment]:
            obj.refresh_from_db()
            self.assertFalse(obj.is_active)
        self.assertFalse(self.scope.is_primary)

    def test_role_admin_deactivation_preserves_scope(self):
        self.action("access_role", "deactivate_selected", self.role.pk)
        self.role_assignment.refresh_from_db()
        self.scope.refresh_from_db()
        self.assertFalse(self.role_assignment.is_active)
        self.assertTrue(self.scope.is_active)
        self.assertTrue(self.scope.is_primary)

    def test_view_only_staff_cannot_invoke_assignment_action(self):
        assignment_services.deactivate_assignment(assignment=self.scope)
        staff = get_user_model().objects.create_user(username="readonly-admin", is_staff=True)
        staff.user_permissions.add(Permission.objects.get(content_type__app_label="organization", codename="view_userorganizationassignment"))
        self.client.force_login(staff)
        with patch.object(assignment_admin, "reactivate_assignment") as operation:
            self.action("organization_userorganizationassignment", "reactivate_selected", self.scope.pk)
        operation.assert_not_called()
        self.scope.refresh_from_db()
        self.assertFalse(self.scope.is_active)

    def test_view_only_staff_cannot_edit_role_permissions(self):
        staff = get_user_model().objects.create_user(username="role-reader", is_staff=True)
        staff.user_permissions.add(Permission.objects.get(content_type__app_label="access", codename="view_role"))
        self.client.force_login(staff)
        with patch.object(services, "set_role_permissions") as operation:
            response = self.client.post(reverse("admin:access_role_change", args=[self.role.pk]), {
                "code": self.role.code, "name": "Forbidden", "is_active": "on", "permissions": [],
            })
        self.assertEqual(response.status_code, 403)
        operation.assert_not_called()
        self.assertEqual(list(self.role.permissions.all()), [self.permission])

    def test_native_user_admin_remains_available(self):
        response = self.client.get(reverse("admin:accounts_user_change", args=[self.user.pk]))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "operator")
