"""Permission-based authentication middleware for FastMCP.

This middleware enforces role/permission-based access control using Auth0 JWT tokens.

Access Levels:
1. Unauthenticated → Only @public tools
2. Authenticated (not ptAdmin) → @public + tools matching their permissions
3. ptAdmin → All tools (full access)

Usage:
    The middleware is automatically applied when using register_tools() from
    the auth-enabled tool registration module.
"""

import os

from fastmcp.server.middleware import Middleware, MiddlewareContext
from fastmcp.server.dependencies import get_access_token
from point_topic_mcp.auth.decorators import (
    is_public_tool,
    get_tool_permissions,
    has_tool_permissions,
)


def get_user_permissions() -> list[str]:
    """Extract user permissions from Auth0 JWT token.

    Returns:
        List of permission/role strings from the JWT token claims
        Empty list if no token or unauthenticated
    """
    try:
        token = get_access_token()
        if token and token.claims:
            permissions = []

            # Auth0 stores permissions in different claim locations
            # Check all possible sources

            # 1. 'roles' claim (from Auth0 Actions/Rules)
            roles = token.claims.get("roles", [])
            if roles:
                if isinstance(roles, list):
                    permissions.extend(roles)
                else:
                    permissions.append(roles)

            # 2. 'permissions' claim (from Auth0 API Authorization)
            perms = token.claims.get("permissions", [])
            if perms:
                if isinstance(perms, list):
                    permissions.extend(perms)
                else:
                    permissions.append(perms)

            # 3. 'scope' claim (OAuth2 scopes, space-separated)
            scope = token.claims.get("scope", "")
            if scope and isinstance(scope, str):
                permissions.extend(scope.split())
            elif scope:
                permissions.append(str(scope))

            # Remove duplicates while preserving order
            seen = set()
            unique_perms = []
            for p in permissions:
                if p not in seen:
                    seen.add(p)
                    unique_perms.append(p)

            return unique_perms
    except Exception:
        pass

    # Stdio (server_local): no JWT. ADMIN_API_KEY is the auth - required for full access.
    if os.getenv("ADMIN_API_KEY"):
        return ["ptAdmin"]

    return []


def is_pt_admin(user_permissions: list[str]) -> bool:
    """Check if user has ptAdmin role.

    Args:
        user_permissions: List of user permissions/roles

    Returns:
        True if user has ptAdmin or superuser role
    """
    admin_roles = [
        "Point Topic Admin",
        "ptAdmin",
        "admin",
        "admin:upc-query-agent",
    ]
    return any(role in user_permissions for role in admin_roles)


def check_tool_access(tool_name: str, user_permissions: list[str]) -> tuple[bool, str]:
    """Check if user can access a specific tool.

    Args:
        tool_name: Name of the tool to check
        user_permissions: List of user's permissions/roles

    Returns:
        Tuple of (allowed: bool, reason: str)
    """
    # Public tools are always allowed
    if is_public_tool(tool_name):
        return True, "Public tool - no authentication required"

    # ptAdmin has access to everything
    if is_pt_admin(user_permissions):
        return True, "ptAdmin access granted"

    # Not authenticated and not public
    if not user_permissions:
        return False, "Authentication required. Please login via Auth0 OAuth."

    # Get required permissions for this tool
    required_perms = get_tool_permissions(tool_name)

    # Tool with no permission requirements = accessible to all authenticated
    if not required_perms:
        return True, "Authenticated access granted"

    # Check if user has ALL required permissions
    missing = [p for p in required_perms if p not in user_permissions]
    if missing:
        return False, (
            f"Missing permissions: {missing}. "
            f"Required: {required_perms}. "
            f"Your permissions: {user_permissions}"
        )

    return True, "Permission check passed"


class PermissionMiddleware(Middleware):
    """Middleware that enforces permission-based access control.

    This middleware filters tools based on user permissions from Auth0 JWT tokens.
    It implements three-tier access:
        - Unauthenticated: Only @public tools visible
        - Authenticated: @public + matching @requires_permissions
        - ptAdmin: All tools visible

    Uses on_list_tools to filter visible tools per-user.
    Uses on_call_tool as defense-in-depth (should rarely trigger if filtering works).
    """

    async def on_list_tools(self, context: MiddlewareContext, call_next):
        """Filter tool list based on user's permissions before returning to client.

        This is the PRIMARY permission check - tools not in this list won't be
        visible to the user in their IDE/client.

        Args:
            context: Middleware context
            call_next: Function to get the full tool list

        Returns:
            Filtered list of tools the user has permission to see
        """
        # Get all registered tools
        all_tools = await call_next(context)

        # Get current user's permissions
        user_permissions = get_user_permissions()
        is_admin = is_pt_admin(user_permissions)
        is_authenticated = len(user_permissions) > 0

        # Filter tools based on permissions
        visible_tools = []
        for tool in all_tools:
            tool_name = tool.name

            # Check if tool should be visible
            if is_public_tool(tool_name):
                # Public tools visible to everyone
                visible_tools.append(tool)
            elif is_admin:
                # ptAdmin sees everything
                visible_tools.append(tool)
            elif is_authenticated:
                # Authenticated user - check permissions
                required = get_tool_permissions(tool_name)
                if not required:
                    # No permission requirements = visible to all authenticated
                    visible_tools.append(tool)
                elif has_tool_permissions(tool_name, user_permissions):
                    # User has required permissions
                    visible_tools.append(tool)
                # else: user doesn't have permission, don't add to list
            # else: unauthenticated, only public tools shown (already handled above)

        return visible_tools

    async def on_call_tool(self, context: MiddlewareContext, call_next):
        """Defense-in-depth: verify permissions before executing tool.

        This should rarely trigger because on_list_tools already filtered
        the visible tools. But we check again in case of:
        - Direct API calls bypassing the tool list
        - Race conditions
        - Cached tool references

        Args:
            context: Middleware context with tool name
            call_next: Function to call the actual tool

        Returns:
            Tool result if allowed

        Raises:
            PermissionError: If user doesn't have required permissions
        """
        tool_name = context.message.name
        user_permissions = get_user_permissions()

        allowed, reason = check_tool_access(tool_name, user_permissions)

        if not allowed:
            raise PermissionError(reason)

        # Access granted - call the tool
        return await call_next(context)


__all__ = [
    "PermissionMiddleware",
    "get_user_permissions",
    "is_pt_admin",
    "check_tool_access",
]
