"""Explainable denials — generate actionable suggestions for denied access.

Analyzes a denied decision and generates suggestions for what would grant access:
- Which roles would have the required permission
- Whether owning the resource would help
- Which conditions failed and what values would satisfy them

Usage:
    from pyauthz.core.explain import explain_denial

    decision = rp._evaluate(user, "documents:write", doc, {})
    if not decision.allowed:
        suggestions = explain_denial(rp, decision)
        for suggestion in suggestions:
            print(suggestion)
"""

from __future__ import annotations

from pyauthz.core.decision import Decision
from pyauthz.core.engine import PyAuthz
from pyauthz.models.permission import Permission


def explain_denial(pyauthz: PyAuthz, decision: Decision) -> list[str]:
    """Generate actionable suggestions for a denied authorization decision.

    Analyzes the policy configuration to determine what changes would allow
    the denied request to succeed.

    Args:
        pyauthz: The PyAuthz engine that produced the decision.
        decision: A denied Decision object.

    Returns:
        List of suggestion strings explaining what would grant access.
    """
    if decision.allowed:
        return ["Access is already allowed."]

    suggestions: list[str] = []
    request = decision.request
    action = request.action
    subject = request.subject

    # Parse action
    if ":" in action:
        req_resource_type, req_action_verb = action.split(":", 1)
    else:
        req_resource_type = request.resource.type
        req_action_verb = action

    roles = pyauthz._storage.get_roles()
    policies = pyauthz._storage.get_policies()

    # Find roles that have the required permission
    matching_roles: list[str] = []
    for role_name, role in roles.items():
        for perm_str in role.permissions:
            try:
                perm = Permission.from_string(perm_str)
            except ValueError:
                continue
            if perm.matches(req_resource_type, req_action_verb):
                matching_roles.append(role_name)
                break

    # Suggest roles the subject doesn't have
    missing_roles = [r for r in matching_roles if r not in subject.roles]
    if missing_roles:
        role_list = ", ".join(f"'{r}'" for r in missing_roles)
        suggestions.append(
            f"The following role(s) have the '{action}' permission: {role_list}. "
            f"Adding one of these roles to '{subject.id}' would grant access."
        )

    # Check if there are allow policies with conditions that could match
    for policy in policies:
        if policy.effect.value != "allow":
            continue

        # Check if this policy's permissions match the request
        if policy.permissions:
            perm_match = False
            for perm_str in policy.permissions:
                try:
                    perm = Permission.from_string(perm_str)
                except ValueError:
                    continue
                if perm.matches(req_resource_type, req_action_verb):
                    perm_match = True
                    break
            if not perm_match:
                continue

        # Check if the subject has the required role for this policy
        if policy.roles and not any(r in subject.roles for r in policy.roles):
            continue

        # Subject has the role and permission matches, but conditions may have failed
        if policy.conditions:
            for condition in policy.conditions:
                if "owner" in condition.field.lower():
                    suggestions.append(
                        f"Policy '{policy.name}' requires {condition.field} {condition.op} "
                        f"{condition.value}. Being the resource owner may grant access."
                    )
                elif condition.field.startswith("environment."):
                    env_key = condition.field.replace("environment.", "")
                    suggestions.append(
                        f"Policy '{policy.name}' requires environment.{env_key} "
                        f"{condition.op} {condition.value}."
                    )

    if not suggestions:
        suggestions.append(
            f"No policy grants '{action}' permission. "
            f"An administrator needs to create an allow policy for this action."
        )

    return suggestions
