"""NotAction/NotResource check - detects dangerous Not* patterns in IAM policies.

NotAction and NotResource are powerful IAM features that can be misused to
grant overly broad permissions. This check detects patterns that violate
the principle of least privilege.
"""

from typing import ClassVar

from iam_validator.checks.utils import format_list_with_backticks
from iam_validator.core.aws_service import AWSServiceFetcher
from iam_validator.core.check_registry import CheckConfig, PolicyCheck
from iam_validator.core.models import Statement, ValidationIssue


def _extract_service_prefixes(actions: list[str]) -> set[str]:
    """Extract unique service prefixes from a list of actions.

    Args:
        actions: List of IAM actions (e.g., ["iam:*", "s3:GetObject"])

    Returns:
        Set of service prefixes (e.g., {"iam", "s3"})
    """
    prefixes: set[str] = set()
    for action in actions:
        if ":" in action:
            prefixes.add(action.split(":")[0])
    return prefixes


def _build_implicit_grant_note(excluded_services: set[str], not_actions: list[str]) -> str:
    """Build a note explaining what services/actions are implicitly granted.

    Args:
        excluded_services: Service prefixes that are excluded
        not_actions: The NotAction list

    Returns:
        Human-readable note about implicitly granted access
    """
    if not excluded_services:
        return ""

    # Check if any entries are full service wildcards (e.g., "iam:*")
    wildcard_services = {a.split(":")[0] for a in not_actions if a.endswith(":*")}

    if wildcard_services:
        other_note = (
            f"All AWS services EXCEPT {format_list_with_backticks(sorted(wildcard_services), 5)} "
            f"are implicitly granted access."
        )
    else:
        other_note = (
            f"Only specific actions in {format_list_with_backticks(sorted(excluded_services), 3)} "
            f"are excluded; all other actions in these and all other services are granted."
        )

    return other_note


class NotActionNotResourceCheck(PolicyCheck):
    """Checks for dangerous NotAction/NotResource patterns.

    Patterns detected:
    1. NotAction with Effect: Allow - grants everything EXCEPT listed actions
    2. NotResource with wildcards - grants access to all resources except listed
    3. NotAction in Allow without strict conditions - missing safeguards

    These patterns are particularly dangerous because they grant permissions
    by exclusion rather than explicit inclusion, making it easy to accidentally
    grant more access than intended.
    """

    check_id: ClassVar[str] = "not_action_not_resource"
    description: ClassVar[str] = "Checks for dangerous NotAction/NotResource patterns"
    default_severity: ClassVar[str] = "high"

    async def execute(
        self,
        statement: Statement,
        statement_idx: int,
        fetcher: AWSServiceFetcher,
        config: CheckConfig,
    ) -> list[ValidationIssue]:
        """Execute NotAction/NotResource check on a statement."""
        del fetcher  # Unused

        issues = []

        not_actions = statement.get_not_actions()
        not_resources = statement.get_not_resources()
        effect = statement.effect

        # When both NotAction AND NotResource are present with Allow,
        # only emit the combined critical finding (Check 3) to avoid noise.
        has_combined = bool(not_actions and not_resources and effect == "Allow")

        # Check 1: NotAction with Effect: Allow
        # This grants ALL actions EXCEPT the listed ones - extremely dangerous
        if not_actions and effect == "Allow" and not has_combined:
            has_conditions = bool(statement.condition)

            # Determine which service prefixes are excluded to explain what's implicitly granted
            excluded_services = _extract_service_prefixes(not_actions)
            implicit_grant_note = _build_implicit_grant_note(excluded_services, not_actions)

            if has_conditions:
                # NotAction with conditions is still risky but less severe
                issues.append(
                    ValidationIssue(
                        severity="medium",
                        statement_sid=statement.sid,
                        statement_index=statement_idx,
                        issue_type="not_action_allow",
                        message=(
                            "Statement uses `NotAction` with `Allow` effect. "
                            "This grants **ALL** actions except the listed ones. "
                            "While conditions are present, this pattern is still risky."
                            + (f" {implicit_grant_note}" if implicit_grant_note else "")
                        ),
                        suggestion=(
                            "Consider using explicit `Action` lists instead of `NotAction`. "
                            "If `NotAction` is intentional (e.g., to deny all except specific actions "
                            "with a `Deny` effect), ensure the conditions are comprehensive enough "
                            "to prevent unintended access. Add `aws:SourceIp`, `aws:PrincipalArn`, "
                            "or `aws:MultiFactorAuthPresent` conditions as additional safeguards."
                        ),
                        example='{\n  "Effect": "Allow",\n  "Action": ["s3:GetObject", "s3:ListBucket"],\n  "Resource": "*"\n}',
                        line_number=statement.line_number,
                        field_name="action",
                        action=format_list_with_backticks(not_actions, 3),
                    )
                )
            else:
                # NotAction Allow without conditions is critical
                issues.append(
                    ValidationIssue(
                        severity=self.get_severity(config),
                        statement_sid=statement.sid,
                        statement_index=statement_idx,
                        issue_type="not_action_allow_no_condition",
                        message=(
                            "Statement uses `NotAction` with `Allow` effect and NO conditions. "
                            f"This grants **ALL** AWS actions except: {format_list_with_backticks(not_actions, 5)}. "
                            "This is equivalent to granting near-administrator access."
                            + (f" {implicit_grant_note}" if implicit_grant_note else "")
                        ),
                        suggestion=(
                            "Replace `NotAction` with an explicit `Action` list to follow the "
                            "principle of least privilege. If you need to allow most actions "
                            "except a few, consider using a `Deny` statement with explicit "
                            "`Action` list instead, combined with a separate `Allow` statement. "
                            "If `NotAction` is required, add strict conditions like "
                            "`aws:SourceIp`, `aws:PrincipalArn`, or `aws:MultiFactorAuthPresent`."
                        ),
                        example='{\n  "Effect": "Deny",\n  "Action": ["iam:*", "organizations:*"],\n  "Resource": "*"\n}',
                        line_number=statement.line_number,
                        field_name="action",
                        action=format_list_with_backticks(not_actions, 3),
                    )
                )

        # Check 2: NotResource with wildcards or broad patterns
        if not_resources and effect == "Allow" and not has_combined:
            # Check if NotResource is used with wildcard Resource
            resources = statement.get_resources()
            has_wildcard_resource = "*" in resources or any("*" in r for r in resources)

            # Trust policies validly omit Resource (the role itself is the resource).
            # Skip the "missing Resource" warning when the statement has a Principal,
            # which is a strong signal this is a resource/trust policy.
            is_trust_like = statement.principal is not None or statement.not_principal is not None

            if has_wildcard_resource or (not resources and not is_trust_like):
                issues.append(
                    ValidationIssue(
                        severity=self.get_severity(config),
                        statement_sid=statement.sid,
                        statement_index=statement_idx,
                        issue_type="not_resource_broad",
                        message=(
                            "Statement uses `NotResource` with `Allow` effect and broad `Resource`. "
                            f"This grants access to ALL resources except: {format_list_with_backticks(not_resources, 3)}."
                        ),
                        suggestion=(
                            "Replace `NotResource` with explicit `Resource` ARNs to follow the "
                            "principle of least privilege. If you need to protect specific resources, "
                            "use a `Deny` statement with explicit `Resource` ARNs instead. "
                            "`NotResource` grants access to all current and future resources "
                            "except those explicitly excluded, which is dangerous as new resources "
                            "are automatically included."
                        ),
                        example='{\n  "Effect": "Allow",\n  "Action": ["s3:GetObject"],\n  "Resource": "arn:aws:s3:::my-bucket/*"\n}',
                        line_number=statement.line_number,
                        field_name="resource",
                        resource=format_list_with_backticks(not_resources, 3),
                    )
                )

        # Check 3: Combined NotAction AND NotResource with Allow
        # This is the most dangerous pattern - grants nearly all actions on nearly all resources
        if not_actions and not_resources and effect == "Allow":
            issues.append(
                ValidationIssue(
                    severity="critical",
                    statement_sid=statement.sid,
                    statement_index=statement_idx,
                    issue_type="combined_not_action_not_resource",
                    message=(
                        "**CRITICAL:** Policy uses both `NotAction` AND `NotResource` with `Allow` effect. "
                        f"This grants **ALL** actions except [{format_list_with_backticks(not_actions, 3)}] "
                        f"on **ALL** resources except [{format_list_with_backticks(not_resources, 3)}]. "
                        "This is equivalent to near-administrator access."
                    ),
                    suggestion=(
                        "Rewrite using explicit `Action` and `Resource` lists instead of negations. "
                        "The combination of `NotAction` + `NotResource` is almost always a mistake. "
                        "If the intent is to protect certain actions/resources, use separate `Deny` "
                        "statements with explicit `Action` and `Resource` lists instead."
                    ),
                    example='{\n  "Effect": "Allow",\n  "Action": ["specific:Action"],\n  "Resource": "arn:aws:service:::specific-resource"\n}',
                    line_number=statement.line_number,
                    field_name="action",
                    action=format_list_with_backticks(not_actions, 3),
                    resource=format_list_with_backticks(not_resources, 3),
                )
            )

        # Check 4: NotAction with Deny - less dangerous but worth noting
        # NotAction with Deny means "deny everything except these actions"
        # which is actually a valid deny pattern but should be reviewed
        if not_actions and effect == "Deny":
            resources = statement.get_resources()
            has_wildcard_resource = "*" in resources

            if has_wildcard_resource:
                issues.append(
                    ValidationIssue(
                        severity="low",
                        statement_sid=statement.sid,
                        statement_index=statement_idx,
                        issue_type="not_action_deny_review",
                        message=(
                            "Statement uses `NotAction` with `Deny` effect on all resources. "
                            f"This denies everything except: {format_list_with_backticks(not_actions, 5)}. "
                            "Review to ensure this is the intended behavior."
                        ),
                        suggestion=(
                            "Verify that allowing only these specific actions is intended. "
                            "Consider if an explicit `Allow` list would be clearer."
                        ),
                        line_number=statement.line_number,
                        field_name="action",
                        action=format_list_with_backticks(not_actions, 3),
                    )
                )

        return issues
