"""
Generic container file handler for LiteLLM.

This module provides a single generic handler that can process any container file
endpoint defined in endpoints.json, eliminating the need for individual handler methods.
"""

import json
from collections.abc import Coroutine
from pathlib import Path
from typing import TYPE_CHECKING, Any, Final

import httpx

import litellm
from litellm.litellm_core_utils.url_utils import encode_url_path_segment
from litellm.llms.custom_httpx.http_handler import (
    AsyncHTTPHandler,
    HTTPHandler,
    _get_httpx_client,
    get_async_httpx_client,
)
from litellm.types.containers.main import (
    ContainerFileListResponse,
    ContainerFileObject,
    DeleteContainerFileResponse,
)
from litellm.types.router import GenericLiteLLMParams

if TYPE_CHECKING:
    from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
    from litellm.llms.base_llm.containers.transformation import BaseContainerConfig


# Response type mapping
RESPONSE_TYPES: Final[dict[str, type]] = {
    "ContainerFileListResponse": ContainerFileListResponse,
    "ContainerFileObject": ContainerFileObject,
    "DeleteContainerFileResponse": DeleteContainerFileResponse,
}

ContainerEndpointResponse = (
    ContainerFileListResponse | ContainerFileObject | DeleteContainerFileResponse | bytes | dict[str, object]
)


def _load_endpoints_config() -> dict:
    """Load the endpoints configuration from JSON file."""
    config_path: Final = Path(__file__).parent.parent.parent / "containers" / "endpoints.json"
    with open(config_path) as f:
        return json.load(f)


def _get_endpoint_config(endpoint_name: str) -> dict | None:
    """Get config for a specific endpoint by name."""
    config: Final = _load_endpoints_config()
    for endpoint in config["endpoints"]:
        if endpoint["name"] == endpoint_name or endpoint["async_name"] == endpoint_name:
            return endpoint
    return None


def _build_url(
    api_base: str,
    path_template: str,
    path_params: dict[str, str],
) -> str:
    """Build the full URL by substituting path parameters.

    The api_base from get_complete_url already includes /containers and may include
    query parameters. We need to parse the URL, append the path, then preserve the
    query parameters.
    """
    # api_base ends with /containers, path_template starts with /containers
    # So we need to strip /containers from the path
    path_template = path_template.removeprefix("/containers")

    # Substitute path parameters
    for param, value in path_params.items():
        encoded_value = encode_url_path_segment(value, field_name=param)
        path_template = path_template.replace(f"{{{param}}}", encoded_value)

    # Parse the api_base to extract existing query params
    parsed_base: Final = httpx.URL(api_base)

    # Append the path to the existing path (before query params)
    new_path: Final = f"{parsed_base.path.rstrip('/')}{path_template}"

    # Rebuild URL with new path, preserving query params
    final_url: Final = parsed_base.copy_with(path=new_path)

    return str(final_url)


def _build_query_params(
    query_param_names: list,
    kwargs: dict[str, Any],
) -> dict[str, str]:
    """Build query parameters from kwargs."""
    params: Final = {}
    for param_name in query_param_names:
        value = kwargs.get(param_name)
        if value is not None:
            params[param_name] = str(value) if not isinstance(value, str) else value
    return params


def _error_message_from_response(response: httpx.Response) -> str:
    try:
        body: Final = response.json()
    except ValueError:
        return response.text

    if isinstance(body, dict) and isinstance(body.get("error"), dict):
        message: Final = body["error"].get("message")
        if isinstance(message, str):
            return message

    return response.text


def _transform_response(
    response: httpx.Response,
    returns_binary: bool,
    response_type_name: str,
) -> ContainerEndpointResponse:
    from litellm.llms.base_llm.chat.transformation import BaseLLMException

    if httpx.codes.is_error(response.status_code):
        raise BaseLLMException(
            status_code=response.status_code,
            message=_error_message_from_response(response),
            headers=dict(response.headers),
        )

    if returns_binary:
        return response.content

    response_json: Final = response.json()
    if "error" in response_json:
        raise BaseLLMException(
            status_code=response.status_code,
            message=response_json.get("error", {}).get("message", str(response_json)),
            headers=dict(response.headers),
        )

    response_type: Final = RESPONSE_TYPES.get(response_type_name)
    if response_type:
        return response_type(**response_json)
    return response_json


def _prepare_multipart_file_upload(
    file: Any,
    headers: dict[str, Any],
) -> tuple:
    """
    Prepare file and headers for multipart upload.

    Returns:
        Tuple of (files_dict, headers_without_content_type)
    """
    from litellm.litellm_core_utils.prompt_templates.common_utils import (
        extract_file_data,
    )

    extracted: Final = extract_file_data(file)
    filename: Final = extracted.get("filename") or "file"
    content: Final = extracted.get("content") or b""
    content_type: Final = extracted.get("content_type") or "application/octet-stream"
    files: Final = {"file": (filename, content, content_type)}

    # Remove content-type header - httpx will set it automatically for multipart
    headers_copy: Final = headers.copy()
    headers_copy.pop("content-type", None)
    headers_copy.pop("Content-Type", None)

    return files, headers_copy


class GenericContainerHandler:
    """
    Generic handler for container file API endpoints.

    This single handler can process any endpoint defined in endpoints.json,
    eliminating the need for individual handler methods per endpoint.
    """

    def handle(
        self,
        endpoint_name: str,
        container_provider_config: "BaseContainerConfig",
        litellm_params: GenericLiteLLMParams,
        logging_obj: "LiteLLMLoggingObj",
        extra_headers: dict[str, Any] | None = None,
        extra_query: dict[str, Any] | None = None,
        timeout: float | httpx.Timeout = 600,
        _is_async: bool = False,
        client: HTTPHandler | AsyncHTTPHandler | None = None,
        **kwargs,
    ) -> Any | Coroutine[Any, Any, Any]:
        """
        Generic handler for any container file endpoint.

        Args:
            endpoint_name: Name of the endpoint (e.g., "list_container_files")
            container_provider_config: Provider-specific configuration
            litellm_params: LiteLLM parameters including api_key, api_base
            logging_obj: Logging object for request logging
            extra_headers: Additional HTTP headers
            extra_query: Additional query parameters
            timeout: Request timeout
            _is_async: Whether to make async request
            client: Optional HTTP client
            **kwargs: Path params and query params (e.g., container_id, file_id, after, limit)
        """
        if _is_async:
            return self._async_handle(
                endpoint_name=endpoint_name,
                container_provider_config=container_provider_config,
                litellm_params=litellm_params,
                logging_obj=logging_obj,
                extra_headers=extra_headers,
                extra_query=extra_query,
                timeout=timeout,
                client=client,
                **kwargs,
            )

        return self._sync_handle(
            endpoint_name=endpoint_name,
            container_provider_config=container_provider_config,
            litellm_params=litellm_params,
            logging_obj=logging_obj,
            extra_headers=extra_headers,
            extra_query=extra_query,
            timeout=timeout,
            client=client,
            **kwargs,
        )

    def _sync_handle(
        self,
        endpoint_name: str,
        container_provider_config: "BaseContainerConfig",
        litellm_params: GenericLiteLLMParams,
        logging_obj: "LiteLLMLoggingObj",
        extra_headers: dict[str, Any] | None = None,
        extra_query: dict[str, Any] | None = None,
        timeout: float | httpx.Timeout = 600,
        client: HTTPHandler | AsyncHTTPHandler | None = None,
        **kwargs,
    ) -> Any:
        """Synchronous request handler."""
        endpoint_config: Final = _get_endpoint_config(endpoint_name)
        if not endpoint_config:
            raise ValueError(f"Unknown endpoint: {endpoint_name}")

        # Get HTTP client
        if client is None or not isinstance(client, HTTPHandler):
            http_client = _get_httpx_client(params={"ssl_verify": litellm_params.get("ssl_verify", None)})
        else:
            http_client = client

        # Build request
        headers = container_provider_config.validate_environment(
            headers=extra_headers or {},
            api_key=litellm_params.get("api_key", None),
        )
        if extra_headers:
            headers.update(extra_headers)

        api_base: Final = container_provider_config.get_complete_url(
            api_base=litellm_params.get("api_base", None),
            litellm_params=dict(litellm_params),
        )

        # Build URL with path params
        path_params: Final = {p: kwargs.get(p, "") for p in endpoint_config.get("path_params", [])}
        url: Final = _build_url(api_base, endpoint_config["path"], path_params)

        # Build query params
        query_params: Final = _build_query_params(endpoint_config.get("query_params", []), kwargs)
        if extra_query:
            query_params.update(extra_query)

        # Log request
        logging_obj.pre_call(
            input="",
            api_key="",
            additional_args={
                "api_base": url,
                "headers": headers,
                "params": query_params,
            },
        )

        # Make request
        method: Final = endpoint_config["method"].upper()
        returns_binary: Final = endpoint_config.get("returns_binary", False)
        is_multipart: Final = endpoint_config.get("is_multipart", False)

        # An empty dict passed as `params` to httpx strips any existing query
        # string from the URL (e.g. ?api-version=...).  Use None instead so
        # httpx leaves the URL's own query string intact.
        effective_params: Final = query_params or None

        try:
            if method == "GET":
                response = http_client.get(url=url, headers=headers, params=effective_params)
            elif method == "DELETE":
                response = http_client.delete(url=url, headers=headers, params=effective_params)
            elif method == "POST":
                if is_multipart and "file" in kwargs:
                    files, headers = _prepare_multipart_file_upload(kwargs["file"], headers)
                    response = http_client.post(url=url, headers=headers, params=effective_params, files=files)
                else:
                    response = http_client.post(url=url, headers=headers, params=effective_params)
            else:
                raise ValueError(f"Unsupported HTTP method: {method}")

            return _transform_response(
                response=response,
                returns_binary=returns_binary,
                response_type_name=endpoint_config["response_type"],
            )

        except Exception as e:
            raise e

    async def _async_handle(
        self,
        endpoint_name: str,
        container_provider_config: "BaseContainerConfig",
        litellm_params: GenericLiteLLMParams,
        logging_obj: "LiteLLMLoggingObj",
        extra_headers: dict[str, Any] | None = None,
        extra_query: dict[str, Any] | None = None,
        timeout: float | httpx.Timeout = 600,
        client: HTTPHandler | AsyncHTTPHandler | None = None,
        **kwargs,
    ) -> Any:
        """Asynchronous request handler."""
        endpoint_config: Final = _get_endpoint_config(endpoint_name)
        if not endpoint_config:
            raise ValueError(f"Unknown endpoint: {endpoint_name}")

        # Get HTTP client
        if client is None or not isinstance(client, AsyncHTTPHandler):
            http_client = get_async_httpx_client(
                llm_provider=litellm.LlmProviders.OPENAI,
                params={"ssl_verify": litellm_params.get("ssl_verify", None)},
            )
        else:
            http_client = client

        # Build request
        headers = container_provider_config.validate_environment(
            headers=extra_headers or {},
            api_key=litellm_params.get("api_key", None),
        )
        if extra_headers:
            headers.update(extra_headers)

        api_base: Final = container_provider_config.get_complete_url(
            api_base=litellm_params.get("api_base", None),
            litellm_params=dict(litellm_params),
        )

        # Build URL with path params
        path_params: Final = {p: kwargs.get(p, "") for p in endpoint_config.get("path_params", [])}
        url: Final = _build_url(api_base, endpoint_config["path"], path_params)

        # Build query params
        query_params: Final = _build_query_params(endpoint_config.get("query_params", []), kwargs)
        if extra_query:
            query_params.update(extra_query)

        # Log request
        logging_obj.pre_call(
            input="",
            api_key="",
            additional_args={
                "api_base": url,
                "headers": headers,
                "params": query_params,
            },
        )

        # Make request
        method: Final = endpoint_config["method"].upper()
        returns_binary: Final = endpoint_config.get("returns_binary", False)
        is_multipart: Final = endpoint_config.get("is_multipart", False)

        # An empty dict passed as `params` to httpx strips any existing query
        # string from the URL (e.g. ?api-version=...).  Use None instead so
        # httpx leaves the URL's own query string intact.
        effective_params: Final = query_params or None

        try:
            if method == "GET":
                response = await http_client.get(url=url, headers=headers, params=effective_params)
            elif method == "DELETE":
                response = await http_client.delete(url=url, headers=headers, params=effective_params)
            elif method == "POST":
                if is_multipart and "file" in kwargs:
                    files, headers = _prepare_multipart_file_upload(kwargs["file"], headers)
                    response = await http_client.post(url=url, headers=headers, params=effective_params, files=files)
                else:
                    response = await http_client.post(url=url, headers=headers, params=effective_params)
            else:
                raise ValueError(f"Unsupported HTTP method: {method}")

            return _transform_response(
                response=response,
                returns_binary=returns_binary,
                response_type_name=endpoint_config["response_type"],
            )

        except Exception as e:
            raise e


# Singleton instance
generic_container_handler: Final = GenericContainerHandler()
