from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, TypeAlias

from relationalai.runtime.retry_classification import _is_retryable_http_status

from .endpoint_stale import is_endpoint_stale_exception, is_endpoint_stale_payload
from .http_util import build_url

if TYPE_CHECKING:
    import requests

    from relationalai.client.transports.http import AsyncHttpClient, HttpClient

    HttpResponse: TypeAlias = requests.Response
else:
    HttpResponse: TypeAlias = Any


BaseUrlProvider: TypeAlias = Callable[[bool], str]
DA_BASE_URL_PROVIDER_META_KEY = "da_base_url_provider"


def _response_status_is_retryable(status: object) -> bool:
    return _is_retryable_http_status(status)


def _merge_request_meta(
    meta: dict[str, Any] | None,
    *,
    base_url_provider: BaseUrlProvider,
    allow_404: bool,
) -> dict[str, Any]:
    """Attach shared DA request metadata used by middleware and stale-endpoint retry.

    We always pass the base URL provider through request metadata so downstream
    layers can force an endpoint refresh without each caller wiring that by hand.
    `allow_404` is normalized here too so sync/async request paths follow the
    same contract.
    """
    req_meta = dict(meta or {})
    req_meta.setdefault(DA_BASE_URL_PROVIDER_META_KEY, base_url_provider)
    if allow_404:
        req_meta["allow_404"] = True
    return req_meta


def request_sync(
    *,
    http: "HttpClient",
    operation: str,
    method: str,
    base_url_provider: BaseUrlProvider,
    endpoint: str,
    path_params: dict[str, Any] | None = None,
    query_params: dict[str, Any] | None = None,
    payload: dict[str, Any] | None = None,
    headers: dict[str, str] | None = None,
    meta: dict[str, Any] | None = None,
    allow_404: bool = False,
) -> HttpResponse:
    """Perform a sync DA request with shared stale-endpoint retry behavior."""

    def _do_request(enforce_update: bool) -> HttpResponse:
        resolved_base = str(base_url_provider(bool(enforce_update))).rstrip("/")
        url = build_url(resolved_base, endpoint, path_params=path_params, query_params=query_params)
        req_meta = _merge_request_meta(meta, base_url_provider=base_url_provider, allow_404=allow_404)
        return http.request(operation=operation, method=method, url=url, payload=payload, headers=headers, meta=req_meta)

    try:
        resp = _do_request(enforce_update=False)
    except Exception as e:
        if not is_endpoint_stale_exception(e):
            raise
        return _do_request(enforce_update=True)

    if getattr(resp, "status_code", None) != 200:
        try:
            err_payload = resp.json()
        except Exception:
            err_payload = getattr(resp, "text", None) or ""

        # Generic HTTP retry middleware already handles transient *exceptions*
        # (timeouts, connection errors, etc.). This helper adds a single extra
        # retry for transient HTTP statuses that arrive as normal responses,
        # because those do not raise and therefore bypass the middleware retry.
        if is_endpoint_stale_payload(err_payload):
            return _do_request(enforce_update=True)
        if _response_status_is_retryable(getattr(resp, "status_code", None)):
            return _do_request(enforce_update=False)

    return resp


async def request_async(
    *,
    http: "AsyncHttpClient",
    operation: str,
    method: str,
    base_url_provider: BaseUrlProvider,
    endpoint: str,
    path_params: dict[str, Any] | None = None,
    query_params: dict[str, Any] | None = None,
    payload: dict[str, Any] | None = None,
    headers: dict[str, str] | None = None,
    meta: dict[str, Any] | None = None,
    allow_404: bool = False,
) -> tuple[int, Any]:
    """Perform an async DA request with shared stale-endpoint retry behavior."""

    async def _do_request(enforce_update: bool) -> tuple[int, Any]:
        resolved_base = str(base_url_provider(bool(enforce_update))).rstrip("/")
        url = build_url(resolved_base, endpoint, path_params=path_params, query_params=query_params)
        req_meta = _merge_request_meta(meta, base_url_provider=base_url_provider, allow_404=allow_404)
        return await http.request(
            operation=operation,
            method=method,
            url=url,
            payload=payload,
            headers=headers,
            meta=req_meta,
        )

    try:
        status, content = await _do_request(enforce_update=False)
    except Exception as e:
        if not is_endpoint_stale_exception(e):
            raise
        return await _do_request(enforce_update=True)

    if int(status) != 200:
        # As in the sync path above, keep helper-level retries narrow: only do
        # one extra attempt for transient response statuses, and leave
        # exception-based retries to the generic HTTP middleware.
        if is_endpoint_stale_payload(content):
            return await _do_request(enforce_update=True)
        if _response_status_is_retryable(int(status)):
            return await _do_request(enforce_update=False)

    return int(status), content


__all__ = ["BaseUrlProvider", "DA_BASE_URL_PROVIDER_META_KEY", "request_sync", "request_async"]
