# ================================================================
# shared/auth/decorators.py - ENHANCED WITH ADMIN ROLE DECORATOR
# ================================================================
"""
Enhanced authentication decorators for microservices including admin role validation.
Designed to work with gateway authentication middleware and provide centralized
role-based access control across all services.
"""

from functools import wraps
from fastapi import HTTPException, status, Request, Depends
from fastapi.security import HTTPBearer
from typing import Callable, Optional, Dict, Any, List
import structlog

logger = structlog.get_logger()

# Bearer token scheme for services that need it
security = HTTPBearer(auto_error=False)

def require_authentication(func: Callable) -> Callable:
    """
    Decorator to require authentication - trusts gateway validation
    Services behind the gateway should use this decorator
    """
    
    @wraps(func)
    async def wrapper(*args, **kwargs):
        # Find request object in arguments
        request = None
        for arg in args:
            if isinstance(arg, Request):
                request = arg
                break
        
        if not request:
            request = kwargs.get('request')
        
        if not request:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="Request object not found"
            )
        
        # Check if user context exists (set by gateway)
        if not hasattr(request.state, 'user') or not request.state.user:
            # Check headers as fallback (for direct service calls in dev)
            user_info = extract_user_from_headers(request)
            if not user_info:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="Authentication required"
                )
            request.state.user = user_info
        
        return await func(*args, **kwargs)
    
    return wrapper

def require_tenant_access(func: Callable) -> Callable:
    """Decorator to require tenant access"""
    
    @wraps(func)
    async def wrapper(*args, **kwargs):
        request = None
        for arg in args:
            if isinstance(arg, Request):
                request = arg
                break
        
        if not request:
            request = kwargs.get('request')
            
        if not request or not hasattr(request.state, 'tenant_id'):
            # Try to extract from headers
            tenant_id = extract_tenant_from_headers(request)
            if not tenant_id:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="Tenant access required"
                )
            request.state.tenant_id = tenant_id
        
        return await func(*args, **kwargs)
    
    return wrapper

def require_role(role: str):
    """Decorator to require specific role"""
    
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        async def wrapper(*args, **kwargs):
            request = None
            for arg in args:
                if isinstance(arg, Request):
                    request = arg
                    break
            
            if not request:
                request = kwargs.get('request')
                
            user = get_current_user(request)
            user_role = user.get('role', '').lower()
            
            if user_role != role.lower() and user_role != 'admin':
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail=f"{role} role required"
                )
            
            return await func(*args, **kwargs)
        
        return wrapper
    
    return decorator

def require_admin_role(func: Callable) -> Callable:
    """
    Decorator to require admin role - simplified version for FastAPI dependencies
    
    This decorator ensures only users with 'admin' role can access the endpoint.
    Can be used as a FastAPI dependency or function decorator.
    
    Usage as dependency:
        @router.delete("/admin/users/{user_id}")
        async def delete_user(
            user_id: str,
            current_user = Depends(get_current_user_dep),
            _admin_check = Depends(require_admin_role),
        ):
            # Admin-only logic here
    
    Usage as decorator:
        @require_admin_role
        @router.delete("/admin/users/{user_id}")
        async def delete_user(...):
            # Admin-only logic here
    """
    
    @wraps(func)
    async def wrapper(*args, **kwargs):
        # Find request object in arguments
        request = None
        current_user = None
        
        # Extract request and current_user from arguments
        for arg in args:
            if isinstance(arg, Request):
                request = arg
            elif isinstance(arg, dict) and 'user_id' in arg:
                current_user = arg
        
        # Check kwargs for request and current_user
        if not request:
            request = kwargs.get('request')
        if not current_user:
            current_user = kwargs.get('current_user')
        
        # If we still don't have current_user, try to get it from request
        if not current_user and request:
            current_user = get_current_user(request)
        
        if not current_user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Authentication required"
            )
        
        # Check if user has admin role
        user_role = current_user.get('role', '').lower()
        
        if user_role != 'admin':
            logger.warning("Non-admin user attempted admin operation", 
                          user_id=current_user.get('user_id'),
                          role=user_role)
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Admin role required"
            )
        
        logger.info("Admin operation authorized", 
                   user_id=current_user.get('user_id'),
                   endpoint=func.__name__)
        
        return await func(*args, **kwargs)
    
    return wrapper

def require_roles(allowed_roles: List[str]):
    """
    Decorator to require one of multiple roles
    
    Args:
        allowed_roles: List of roles that are allowed to access the endpoint
    
    Usage:
        @require_roles(['admin', 'manager'])
        @router.post("/sensitive-operation")
        async def sensitive_operation(...):
            # Only admins and managers can access
    """
    
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        async def wrapper(*args, **kwargs):
            request = None
            current_user = None
            
            # Extract request and current_user from arguments
            for arg in args:
                if isinstance(arg, Request):
                    request = arg
                elif isinstance(arg, dict) and 'user_id' in arg:
                    current_user = arg
            
            # Check kwargs
            if not request:
                request = kwargs.get('request')
            if not current_user:
                current_user = kwargs.get('current_user')
            
            # Get user from request if not provided
            if not current_user and request:
                current_user = get_current_user(request)
            
            if not current_user:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="Authentication required"
                )
            
            # Check if user has one of the allowed roles
            user_role = current_user.get('role', '').lower()
            allowed_roles_lower = [role.lower() for role in allowed_roles]
            
            if user_role not in allowed_roles_lower:
                logger.warning("Unauthorized role attempted restricted operation", 
                              user_id=current_user.get('user_id'),
                              user_role=user_role,
                              allowed_roles=allowed_roles)
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail=f"One of these roles required: {', '.join(allowed_roles)}"
                )
            
            logger.info("Role-based operation authorized", 
                       user_id=current_user.get('user_id'),
                       user_role=user_role,
                       endpoint=func.__name__)
            
            return await func(*args, **kwargs)
        
        return wrapper
    
    return decorator

def require_tenant_admin(func: Callable) -> Callable:
    """
    Decorator to require admin role within a specific tenant context
    
    This checks that the user is an admin AND has access to the tenant
    being operated on. Useful for tenant-scoped admin operations.
    """
    
    @wraps(func)
    async def wrapper(*args, **kwargs):
        request = None
        current_user = None
        
        # Extract request and current_user from arguments
        for arg in args:
            if isinstance(arg, Request):
                request = arg
            elif isinstance(arg, dict) and 'user_id' in arg:
                current_user = arg
        
        if not request:
            request = kwargs.get('request')
        if not current_user:
            current_user = kwargs.get('current_user')
        
        if not current_user and request:
            current_user = get_current_user(request)
        
        if not current_user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Authentication required"
            )
        
        # Check admin role first
        user_role = current_user.get('role', '').lower()
        if user_role != 'admin':
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Admin role required"
            )
        
        # Check tenant access
        tenant_id = get_current_tenant_id(request) if request else None
        if not tenant_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Tenant context required"
            )
        
        # Additional tenant admin validation could go here
        # For now, we trust that admin users have access to operate on any tenant
        
        logger.info("Tenant admin operation authorized", 
                   user_id=current_user.get('user_id'),
                   tenant_id=tenant_id,
                   endpoint=func.__name__)
        
        return await func(*args, **kwargs)
    
    return wrapper

def get_current_user(request: Request) -> Dict[str, Any]:
    """Get current user from request state or headers"""
    if hasattr(request.state, 'user') and request.state.user:
        return request.state.user
    
    # Fallback to headers (for dev/testing)
    user_info = extract_user_from_headers(request)
    if not user_info:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User not authenticated"
        )
    
    return user_info

def get_current_tenant_id(request: Request) -> Optional[str]:
    """Get current tenant ID from request state or headers"""
    if hasattr(request.state, 'tenant_id'):
        return request.state.tenant_id
    
    # Fallback to headers
    return extract_tenant_from_headers(request)

def extract_user_from_headers(request: Request) -> Optional[Dict[str, Any]]:
    """Extract user information from forwarded headers (gateway sets these)"""
    user_id = request.headers.get("x-user-id")
    logger.info(f"🔍 Extracting user from headers",
               user_id=user_id,
               has_user_id=bool(user_id),
               path=request.url.path)

    if not user_id:
        logger.warning(f"❌ No x-user-id header found", path=request.url.path)
        return None

    user_context = {
        "user_id": user_id,
        "email": request.headers.get("x-user-email", ""),
        "role": request.headers.get("x-user-role", "user"),
        "tenant_id": request.headers.get("x-tenant-id"),
        "permissions": request.headers.get("X-User-Permissions", "").split(",") if request.headers.get("X-User-Permissions") else [],
        "full_name": request.headers.get("x-user-full-name", ""),
        "subscription_tier": request.headers.get("x-subscription-tier", ""),
        "is_demo": request.headers.get("x-is-demo", "").lower() == "true",
        "demo_session_id": request.headers.get("x-demo-session-id", ""),
        "demo_account_type": request.headers.get("x-demo-account-type", "")
    }

    logger.info(f"✅ User context extracted from headers",
               user_context=user_context,
               path=request.url.path)

    # ✅ ADD THIS: Handle service tokens properly
    user_type = request.headers.get("x-user-type", "")
    service_name = request.headers.get("x-service-name", "")

    if user_type == "service" or service_name:
        user_context.update({
            "type": "service",
            "service": service_name,
            "role": "admin",  # Service tokens always have admin role
            "is_service": True
        })

    return user_context

def extract_tenant_from_headers(request: Request) -> Optional[str]:
    """Extract tenant ID from headers"""
    return request.headers.get("x-tenant-id")

def extract_user_from_jwt(auth_header: str) -> Optional[Dict[str, Any]]:
    """
    Extract user information from JWT token
    This is a fallback for when gateway doesn't inject x-user-* headers
    """
    try:
        from jose import jwt
        from shared.config.base import is_internal_service

        # Remove "Bearer " prefix
        token = auth_header.replace("Bearer ", "").strip()

        # Decode without verification (we trust tokens from gateway)
        # In production, you'd verify with the secret key
        payload = jwt.decode(token, key="dummy", options={"verify_signature": False})

        logger.debug("JWT payload decoded", payload_keys=list(payload.keys()))

        # Extract user information from JWT payload
        user_id = payload.get("sub") or payload.get("user_id") or payload.get("service")

        if not user_id:
            logger.warning("No user_id found in JWT payload", payload=payload)
            return None

        # Check if this is a service token
        token_type = payload.get("type", "")
        service_name = payload.get("service", "")

        if token_type == "service" or is_internal_service(user_id) or is_internal_service(service_name):
            # This is a service token
            service_identifier = service_name or user_id
            user_context = {
                "user_id": service_identifier,
                "type": "service",
                "service": service_identifier,
                "role": "admin",  # Services get admin privileges
                "is_service": True,
                "permissions": ["read", "write", "admin"],
                "email": f"{service_identifier}@internal.service",
                "full_name": f"{service_identifier.replace('-', ' ').title()}"
            }
            logger.info("Service authenticated via JWT", service=service_identifier)
        else:
            # This is a user token
            user_context = {
                "user_id": user_id,
                "type": "user",
                "email": payload.get("email", ""),
                "role": payload.get("role", "user"),
                "tenant_id": payload.get("tenant_id"),
                "permissions": payload.get("permissions", []),
                "full_name": payload.get("full_name", ""),
                "subscription_tier": payload.get("subscription_tier", ""),
                "is_service": False
            }
            logger.info("User authenticated via JWT", user_id=user_id)

        return user_context

    except Exception as e:
        logger.error("Failed to extract user from JWT", error=str(e), error_type=type(e).__name__)
        return None

# ================================================================
# FASTAPI DEPENDENCY FUNCTIONS
# ================================================================

async def get_current_user_dep(request: Request) -> Dict[str, Any]:
    """FastAPI dependency to get current user - ENHANCED with JWT fallback for services"""
    try:
        # Enhanced logging for debugging
        logger.info(
            "🔐 Authentication attempt",
            path=request.url.path,
            method=request.method,
            has_auth_header=bool(request.headers.get("authorization")),
            has_x_user_id=bool(request.headers.get("x-user-id")),
            has_x_is_demo=bool(request.headers.get("x-is-demo")),
            has_x_demo_session_id=bool(request.headers.get("x-demo-session-id")),
            x_user_id=request.headers.get("x-user-id", "MISSING"),
            x_is_demo=request.headers.get("x-is-demo", "MISSING"),
            x_demo_session_id=request.headers.get("x-demo-session-id", "MISSING"),
            client_ip=request.client.host if request.client else "unknown"
        )

        # Try to get user from headers first (preferred method)
        user = None
        try:
            user = get_current_user(request)
        except HTTPException:
            # If headers are missing, try JWT token as fallback
            auth_header = request.headers.get("authorization", "")
            if auth_header.startswith("Bearer "):
                user = extract_user_from_jwt(auth_header)
                if user:
                    logger.info(
                        "User authenticated via JWT fallback",
                        user_id=user.get("user_id"),
                        user_type=user.get("type", "user"),
                        is_service=user.get("type") == "service",
                        path=request.url.path
                    )

            # If still no user, raise original exception
            if not user:
                raise

        logger.info(
            "User authenticated successfully",
            user_id=user.get("user_id"),
            user_type=user.get("type", "user"),
            is_service=user.get("type") == "service",
            role=user.get("role"),
            path=request.url.path
        )

        return user

    except HTTPException as e:
        logger.warning(
            "Authentication failed - 401",
            path=request.url.path,
            status_code=e.status_code,
            detail=e.detail,
            has_x_user_id=bool(request.headers.get("x-user-id")),
            has_auth_header=bool(request.headers.get("authorization")),
            x_user_type=request.headers.get("x-user-type", "none"),
            x_service_name=request.headers.get("x-service-name", "none"),
            client_ip=request.client.host if request.client else "unknown"
        )
        raise

async def get_current_tenant_id_dep(request: Request) -> Optional[str]:
    """FastAPI dependency to get current tenant ID"""
    return get_current_tenant_id(request)

async def require_admin_role_dep(
    current_user: Dict[str, Any] = Depends(get_current_user_dep)
) -> Dict[str, Any]:
    """
    FastAPI dependency that requires admin role
    
    Usage:
        @router.delete("/admin/users/{user_id}")
        async def delete_user(
            user_id: str,
            admin_user: Dict[str, Any] = Depends(require_admin_role_dep)
        ):
            # admin_user is guaranteed to have admin role
    """
    
    user_role = current_user.get('role', '').lower()
    
    if user_role != 'admin':
        logger.warning("Non-admin user attempted admin operation", 
                      user_id=current_user.get('user_id'),
                      role=user_role)
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Admin role required"
        )
    
    logger.info("Admin operation authorized via dependency", 
               user_id=current_user.get('user_id'))
    
    return current_user

async def require_roles_dep(allowed_roles: List[str]):
    """
    FastAPI dependency factory that requires one of multiple roles
    
    Usage:
        require_manager_or_admin = require_roles_dep(['admin', 'manager'])
        
        @router.post("/sensitive-operation")
        async def sensitive_operation(
            user: Dict[str, Any] = Depends(require_manager_or_admin)
        ):
            # Only admins and managers can access
    """
    
    async def check_roles(
        current_user: Dict[str, Any] = Depends(get_current_user_dep)
    ) -> Dict[str, Any]:
        user_role = current_user.get('role', '').lower()
        allowed_roles_lower = [role.lower() for role in allowed_roles]
        
        if user_role not in allowed_roles_lower:
            logger.warning("Unauthorized role attempted restricted operation", 
                          user_id=current_user.get('user_id'),
                          user_role=user_role,
                          allowed_roles=allowed_roles)
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"One of these roles required: {', '.join(allowed_roles)}"
            )
        
        logger.info("Role-based operation authorized via dependency", 
                   user_id=current_user.get('user_id'),
                   user_role=user_role)
        
        return current_user
    
    return check_roles

# ================================================================
# UTILITY FUNCTIONS FOR ROLE CHECKING
# ================================================================

def is_admin_user(user: Dict[str, Any]) -> bool:
    """Check if user has admin role"""
    return user.get('role', '').lower() == 'admin'

def is_user_in_roles(user: Dict[str, Any], allowed_roles: List[str]) -> bool:
    """Check if user has one of the allowed roles"""
    user_role = user.get('role', '').lower()
    allowed_roles_lower = [role.lower() for role in allowed_roles]
    return user_role in allowed_roles_lower

def get_user_permissions(user: Dict[str, Any]) -> List[str]:
    """Get user permissions list"""
    return user.get('permissions', [])

def has_permission(user: Dict[str, Any], permission: str) -> bool:
    """Check if user has specific permission"""
    permissions = get_user_permissions(user)
    return permission in permissions

# ================================================================
# ADVANCED ROLE DECORATORS
# ================================================================

def require_permission(permission: str):
    """
    Decorator to require specific permission
    
    Usage:
        @require_permission('delete_users')
        @router.delete("/users/{user_id}")
        async def delete_user(...):
            # Only users with 'delete_users' permission can access
    """
    
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        async def wrapper(*args, **kwargs):
            current_user = None
            
            # Extract current_user from arguments
            for arg in args:
                if isinstance(arg, dict) and 'user_id' in arg:
                    current_user = arg
                    break
            
            if not current_user:
                current_user = kwargs.get('current_user')
            
            if not current_user:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="Authentication required"
                )
            
            # Check permission
            if not has_permission(current_user, permission):
                # Admins bypass permission checks
                if not is_admin_user(current_user):
                    logger.warning("User lacks required permission", 
                                  user_id=current_user.get('user_id'),
                                  required_permission=permission,
                                  user_permissions=get_user_permissions(current_user))
                    raise HTTPException(
                        status_code=status.HTTP_403_FORBIDDEN,
                        detail=f"Permission '{permission}' required"
                    )
            
            logger.info("Permission-based operation authorized", 
                       user_id=current_user.get('user_id'),
                       permission=permission)
            
            return await func(*args, **kwargs)
        
        return wrapper
    
    return decorator

# Export all decorators and functions
__all__ = [
    # Main decorators
    'require_authentication',
    'require_tenant_access', 
    'require_role',
    'require_admin_role',
    'require_roles',
    'require_tenant_admin',
    'require_permission',
    
    # FastAPI dependencies
    'get_current_user_dep',
    'get_current_tenant_id_dep',
    'require_admin_role_dep',
    'require_roles_dep',
    
    # Utility functions
    'get_current_user',
    'get_current_tenant_id',
    'extract_user_from_headers',
    'extract_user_from_jwt',
    'extract_tenant_from_headers',
    'is_admin_user',
    'is_user_in_roles',
    'get_user_permissions',
    'has_permission'
]
