"""Mandatory test 3 (module gating) plus dependency control and the module audit trail.
Mandatory test 10 (data survives disabling) lives in test_attendance_tenancy.py, where attendance
data is tied to an organization."""
from apps.audit.models import AuditLog
from apps.modules import services as modules
from apps.modules.models import Module, ModuleDependency
from domain.exceptions import ModuleDependencyError, ModuleNotAvailableError
from tests.base import CoreTestCase


class ModuleGatingTests(CoreTestCase):
    def test_3_disabled_module_is_unusable_for_everyone_in_the_organization(self):
        self.assertEqual(self.api(self.employee_a, self.org_a).get("/probe/").status_code, 200)
        modules.disable_module(actor=self.root, organization=self.org_a, module_code="attendance")

        for user in (self.employee_a, self.manager_a):
            response = self.api(user, self.org_a).get("/probe/")
            self.assertEqual(response.status_code, 403)
            self.assertEqual(response.data["code"], "module_disabled")

    def test_disabled_module_disappears_from_permissions_and_menu(self):
        before = self.api(self.manager_a, self.org_a).get("/api/v1/me/").data["context"]
        self.assertIn("attendance.view", before["permissions"])
        self.assertIn("attendance", before["modules"])
        self.assertTrue(any(i["module"] == "attendance" for i in before["navigation"]))

        modules.disable_module(actor=self.root, organization=self.org_a, module_code="attendance")
        after = self.api(self.manager_a, self.org_a).get("/api/v1/me/").data["context"]
        self.assertNotIn("attendance.view", after["permissions"])
        self.assertNotIn("attendance", after["modules"])
        self.assertFalse(any(i["module"] == "attendance" for i in after["navigation"]))
        self.assertIn("members.view", after["permissions"])  # core stays

    def test_permission_catalog_hides_disabled_modules_and_platform_only_entries(self):
        codes = {p["code"] for p in self.api(self.manager_a, self.org_a).get("/api/v1/permissions/").data}
        self.assertIn("attendance.view", codes)
        self.assertNotIn("modules.manage", codes)
        self.assertNotIn("organizations.create", codes)
        modules.disable_module(actor=self.root, organization=self.org_a, module_code="attendance")
        codes = {p["code"] for p in self.api(self.manager_a, self.org_a).get("/api/v1/permissions/").data}
        self.assertNotIn("attendance.view", codes)

    def test_only_platform_staff_can_toggle_modules_via_api(self):
        url = f"/api/v1/organizations/{self.org_a.pk}/modules/attendance/disable/"
        self.assertEqual(self.api(self.manager_a).post(url).status_code, 403)
        self.assertEqual(self.api(self.manager_a, self.org_a).post(url).status_code, 403)
        response = self.api(self.root).post(url)
        self.assertEqual(response.status_code, 200)
        state = {m["code"]: m["enabled"] for m in response.data}
        self.assertFalse(state["attendance"])
        self.assertTrue(state["core"])

    def test_core_cannot_be_disabled_and_planned_modules_cannot_be_enabled(self):
        with self.assertRaises(ModuleNotAvailableError):
            modules.disable_module(actor=self.root, organization=self.org_a, module_code="core")
        with self.assertRaises(ModuleNotAvailableError):
            modules.enable_module(actor=self.root, organization=self.org_a, module_code="vendors")
        response = self.api(self.root).post(f"/api/v1/organizations/{self.org_a.pk}/modules/vendors/enable/")
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.data["code"], "module_not_available")


class ModuleDependencyTests(CoreTestCase):
    def setUp(self):
        super().setUp()
        Module.objects.create(code="reports_plus", title_fa="گزارش‌های پیشرفته", status="available")
        ModuleDependency.objects.create(module_id="reports_plus", depends_on_id="attendance")

    def test_enable_requires_dependencies(self):
        modules.disable_module(actor=self.root, organization=self.org_a, module_code="attendance")
        with self.assertRaises(ModuleDependencyError) as raised:
            modules.enable_module(actor=self.root, organization=self.org_a, module_code="reports_plus")
        self.assertEqual(raised.exception.modules, ["attendance"])

    def test_disable_is_blocked_while_dependents_are_enabled(self):
        modules.enable_module(actor=self.root, organization=self.org_a, module_code="reports_plus")
        with self.assertRaises(ModuleDependencyError) as raised:
            modules.disable_module(actor=self.root, organization=self.org_a, module_code="attendance")
        self.assertEqual(raised.exception.modules, ["reports_plus"])
        modules.disable_module(actor=self.root, organization=self.org_a, module_code="reports_plus")
        modules.disable_module(actor=self.root, organization=self.org_a, module_code="attendance")

    def test_dependency_error_is_reported_through_the_api(self):
        modules.enable_module(actor=self.root, organization=self.org_a, module_code="reports_plus")
        response = self.api(self.root).post(f"/api/v1/organizations/{self.org_a.pk}/modules/attendance/disable/")
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.data["code"], "module_dependency")
        self.assertEqual(response.data["modules"], ["reports_plus"])

    def test_dependencies_are_visible_in_the_module_list(self):
        listing = self.api(self.root).get(f"/api/v1/organizations/{self.org_a.pk}/modules/").data
        by_code = {m["code"]: m for m in listing}
        self.assertEqual(by_code["reports_plus"]["depends_on"], ["attendance"])
        self.assertEqual(by_code["payroll"]["depends_on"], ["attendance", "personnel"])

    def test_other_organizations_are_unaffected_by_a_toggle(self):
        modules.enable_module(actor=self.root, organization=self.org_a, module_code="reports_plus")
        self.assertNotIn("reports_plus", modules.enabled_module_codes(self.org_b))


class ModuleAuditTrailTests(CoreTestCase):
    def test_toggle_history_is_audited(self):
        modules.disable_module(actor=self.root, organization=self.org_a, module_code="attendance")
        modules.enable_module(actor=self.root, organization=self.org_a, module_code="attendance")
        rows = AuditLog.objects.filter(organization=self.org_a, entity_type="module", entity_id="attendance")
        actions = list(rows.order_by("id").values_list("action", flat=True))
        self.assertEqual(actions[-2:], ["module.disabled", "module.enabled"])
        history = self.api(self.manager_a, self.org_a).get("/api/v1/audit-logs/?entity_type=module").data
        self.assertGreaterEqual(history["count"], 3)
        self.assertEqual(history["results"][0]["actor"]["username"], "root")
