import re
from functools import wraps
from typing import List, Literal, Union
from fastapi import Request
from abs_exception_core.exceptions import PermissionDeniedError
from .service import RBACService
from .types import (
    PermissionTuple,
    PermissionNode,
    PreCheckCallbacks,
    PostCheckCallback,
    CheckResult,
)

def rbac_require_permission(permissions: Union[str, List[str]]):
    """
    Decorator to enforce all required permissions for a user.

    Args:
        permissions (str | list[str]): One or more "resource:action" strings.

    Raises:
        PermissionDeniedError: If the user lacks any one of the required permissions.
    """
    if isinstance(permissions, str):
        permissions = [permissions]

    def decorator(func):
        @wraps(func)
        async def wrapper(
            request:Request,
            *args,rbac_service:RBACService, **kwargs,
        ):
            current_user_uuid = request.state.user.uuid
            if not current_user_uuid:
                raise PermissionDeniedError(
                    detail="User not found (missing 'uuid')."
                )
            for perm in permissions:
                try:
                    module, resource, action = perm.split(":")
                except ValueError:
                    raise ValueError(
                        f"Invalid permission format: '{perm}'. Expected 'module:resource:action'."
                    )
                
                has_permission = rbac_service.check_permission(
                    user_uuid=current_user_uuid, resource=resource, action=action,module=module
                )

                if not has_permission:
                    raise PermissionDeniedError(
                        detail=f"Permission denied: {action} on {resource} in {module}"
                    )
            return await func(*args,request=request,rbac_service=rbac_service, **kwargs)
        return wrapper
    return decorator


def _resolve_placeholder(template: str, context: dict) -> str:
    """
    Resolve {placeholder} patterns from context dictionary.

    Args:
        template: String potentially containing {placeholder} patterns
        context: Dictionary of values to substitute

    Returns:
        String with placeholders resolved, or None if a placeholder is missing
    """
    pattern = r'\{(\w+)\}'

    missing = False

    def replacer(match):
        nonlocal missing
        key = match.group(1)
        if key not in context or context[key] is None:
            missing = True
            return match.group(0)
        return str(context[key])

    result = re.sub(pattern, replacer, template)
    return None if missing else result


def _check_single_permission(
    rbac_service: RBACService,
    user_uuid: str,
    module: str,
    resource: str,
    action: str,
) -> bool:
    """
    Check a single permission using user-direct and/or role-based methods.

    Args:
        rbac_service: The RBAC service instance
        user_uuid: The user's UUID
        module: Permission module
        resource: Permission resource (may contain resolved placeholders)
        action: Permission action

    Returns:
        True if user has permission, False otherwise
    """
    # Check user-direct permissions first
    if rbac_service.check_permission_by_user(
        user_uuid, resource, action, module
    ):
        return True

    # Check role-based permissions
    if rbac_service.check_permission(
        user_uuid, resource, action, module
    ):
        return True

    return False


def _is_permission_tuple(node) -> bool:
    """Check if a node is a permission tuple (module, resource, action)."""
    return (
        isinstance(node, tuple) and
        len(node) == 3 and
        all(isinstance(x, str) for x in node)
    )


def _evaluate_permission_node(
    node: PermissionNode,
    rbac_service: RBACService,
    user_uuid: str,
    context: dict,
) -> bool:
    """
    Recursively evaluate a permission node (tuple or nested AND/OR dict).

    Args:
        node: Permission node - either a tuple or a dict with AND/OR key
        rbac_service: The RBAC service instance
        user_uuid: The user's UUID
        context: Context dict for placeholder resolution

    Returns:
        True if permission check passes, False otherwise
    """
    # Case 1: Single permission tuple
    if _is_permission_tuple(node):
        module, resource, action = node

        # Resolve placeholders in resource string
        resolved_resource = _resolve_placeholder(resource, context)
        resolved_module = _resolve_placeholder(module, context)

        # Skip this permission check if placeholders couldn't be resolved
        if resolved_resource is None or resolved_module is None:
            return True

        # Check the permission
        has_permission = _check_single_permission(
            rbac_service=rbac_service,
            user_uuid=user_uuid,
            module=resolved_module,
            resource=resolved_resource,
            action=action,
        )

        return has_permission

    # Case 2: Nested dict with AND/OR operator
    if isinstance(node, dict):
        if "AND" in node:
            children = node["AND"]
            results = [
                _evaluate_permission_node(
                    child, rbac_service, user_uuid, context
                )
                for child in children
            ]
            return all(results)

        elif "OR" in node:
            children = node["OR"]
            results = []
            for child in children:
                result = _evaluate_permission_node(
                    child, rbac_service, user_uuid, context
                )
                results.append(result)

            return any(results)

    raise ValueError(f"Invalid permission node format: {node}")


def _build_permission_tree(
    permissions: Union[List[PermissionTuple], PermissionTuple, PermissionNode],
    operator: Literal["AND", "OR"] = "AND",
) -> PermissionNode:
    """
    Normalize various permission formats into a PermissionNode tree.

    Supports:
    - Single tuple: ("MODULE", "resource", "action")
    - List of tuples with operator: [tuple1, tuple2] with operator="OR"
    - Already structured PermissionNode: {"AND": [...]} or {"OR": [...]}

    Args:
        permissions: Permission specification in any supported format
        operator: Operator to use for flat lists (default: "AND")

    Returns:
        Normalized PermissionNode structure
    """
    # Already a dict with AND/OR - return as-is
    if isinstance(permissions, dict) and ("AND" in permissions or "OR" in permissions):
        return permissions

    # Single tuple
    if _is_permission_tuple(permissions):
        return permissions

    # List of items - wrap with operator
    if isinstance(permissions, list):
        return {operator: permissions}

    raise ValueError(f"Invalid permissions format: {permissions}")

def check_permissions(
    permissions: Union[List[PermissionTuple], PermissionTuple, PermissionNode],
    operator: Literal["AND", "OR"] = "AND",
    pre_check: PreCheckCallbacks | None = None,
    pre_check_operator: Literal["AND", "OR"] = "AND",
    post_check: PostCheckCallback | None = None,
):
    """
    Permission decorator with nested AND/OR logic, placeholders, and callbacks.

    Args:
        permissions: Permission tuple(s) as (module, resource, action).
            Formats: single tuple, list of tuples, or nested {"AND": [...]} / {"OR": [...]}
            Resource can have placeholders: "view:{view_id}" resolved from path params/kwargs.
            If a placeholder cannot be resolved, that permission node is skipped entirely.

        operator: "AND" (all required) or "OR" (any one). Default: "AND".

        pre_check: A single async callback or a list of async callbacks.
            Each callback: (request, rbac_service, context) -> bool | None | CheckResult
            True=grant, False=deny, None=continue. Use CheckResult for custom error message.

        pre_check_operator: How to combine multiple pre_check results. Default: "AND".
            "AND": All must agree. Any False = deny immediately. Grant only if ALL return True.
                   If any return None, continue to permission tree.
            "OR":  Any True = grant immediately. Deny only if ALL return False.
                   If mixed False/None, continue to permission tree.

        post_check: Async callback (request, rbac_service, context, result) -> bool | CheckResult
            Modify final result. Use CheckResult for custom error message.

    Raises:
        PermissionDeniedError: If permission check fails.

    Examples:
        # Single permission
        @check_permissions(("MODULE", "resource", "read"))

        # Multiple permissions (all required by default)
        @check_permissions([
            ("MODULE", "resource", "read"),
            ("MODULE", "resource", "write"),
        ])

        # OR operator (any one is sufficient)
        @check_permissions([
            ("MODULE", "resource", "read"),
            ("MODULE", "resource", "admin"),
        ], operator="OR")

        # Placeholder in resource (resolved from path params/kwargs)
        @check_permissions(("MODULE", "view:{view_id}", "read"))

        # Single pre_check (backward compatible)
        @check_permissions(("MODULE", "resource", "read"), pre_check=my_check)

        # Multiple pre_checks with AND (all must agree to grant/deny)
        @check_permissions(
            ("MODULE", "resource", "read"),
            pre_check=[is_owner_check, is_active_check],
            pre_check_operator="AND",
        )

        # Multiple pre_checks with OR (any one True grants access)
        @check_permissions(
            ("MODULE", "resource", "read"),
            pre_check=[is_owner_check, is_admin_check],
            pre_check_operator="OR",
        )
    """
    # Normalize permissions to a tree structure
    permission_tree = _build_permission_tree(permissions, operator)

    def decorator(func):
        @wraps(func)
        async def wrapper(
            request: Request,
            *args,
            rbac_service: RBACService,
            **kwargs,
        ):
            # Get user UUID from request state
            user = request.state.user if hasattr(request.state, 'user') else None
            contact = request.state.contact if hasattr(request.state, 'contact') else None
            if contact:
                return await func(*args, request=request, rbac_service=rbac_service, **kwargs)
            if user:
                current_user_uuid = user.uuid
            else:
                raise PermissionDeniedError(
                    detail="User does not have access to this resource"
                )

            # Build context from kwargs and path_params for placeholder resolution
            context = {**kwargs}
            access_granted = None
            if hasattr(request, 'path_params') and request.path_params:
                context.update(request.path_params)

            # Execute pre-check callback(s) if provided
            pre_checks = [pre_check] if callable(pre_check) else (pre_check or [])

            if pre_checks:
                has_neutral = False
                deny_msg = "Access denied by pre-check callback."

                for cb in pre_checks:
                    result = await cb(request, rbac_service, context)
                    allowed = result.allowed if isinstance(result, CheckResult) else result

                    if isinstance(result, CheckResult) and result.message and allowed is False:
                        deny_msg = result.message

                    if allowed is None:
                        has_neutral = True
                    elif allowed is False and pre_check_operator == "AND":
                        raise PermissionDeniedError(detail=deny_msg)
                    elif allowed is True and pre_check_operator == "OR":
                        return await func(*args, request=request, rbac_service=rbac_service, **kwargs)

                if not has_neutral:
                    if pre_check_operator == "AND":
                        return await func(*args, request=request, rbac_service=rbac_service, **kwargs)
                    raise PermissionDeniedError(detail=deny_msg)
                # Some callbacks returned None → continue to permission tree

            # Evaluate the permission tree
            try:
                access_granted = _evaluate_permission_node(
                    node=permission_tree,
                    rbac_service=rbac_service,
                    user_uuid=current_user_uuid,
                    context=context,
                )
            except ValueError as e:
                raise PermissionDeniedError(detail=str(e))

            # Execute post-check callback if provided
            custom_error_message = None
            if post_check is not None:
                post_check_result = await post_check(request, rbac_service, context, access_granted)

                # Handle CheckResult object
                if isinstance(post_check_result, CheckResult):
                    if post_check_result.allowed is not None:
                        access_granted = post_check_result.allowed
                    if post_check_result.message:
                        custom_error_message = post_check_result.message
                # Handle simple bool (only override if explicitly True/False)
                elif post_check_result is True or post_check_result is False:
                    access_granted = post_check_result
                # post_check_result is None -> keep original access_granted

            # Raise exception if access denied
            if not access_granted:
                if custom_error_message:
                    detail = custom_error_message
                else:
                    detail = "User does not have access to this resource"
                raise PermissionDeniedError(detail=detail)

            return await func(*args, request=request, rbac_service=rbac_service, **kwargs)

        return wrapper
    return decorator
