"""Property-based tests using Hypothesis.

These tests verify invariants that must hold for all possible inputs,
not just specific examples.
"""

from hypothesis import given, settings
from hypothesis import strategies as st

from pyauthz import Effect, PyAuthz, Resource, Role, Subject
from pyauthz.models.permission import Permission

# --- Strategies ---

role_names = st.sampled_from(["admin", "editor", "viewer", "analyst", "intern"])
resource_types = st.sampled_from(["documents", "users", "pii", "endpoints", "reports"])
actions = st.sampled_from(["read", "write", "delete", "create", "update"])
subject_ids = st.text(
    min_size=1, max_size=20, alphabet=st.characters(whitelist_categories=("L", "N"))
)


# --- Permission matching properties ---


class TestPermissionProperties:
    @given(rt=resource_types, act=actions)
    def test_wildcard_matches_everything(self, rt: str, act: str):
        """The '*:*' permission must match any resource_type:action pair."""
        perm = Permission.from_string("*:*")
        assert perm.matches(rt, act) is True

    @given(rt=resource_types, act=actions)
    def test_exact_match_is_reflexive(self, rt: str, act: str):
        """A permission always matches its own resource type and action."""
        perm = Permission(resource_type=rt, action=act)
        assert perm.matches(rt, act) is True

    @given(rt=resource_types, act=actions)
    def test_action_wildcard_matches_any_action(self, rt: str, act: str):
        """'resource_type:*' matches any action on that resource type."""
        perm = Permission(resource_type=rt, action="*")
        assert perm.matches(rt, act) is True

    @given(rt=resource_types, act=actions)
    def test_resource_wildcard_matches_any_resource(self, rt: str, act: str):
        """'*:action' matches any resource type for that action."""
        perm = Permission(resource_type="*", action=act)
        assert perm.matches(rt, act) is True


# --- Engine invariants ---


class TestEngineProperties:
    @given(
        sid=subject_ids,
        role_name=role_names,
        rt=resource_types,
        act=actions,
    )
    @settings(max_examples=50)
    def test_deny_by_default_when_no_roles(self, sid: str, role_name: str, rt: str, act: str):
        """A subject with no roles should be denied by default (no policies, no matching roles)."""
        rp = PyAuthz()
        rp.add_role(Role(name=role_name, permissions=[f"{rt}:{act}"]))
        user = Subject(id=sid, roles=[])
        res = Resource(id="r1", type=rt)
        assert rp.is_allowed(user, act, res) is False

    @given(
        sid=subject_ids,
        rt=resource_types,
        act=actions,
    )
    @settings(max_examples=50)
    def test_admin_wildcard_always_allows(self, sid: str, rt: str, act: str):
        """A subject with a '*:*' role is allowed to do anything (auto-default mode)."""
        rp = PyAuthz()
        rp.add_role(Role(name="superadmin", permissions=["*:*"]))
        user = Subject(id=sid, roles=["superadmin"])
        res = Resource(id="r1", type=rt)
        assert rp.is_allowed(user, act, res) is True

    @given(
        sid=subject_ids,
        rt=resource_types,
        act=actions,
    )
    @settings(max_examples=50)
    def test_deny_policy_always_wins_in_deny_override(self, sid: str, rt: str, act: str):
        """In deny-override mode, a DENY policy beats any ALLOW policy."""
        rp = PyAuthz(combiner="deny-override")
        from pyauthz import Policy

        rp.add_policy(Policy(name="allow-all", effect=Effect.ALLOW, permissions=["*:*"]))
        rp.add_policy(Policy(name="deny-all", effect=Effect.DENY, permissions=["*:*"]))
        user = Subject(id=sid, roles=[])
        res = Resource(id="r1", type=rt)
        assert rp.is_allowed(user, act, res) is False

    @given(
        sid=subject_ids,
        rt=resource_types,
        act=actions,
    )
    @settings(max_examples=50)
    def test_is_allowed_never_raises(self, sid: str, rt: str, act: str):
        """is_allowed() must never raise an exception — always returns a bool."""
        rp = PyAuthz()
        user = Subject(id=sid, roles=["nonexistent"])
        res = Resource(id="r1", type=rt)
        result = rp.is_allowed(user, act, res)
        assert isinstance(result, bool)

    @given(
        sid=subject_ids,
        rt=resource_types,
        act=actions,
    )
    @settings(max_examples=50)
    def test_action_shorthand_equivalent(self, sid: str, rt: str, act: str):
        """Shorthand action and full action must produce the same result."""
        rp = PyAuthz()
        rp.add_role(Role(name="r", permissions=[f"{rt}:{act}"]))
        user = Subject(id=sid, roles=["r"])
        res = Resource(id="r1", type=rt)
        short = rp.is_allowed(user, act, res)
        full = rp.is_allowed(user, f"{rt}:{act}", res)
        assert short == full
