"""Authentication and authorization middleware."""

from typing import Any, Awaitable, Callable, Dict, Optional

from fastapi import HTTPException, Request, Response
from fastapi.responses import JSONResponse
import structlog

from .models import UserRole
from .permissions import PermissionChecker
from .session_storage import session_storage
from .utils import extract_token_from_header, verify_jwt_token

logger = structlog.get_logger(__name__)

# Public endpoints that don't require authentication
PUBLIC_PATHS = {
    "/",
    "/api/health",
    "/api/docs",
    "/api/openapi.json",
    "/api/redoc",
    "/favicon.ico",
    "/api/auth/login",
}

# Static file prefixes for FastAPI (Swagger UI)
STATIC_PREFIXES = [
    "/static/",
    "/docs/",
    "/redoc/",
    "/api/static/",
    "/api/docs/",
    "/api/redoc/",
]


async def jwt_auth_middleware(
    request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
    """
    Middleware for JWT token verification and user authentication.

    Supports JWT token in Authorization header: Bearer <token>
    """
    if request.method == "OPTIONS":
        return await call_next(request)

    path = request.url.path
    if path in PUBLIC_PATHS:
        return await call_next(request)

    if any(path.startswith(prefix) for prefix in STATIC_PREFIXES):
        return await call_next(request)

    user_token: Optional[str] = None
    user_info: Optional[Dict[str, Any]] = None
    auth_method: Optional[str] = None

    try:
        auth_header = request.headers.get("Authorization")
        if auth_header:
            jwt_token = extract_token_from_header(auth_header)
            if jwt_token:
                jwt_payload = verify_jwt_token(jwt_token)
                if jwt_payload:
                    session_id = jwt_payload.get("session_id")
                    if session_id:
                        session_data = session_storage.get_session(session_id)
                        if session_data:
                            user_token = session_data.get("guac_token")
                        else:
                            logger.warning(
                                "Session not found in Redis",
                                session_id=session_id,
                                username=jwt_payload.get("username"),
                            )
                    else:
                        user_token = jwt_payload.get("guac_token")

                    user_info = {
                        "username": jwt_payload["username"],
                        "role": jwt_payload["role"],
                        "permissions": jwt_payload.get("permissions", []),
                        "full_name": jwt_payload.get("full_name"),
                        "email": jwt_payload.get("email"),
                        "organization": jwt_payload.get("organization"),
                        "organizational_role": jwt_payload.get("organizational_role"),
                    }
                    auth_method = "jwt"

                    logger.debug(
                        "JWT authentication successful",
                        username=user_info["username"],
                        role=user_info["role"],
                        has_session=session_id is not None,
                        has_token=user_token is not None,
                    )

        if not user_token or not user_info:
            logger.info(
                "Authentication required",
                path=path,
                method=request.method,
                client_ip=request.client.host if request.client else "unknown",
            )

            return JSONResponse(
                status_code=401,
                content={
                    "error": "Authentication required",
                    "message": (
                        "Provide JWT token in Authorization header. "
                        "Get token via /auth/login"
                    ),
                    "login_endpoint": "/auth/login",
                },
            )

        user_role = UserRole(user_info["role"])
        allowed, reason = PermissionChecker.check_endpoint_access(
            user_role, request.method, path
        )

        if not allowed:
            logger.warning(
                "Access denied to endpoint",
                username=user_info["username"],
                role=user_info["role"],
                endpoint=f"{request.method} {path}",
                reason=reason,
            )

            return JSONResponse(
                status_code=403,
                content={
                    "error": "Access denied",
                    "message": reason,
                    "required_role": "Higher privileges required",
                },
            )

        request.state.user_token = user_token
        request.state.user_info = user_info
        request.state.auth_method = auth_method

        logger.debug(
            "Authentication and authorization successful",
            username=user_info["username"],
            role=user_info["role"],
            auth_method=auth_method,
            endpoint=f"{request.method} {path}",
        )

        response = await call_next(request)

        if hasattr(request.state, "user_info"):
            response.headers["X-User"] = request.state.user_info["username"]
            response.headers["X-User-Role"] = request.state.user_info["role"]
            response.headers["X-Auth-Method"] = request.state.auth_method

        return response

    except HTTPException:
        raise
    except Exception as e:
        logger.error(
            "Unexpected error in auth middleware",
            error=str(e),
            path=path,
            method=request.method,
        )

        return JSONResponse(
            status_code=500,
            content={
                "error": "Internal server error",
                "message": "Authentication system error",
            },
        )


def get_current_user(request: Request) -> Optional[Dict[str, Any]]:
    """
    Get current user information from request.state.

    Args:
        request: FastAPI Request object.

    Returns:
        User information dictionary or None.
    """
    return getattr(request.state, "user_info", None)


def get_current_user_token(request: Request) -> Optional[str]:
    """
    Get current user token from request.state.

    Args:
        request: FastAPI Request object.

    Returns:
        User token string or None.
    """
    return getattr(request.state, "user_token", None)


def require_role(required_role: UserRole) -> Callable:
    """
    Decorator to check user role.

    Args:
        required_role: Required user role.

    Returns:
        Function decorator.
    """
    def decorator(func: Callable) -> Callable:
        async def wrapper(request: Request, *args: Any, **kwargs: Any) -> Any:
            user_info = get_current_user(request)
            if not user_info:
                raise HTTPException(
                    status_code=401, detail="Authentication required"
                )

            user_role = UserRole(user_info["role"])
            permission = f"role_{required_role.value}"
            if not PermissionChecker.check_permission(user_role, permission):
                raise HTTPException(
                    status_code=403,
                    detail=f"Role {required_role.value} required",
                )

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

        return wrapper

    return decorator


def require_permission(permission: str) -> Callable:
    """
    Decorator to check specific permission.

    Args:
        permission: Required permission string.

    Returns:
        Function decorator.
    """
    def decorator(func: Callable) -> Callable:
        async def wrapper(request: Request, *args: Any, **kwargs: Any) -> Any:
            user_info = get_current_user(request)
            if not user_info:
                raise HTTPException(
                    status_code=401, detail="Authentication required"
                )

            user_role = UserRole(user_info["role"])
            if not PermissionChecker.check_permission(user_role, permission):
                raise HTTPException(
                    status_code=403,
                    detail=f"Permission '{permission}' required",
                )

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

        return wrapper

    return decorator


async def validate_connection_ownership(
    request: Request, connection_id: str
) -> bool:
    """
    Check user permissions for connection management.

    Args:
        request: FastAPI Request object.
        connection_id: Connection ID.

    Returns:
        True if user can manage the connection, False otherwise.
    """
    user_info = get_current_user(request)
    if not user_info:
        return False

    user_role = UserRole(user_info["role"])

    if PermissionChecker.can_delete_any_connection(user_role):
        return True

    return True 