"""Tests for policy simulation."""

from pyauthz import (
    Effect,
    PyAuthz,
    Resource,
    Role,
    SimulationResult,
    Subject,
    simulate,
)
from pyauthz.models.policy import Policy


class TestSimulationResult:
    def test_explain_allowed(self):
        result = SimulationResult(
            subject=Subject(id="alice", roles=["admin"]),
            action="documents:read",
            resource=Resource(id="doc-1", type="documents"),
            decision_allowed=True,
            reasoning=["Auto-default: role 'admin' matched"],
            matched_policies=["__auto_default__"],
        )
        explanation = result.explain()
        assert "ALLOWED" in explanation
        assert "alice" in explanation
        assert "documents:read" in explanation

    def test_explain_denied(self):
        result = SimulationResult(
            subject=Subject(id="bob", roles=["viewer"]),
            action="documents:write",
            resource=Resource(id="doc-1", type="documents"),
            decision_allowed=False,
            reasoning=["no matching policy"],
            matched_policies=[],
        )
        explanation = result.explain()
        assert "DENIED" in explanation


class TestSimulate:
    def test_basic_simulation(self):
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))

        admin = Subject(id="alice", roles=["admin"])
        viewer = Subject(id="bob", roles=["viewer"])
        doc = Resource(id="doc-1", type="documents")

        results = simulate(
            pyauthz=rp,
            scenarios=[
                (admin, "documents:read", doc),
                (viewer, "documents:read", doc),
            ],
        )
        assert len(results) == 2
        assert results[0].decision_allowed is True
        assert results[1].decision_allowed is False

    def test_simulate_with_added_policy(self):
        rp = PyAuthz()
        rp.add_role(Role(name="editor", permissions=["documents:write"]))

        editor = Subject(id="alice", roles=["editor"])
        doc = Resource(id="doc-1", type="documents")

        # Without the policy, editor can write
        results_before = simulate(
            pyauthz=rp,
            scenarios=[(editor, "documents:write", doc)],
        )
        assert results_before[0].decision_allowed is True

        # Simulate adding a deny policy
        results_after = simulate(
            pyauthz=rp,
            scenarios=[(editor, "documents:write", doc)],
            add_policies=[
                Policy(
                    name="deny-write",
                    effect=Effect.DENY,
                    roles=["editor"],
                    permissions=["documents:write"],
                )
            ],
        )
        assert results_after[0].decision_allowed is False

    def test_simulate_with_removed_policy(self):
        rp = PyAuthz()
        rp.add_role(Role(name="editor", permissions=["documents:write"]))
        rp.add_policy(
            Policy(
                name="deny-write",
                effect=Effect.DENY,
                roles=["editor"],
                permissions=["documents:write"],
            )
        )

        editor = Subject(id="alice", roles=["editor"])
        doc = Resource(id="doc-1", type="documents")

        # With deny policy, editor cannot write
        results_with = simulate(
            pyauthz=rp,
            scenarios=[(editor, "documents:write", doc)],
        )
        assert results_with[0].decision_allowed is False

        # Simulate removing the deny policy
        results_without = simulate(
            pyauthz=rp,
            scenarios=[(editor, "documents:write", doc)],
            remove_policies=["deny-write"],
        )
        assert results_without[0].decision_allowed is True

    def test_simulate_with_added_role(self):
        rp = PyAuthz()
        rp.add_role(Role(name="viewer", permissions=["documents:read"]))

        user = Subject(id="alice", roles=["superadmin"])
        doc = Resource(id="doc-1", type="documents")

        # superadmin role doesn't exist
        results_before = simulate(
            pyauthz=rp,
            scenarios=[(user, "documents:write", doc)],
        )
        assert results_before[0].decision_allowed is False

        # Add the superadmin role in simulation
        results_after = simulate(
            pyauthz=rp,
            scenarios=[(user, "documents:write", doc)],
            add_roles=[Role(name="superadmin", permissions=["*:*"])],
        )
        assert results_after[0].decision_allowed is True

    def test_simulate_with_removed_role(self):
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))

        admin = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")

        # Admin can do everything
        results_with = simulate(
            pyauthz=rp,
            scenarios=[(admin, "documents:write", doc)],
        )
        assert results_with[0].decision_allowed is True

        # Remove admin role in simulation
        results_without = simulate(
            pyauthz=rp,
            scenarios=[(admin, "documents:write", doc)],
            remove_roles=["admin"],
        )
        assert results_without[0].decision_allowed is False

    def test_simulate_does_not_modify_original(self):
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))

        admin = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")

        simulate(
            pyauthz=rp,
            scenarios=[(admin, "documents:write", doc)],
            add_roles=[Role(name="superadmin", permissions=["*:*"])],
            add_policies=[
                Policy(
                    name="test-policy",
                    effect=Effect.ALLOW,
                    permissions=["*:*"],
                )
            ],
        )

        # Original engine should be unmodified
        roles = rp._storage.get_roles()
        assert "superadmin" not in roles
        policies = rp._storage.get_policies()
        assert not any(p.name == "test-policy" for p in policies)
