from typing import Dict, Any, List, Optional

from fastapi import APIRouter, Request
from fastapi import FastAPI
from fastapi.routing import APIRoute
from loguru import logger
from starlette import status
from starlette.responses import RedirectResponse
from starlette.routing import WebSocketRoute
from urllib.parse import urlencode


def _has_auth_dependency(route) -> bool:
    """Check if a route has require_authentication in its dependency chain."""
    from fastpluggy.core.auth import require_authentication
    for dep in getattr(route, 'dependencies', []):
        call = getattr(dep, 'dependency', None)
        if call is require_authentication:
            return True
    # Also check dependant.dependencies (injected via Depends in function signature)
    dependant = getattr(route, 'dependant', None)
    if dependant:
        for dep in getattr(dependant, 'dependencies', []):
            call = getattr(dep, 'call', None)
            if call is require_authentication:
                return True
    return False


def _extract_route_metadata(route) -> Dict[str, Any]:
    """Extract metadata from a route, including parameter types and default.

    Args:
        route: The route to inspect (APIRoute or WebSocketRoute).

    Returns:
        Dict[str, Any]: A dictionary containing route metadata:
            - name (str): The route name
            - path (str): The route path
            - methods (List[str]): HTTP methods supported by the route or ["websocket"] for WebSocket routes
            - path_params (List[Dict[str, str]]): List of path parameter names,types,default
            - query_params (List[Dict[str, str]]): List of query parameter names,types,default
            - body_params (List[Dict[str, str]]): List of body parameter names,types,default
            - auth (bool): Whether the route requires authentication
    """
    def param_info(param) -> Dict[str, Any]:
        type_obj = getattr(param, 'type_', None)
        if type_obj is None:
            type_obj = getattr(getattr(param, 'field_info', None), 'annotation', None)
        return {
            "name": param.name,
            "type": type_obj.__name__ if hasattr(type_obj, '__name__') else str(type_obj),
            "default": param.default if param.default != ... else None  # Ellipsis means required
        }

    # Handle WebSocketRoute
    if isinstance(route, WebSocketRoute):
        return {
            "name": route.name,
            "path": route.path,
            "methods": ["websocket"],  # Use "websocket" as the method for WebSocket routes
            "path_params": [],  # WebSocketRoute doesn't have dependant attribute
            "query_params": [],
            "body_params": [],
            "auth": False,
        }

    # Handle APIRoute
    return {
        "name": route.name,
        "path": route.path,
        "methods": [method.lower() for method in route.methods],
        "path_params": [param_info(param) for param in route.dependant.path_params],
        "query_params": [param_info(param) for param in route.dependant.query_params],
        "body_params": [param_info(param) for param in route.dependant.body_params],
        "auth": _has_auth_dependency(route),
    }



def inspect_fastapi_route(app: FastAPI, route_name: str) -> Dict[str, Any]:
    """Inspect a specific FastAPI route to determine its HTTP method and parameters.

    Args:
        app (FastAPI): The FastAPI application instance.
        route_name (str): The name of the route to inspect.

    Returns:
        Dict[str, Any]: A dictionary containing route metadata including:
            - name (str): The route name
            - path (str): The route path
            - methods (List[str]): Lowercase HTTP methods supported by the route
            - path_params (List[str]): Names of path parameters
            - query_params (List[str]): Names of query parameters
            - body_params (List[str]): Names of request body parameters

    Raises:
        ValueError: If the route is not found or no HTTP methods are defined.
    """
    route = next(
        (r for r in app.routes if isinstance(r, APIRoute) and r.name == route_name),
        None
    )
    if not route:
        raise ValueError(f"Route '{route_name}' not found in the application.")
    return _extract_route_metadata(route)


def list_router_routes(router: APIRouter) -> List[Dict[str, Any]]:
    """Inspect all routes of an APIRouter to determine their HTTP methods, paths, and parameters.

    Args:
        router (APIRouter): The FastAPI APIRouter instance.

    Returns:
        List[Dict[str, Any]]: A list of dictionaries containing route metadata for each route, including:
            - name (str): The route name
            - path (str): The route path
            - methods (List[str]): Lowercase HTTP methods supported by the route or ["websocket"] for WebSocket routes
            - path_params (List[str]): Names of path parameters
            - query_params (List[str]): Names of query parameters
            - body_params (List[str]): Names of request body parameters
    """
    routes_metadata: List[Dict[str, Any]] = []

    # Check if the router itself has require_authentication as a dependency
    router_has_auth = _has_auth_dependency(router)

    if hasattr(router, 'routes'):
        for route in router.routes:
            if not isinstance(route, (APIRoute, WebSocketRoute)):
                continue

            try:
                metadata = _extract_route_metadata(route)
                # Inherit auth from parent router if not set on route itself
                if router_has_auth and not metadata.get('auth'):
                    metadata['auth'] = True
                routes_metadata.append(metadata)
            except Exception as e:
                logger.warning(f"Failed to extract metadata for route {getattr(route, 'path', 'unknown')}: {e}")
    else:
        logger.warning(f"Router {router} does not have routes attribute (type): {type(router)}")
    return routes_metadata


def redirect_to_previous(request: Request, endpoint_name: str = 'fast_pluggy_home') -> RedirectResponse:
    """Redirect to the previous page based on the 'Referer' header.

    Args:
        request (Request): The incoming request object.

    Returns:
        RedirectResponse: A response that redirects to the previous page or a default page if 'referer' is not present.

    Raises:
        ValueError: If neither 'referer' nor any other method can be used to determine the redirect.
    """
    referer = request.headers.get("referer")

    if referer:
        # Redirect to the previous page
        return RedirectResponse(url=referer, status_code=status.HTTP_303_SEE_OTHER)
    else:
        # Fallback: Redirect to a default page if 'referer' is not available
        url = request.url_for(endpoint_name)
        return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)


def url_for_with_query(request: Request, name: str, **params) -> str:
    """Generate a URL with optional query parameters."""
    # Extract query params (not path params)
    path_params = {}
    query_params = {}

    # Separate between path and query params
    route = request.app.url_path_for(name)
    for key, value in params.items():
        if "{" + key + "}" in route:
            path_params[key] = value
        else:
            query_params[key] = value

    url = str(request.url_for(name, **path_params))
    if query_params:
        url += "?" + urlencode({k: v for k, v in query_params.items() if v is not None})
    return url


def get_client_ip(request: Request) -> Optional[str]:
    """Extract the client IP address from the request, respecting X-Forwarded-For."""
    forwarded = request.headers.get("X-Forwarded-For")
    if forwarded:
        return forwarded.split(",")[0].strip()
    return request.client.host if request.client else None
