"""Pure unit tests for the access-policy engine (no database)."""
from django.test import SimpleTestCase

from domain.access_policy import OverrideRule, RoleGrant, compute_effective_access
from domain.value_objects import normalize_iran_mobile

PERMS = {
    "attendance.view": "attendance",
    "attendance.approve": "attendance",
    "members.view": "core",
    "payroll.view": "payroll",
}


def compute(**kwargs):
    base = dict(
        organization_active=True,
        membership_active=True,
        enabled_modules={"attendance"},
        permission_modules=PERMS,
        role_grants=[],
        overrides=[],
    )
    base.update(kwargs)
    return compute_effective_access(**base)


class DenyByDefaultTests(SimpleTestCase):
    def test_nothing_granted_means_nothing_allowed(self):
        access = compute()
        self.assertEqual(access.permissions, {})
        self.assertFalse(access.has("attendance.view"))

    def test_unknown_permission_is_dropped(self):
        access = compute(role_grants=[RoleGrant("ghost.permission", "organization")])
        self.assertFalse(access.has("ghost.permission"))

    def test_inactive_organization_or_membership_yields_nothing(self):
        grants = [RoleGrant("attendance.view", "organization")]
        self.assertEqual(compute(role_grants=grants, organization_active=False).permissions, {})
        self.assertEqual(compute(role_grants=grants, membership_active=False).permissions, {})


class ModuleLayerTests(SimpleTestCase):
    def test_permissions_of_disabled_modules_vanish(self):
        access = compute(role_grants=[RoleGrant("payroll.view", "organization"), RoleGrant("attendance.view", "self")])
        self.assertFalse(access.has("payroll.view"))
        self.assertTrue(access.has("attendance.view"))

    def test_core_permissions_never_need_enablement(self):
        access = compute(role_grants=[RoleGrant("members.view", "organization")], enabled_modules=set())
        self.assertTrue(access.has("members.view"))


class RoleAndScopeTests(SimpleTestCase):
    def test_widest_scope_wins_across_roles(self):
        access = compute(role_grants=[RoleGrant("attendance.view", "self"), RoleGrant("attendance.view", "organization")])
        self.assertEqual(access.scope_of("attendance.view"), "organization")
        self.assertTrue(access.has("attendance.view", "self"))
        self.assertTrue(access.has("attendance.view", "organization"))

    def test_scope_is_a_minimum_requirement(self):
        access = compute(role_grants=[RoleGrant("attendance.view", "self")])
        self.assertTrue(access.has("attendance.view"))
        self.assertFalse(access.has("attendance.view", "organization"))

    def test_delegation_requires_flag_and_sufficient_scope(self):
        access = compute(role_grants=[RoleGrant("attendance.view", "organization", can_delegate=True)])
        self.assertTrue(access.can_delegate("attendance.view", "organization"))
        self.assertTrue(access.can_delegate("attendance.view", "self"))
        self.assertFalse(access.can_delegate("attendance.view", "all"))
        plain = compute(role_grants=[RoleGrant("attendance.view", "organization")])
        self.assertFalse(plain.can_delegate("attendance.view", "self"))


class OverrideTests(SimpleTestCase):
    def test_allow_override_adds_a_permission(self):
        access = compute(overrides=[OverrideRule("attendance.approve", "allow", "organization")])
        self.assertTrue(access.has("attendance.approve", "organization"))

    def test_explicit_deny_beats_role_grant(self):
        access = compute(
            role_grants=[RoleGrant("attendance.view", "organization", True)],
            overrides=[OverrideRule("attendance.view", "deny")],
        )
        self.assertFalse(access.has("attendance.view"))

    def test_explicit_deny_beats_allow_override(self):
        access = compute(
            overrides=[
                OverrideRule("attendance.view", "allow", "organization"),
                OverrideRule("attendance.view", "deny"),
            ]
        )
        self.assertFalse(access.has("attendance.view"))

    def test_deny_wins_regardless_of_order(self):
        rules = [OverrideRule("attendance.view", "deny"), OverrideRule("attendance.view", "allow", "organization")]
        self.assertFalse(compute(overrides=rules).has("attendance.view"))

    def test_blocked_permissions_cannot_be_unblocked_by_allow(self):
        access = compute(
            overrides=[OverrideRule("attendance.view", "allow", "organization")],
            blocked_permissions={"attendance.view"},
        )
        self.assertFalse(access.has("attendance.view"))

    def test_unknown_effect_is_rejected(self):
        with self.assertRaises(ValueError):
            compute(overrides=[OverrideRule("attendance.view", "maybe")])


class MergeTests(SimpleTestCase):
    def test_merge_is_additive_with_widest_scope_and_ored_delegation(self):
        from domain.access_policy import merge_effective_access

        own = compute(role_grants=[RoleGrant("attendance.view", "self"), RoleGrant("members.view", "organization")])
        staff = compute(role_grants=[RoleGrant("attendance.view", "all", True), RoleGrant("attendance.approve", "all")])
        merged = merge_effective_access(own, staff)
        self.assertEqual(merged.scope_of("attendance.view"), "all")
        self.assertTrue(merged.can_delegate("attendance.view", "all"))
        self.assertTrue(merged.has("members.view"))
        self.assertTrue(merged.has("attendance.approve"))

    def test_a_deny_only_removes_the_permission_from_its_own_source(self):
        from domain.access_policy import merge_effective_access

        own = compute(
            role_grants=[RoleGrant("attendance.view", "organization")],
            overrides=[OverrideRule("attendance.view", "deny")],
        )
        staff = compute(role_grants=[RoleGrant("attendance.view", "all")])
        self.assertFalse(own.has("attendance.view"))
        self.assertTrue(merge_effective_access(own, staff).has("attendance.view"))

    def test_merging_nothing_gives_nothing(self):
        from domain.access_policy import merge_effective_access

        self.assertEqual(merge_effective_access(compute(), compute()).permissions, {})


class MobileNormalizationTests(SimpleTestCase):
    def test_accepted_formats(self):
        for raw in ("09123456789", "+98 912 345 6789", "0098-912-345-6789", "9123456789", "۰۹۱۲۳۴۵۶۷۸۹", "989123456789"):
            self.assertEqual(normalize_iran_mobile(raw), "09123456789", raw)

    def test_rejected_formats(self):
        for raw in ("", None, "12345", "0912345678", "08123456789", "abc"):
            self.assertIsNone(normalize_iran_mobile(raw), repr(raw))
