"""Policy simulation — "what would happen if..." analysis.

Enables testing policy changes before applying them. Run authorization
checks against a hypothetical set of policies/roles without affecting
the live engine.

Usage:
    from pyauthz.core.simulation import simulate

    # What if we add this new policy?
    results = simulate(
        pyauthz=rp,
        scenarios=[
            (user, "documents:write", doc),
            (admin, "pii:read", record),
        ],
        add_policies=[new_policy],
        remove_policies=["old-policy-name"],
    )
    for scenario, decision in results:
        print(decision.explain())
"""

from __future__ import annotations

from typing import Any

from pyauthz.core.engine import PyAuthz
from pyauthz.models.policy import Policy
from pyauthz.models.resource import Resource
from pyauthz.models.role import Role
from pyauthz.models.subject import Subject


class SimulationResult:
    """Result of a single simulation scenario.

    Attributes:
        subject: The subject in the scenario.
        action: The action checked.
        resource: The resource checked.
        decision_allowed: Whether the simulated decision allows access.
        reasoning: The reasoning chain from the decision.
        matched_policies: Which policies contributed to the decision.
    """

    def __init__(
        self,
        subject: Subject,
        action: str,
        resource: Resource,
        decision_allowed: bool,
        reasoning: list[str],
        matched_policies: list[str],
    ) -> None:
        self.subject = subject
        self.action = action
        self.resource = resource
        self.decision_allowed = decision_allowed
        self.reasoning = reasoning
        self.matched_policies = matched_policies

    def explain(self) -> str:
        """Human-readable explanation."""
        status = "ALLOWED" if self.decision_allowed else "DENIED"
        reasons = "; ".join(self.reasoning) or "no matching policy"
        return (
            f"{status}: {self.subject.id} -> {self.action} "
            f"on {self.resource.type}/{self.resource.id} ({reasons})"
        )


def simulate(
    pyauthz: PyAuthz,
    scenarios: list[tuple[Subject, str, Resource]],
    *,
    add_policies: list[Policy] | None = None,
    remove_policies: list[str] | None = None,
    add_roles: list[Role] | None = None,
    remove_roles: list[str] | None = None,
    env: dict[str, Any] | None = None,
) -> list[SimulationResult]:
    """Run authorization scenarios against a simulated policy configuration.

    Creates a temporary PyAuthz engine with the modified policies/roles
    and evaluates all scenarios against it. The original engine is not modified.

    Args:
        pyauthz: The live PyAuthz engine (policies/roles are cloned from here).
        scenarios: List of (Subject, action, Resource) tuples to test.
        add_policies: Policies to add to the simulation.
        remove_policies: Policy names to remove from the simulation.
        add_roles: Roles to add to the simulation.
        remove_roles: Role names to remove from the simulation.
        env: Environment context for the simulation.

    Returns:
        List of SimulationResult objects, one per scenario.
    """
    # Clone current state
    sim = PyAuthz(
        default_effect=pyauthz._default_effect,
        combiner=pyauthz._combiner_name,
    )

    # Copy existing roles
    existing_roles = pyauthz._storage.get_roles()
    remove_role_set = set(remove_roles or [])
    for name, role in existing_roles.items():
        if name not in remove_role_set:
            sim.add_role(role)

    # Add new roles
    for role in add_roles or []:
        if not sim._storage.get_roles().get(role.name):
            sim.add_role(role)

    # Copy existing policies
    existing_policies = pyauthz._storage.get_policies()
    remove_policy_set = set(remove_policies or [])
    for policy in existing_policies:
        if policy.name not in remove_policy_set:
            sim.add_policy(policy)

    # Add new policies
    for policy in add_policies or []:
        sim.add_policy(policy)

    # Copy environment
    if pyauthz._default_environment:
        sim.set_environment(**pyauthz._default_environment)

    # Run scenarios
    results: list[SimulationResult] = []
    merged_env = env or {}

    for subject, action, resource in scenarios:
        decision = sim._evaluate(subject, action, resource, merged_env)
        results.append(
            SimulationResult(
                subject=subject,
                action=action,
                resource=resource,
                decision_allowed=decision.allowed,
                reasoning=decision.reasoning,
                matched_policies=decision.matched_policies,
            )
        )

    return results
