"""Condition comparison logic for SCP policy analysis."""

import fnmatch
from typing import Any


def conditions_could_overlap(cond1: dict[str, Any] | None, cond2: dict[str, Any] | None) -> bool:
    """
    Check if two conditions could both be true simultaneously.

    If either condition is None (unconditional), they always overlap.
    If conditions have mutually exclusive values, they don't overlap.

    Args:
        cond1: First condition dict or None
        cond2: Second condition dict or None

    Returns:
        True if conditions could both be true at the same time
    """
    # No condition means always applies
    if cond1 is None or cond2 is None:
        return True

    # Check each operator in both conditions
    all_operators = set(cond1.keys()) | set(cond2.keys())

    for operator in all_operators:
        op1_values = cond1.get(operator, {})
        op2_values = cond2.get(operator, {})

        if not _operator_values_could_overlap(operator, op1_values, op2_values):
            return False

    return True


def condition_is_subset(narrower: dict[str, Any] | None, broader: dict[str, Any] | None) -> bool:
    """
    Check if the narrower condition is always true when the broader condition is true.

    This means the broader condition is more restrictive or equal.
    If broader is None (unconditional), the narrower is always a subset.

    Args:
        narrower: The potentially narrower (less restrictive) condition
        broader: The potentially broader (more restrictive) condition

    Returns:
        True if narrower is always true when broader is true
    """
    # Unconditional (None) is the broadest - covers everything
    if broader is None:
        return True

    # If broader has conditions but narrower doesn't, narrower is not a subset
    if narrower is None:
        return False

    # Check that all broader conditions are covered by narrower
    for operator, broader_keys in broader.items():
        narrower_keys = narrower.get(operator, {})
        if not _operator_is_subset(operator, narrower_keys, broader_keys):
            return False

    return True


def _operator_values_could_overlap(
    operator: str,
    values1: dict[str, Any],
    values2: dict[str, Any],
) -> bool:
    """Check if two operator value dicts could be true simultaneously."""
    if not values1 or not values2:
        return True

    # Get common keys
    common_keys = set(values1.keys()) & set(values2.keys())

    for key in common_keys:
        v1 = values1[key]
        v2 = values2[key]

        # Normalize to lists
        v1_list = v1 if isinstance(v1, list) else [v1]
        v2_list = v2 if isinstance(v2, list) else [v2]

        if not _values_overlap(operator, v1_list, v2_list):
            return False

    return True


def _values_overlap(operator: str, values1: list[Any], values2: list[Any]) -> bool:
    """Check if two value lists could overlap for a given operator."""
    operator_lower = operator.lower()

    # String equality operators
    if "stringequals" in operator_lower:
        if "ifexists" in operator_lower:
            return True  # IfExists conditions are optional
        # Check if any values match
        v1_lower = {str(v).lower() for v in values1}
        v2_lower = {str(v).lower() for v in values2}
        return bool(v1_lower & v2_lower)

    # String not equals - mutually exclusive if same values
    if "stringnotequals" in operator_lower:
        # If both require NOT the same value, they're compatible
        return True

    # String like (pattern matching)
    if "stringlike" in operator_lower:
        # Check if any patterns could match
        for v1 in values1:
            for v2 in values2:
                if _patterns_could_match_same_string(str(v1), str(v2)):
                    return True
        return False

    # ARN operators
    if "arn" in operator_lower:
        for v1 in values1:
            for v2 in values2:
                if _arns_could_overlap(str(v1), str(v2)):
                    return True
        return False

    # Bool operators
    if operator_lower in ("bool",):
        v1_bools = {str(v).lower() == "true" for v in values1}
        v2_bools = {str(v).lower() == "true" for v in values2}
        return bool(v1_bools & v2_bools)

    # Null operator
    if operator_lower == "null":
        return True  # Null checks are orthogonal to values

    # Default: assume they could overlap
    return True


def _patterns_could_match_same_string(pattern1: str, pattern2: str) -> bool:
    """Check if two wildcard patterns could match the same concrete string."""
    p1 = pattern1.lower()
    p2 = pattern2.lower()

    # Exact match
    if p1 == p2:
        return True

    # If one is a wildcard, check prefix
    if "*" in p1 or "*" in p2:
        p1_prefix = p1.replace("*", "")
        p2_prefix = p2.replace("*", "")

        if not p1_prefix or not p2_prefix:
            return True

        # Check if prefixes are compatible
        if p1_prefix.startswith(p2_prefix) or p2_prefix.startswith(p1_prefix):
            return True

        return False

    # No wildcards, must match exactly
    return p1 == p2


def _arns_could_overlap(arn1: str, arn2: str) -> bool:
    """Check if two ARN patterns could match the same resource."""
    # Universal wildcard
    if arn1 == "*" or arn2 == "*":
        return True

    # Split ARNs into parts
    parts1 = arn1.split(":")
    parts2 = arn2.split(":")

    # Check each part for overlap
    for i in range(min(len(parts1), len(parts2))):
        p1 = parts1[i].lower()
        p2 = parts2[i].lower()

        if p1 == "*" or p2 == "*":
            continue

        if "*" in p1 or "*" in p2:
            if not _patterns_could_match_same_string(p1, p2):
                return False
        elif p1 != p2:
            return False

    return True


def _operator_is_subset(
    operator: str,
    narrower_keys: dict[str, Any],
    broader_keys: dict[str, Any],
) -> bool:
    """Check if narrower operator values are a subset of broader."""
    # All broader keys must be present in narrower with compatible values
    for key, broader_value in broader_keys.items():
        if key not in narrower_keys:
            return False

        narrower_value = narrower_keys[key]

        # Normalize to lists
        broader_list = broader_value if isinstance(broader_value, list) else [broader_value]
        narrower_list = narrower_value if isinstance(narrower_value, list) else [narrower_value]

        if not _value_list_is_subset(operator, narrower_list, broader_list):
            return False

    return True


def _value_list_is_subset(operator: str, narrower: list[Any], broader: list[Any]) -> bool:
    """Check if narrower values are a subset of broader values."""
    operator_lower = operator.lower()

    # For equality operators, narrower must be subset of broader
    if "equals" in operator_lower and "not" not in operator_lower:
        narrower_set = {str(v).lower() for v in narrower}
        broader_set = {str(v).lower() for v in broader}
        return narrower_set <= broader_set

    # For pattern matching, check if narrower patterns are covered by broader
    if "like" in operator_lower or "arn" in operator_lower:
        for n in narrower:
            covered = False
            for b in broader:
                if _pattern_is_subset(str(n), str(b)):
                    covered = True
                    break
            if not covered:
                return False
        return True

    # Default: assume subset if equal
    return set(str(v).lower() for v in narrower) <= set(str(v).lower() for v in broader)


def _pattern_is_subset(narrower: str, broader: str) -> bool:
    """Check if narrower pattern is covered by broader pattern."""
    n = narrower.lower()
    b = broader.lower()

    # Same pattern
    if n == b:
        return True

    # Broader is universal
    if b == "*":
        return True

    # If broader has wildcard, check if it covers narrower
    if "*" in b:
        if "*" not in n:
            return fnmatch.fnmatch(n, b)
        # Both have wildcards - check prefix
        b_prefix = b.replace("*", "")
        n_prefix = n.replace("*", "")
        return n_prefix.startswith(b_prefix) and len(b_prefix) <= len(n_prefix)

    return False
