import asyncio
import time
from typing import Literal, overload

import httpx

# Throw an exception whenever the response code is 4xx or 5xx


def response_hook(response: httpx.Response) -> None:
    if response.is_error:
        response.read()
        response.raise_for_status()


async def response_hook_async(response: httpx.Response) -> None:
    if response.is_error:
        await response.aread()
        response.raise_for_status()


class RetryTransport(httpx.BaseTransport):
    def __init__(self, **kwargs):
        self._wrapper = httpx.HTTPTransport(**kwargs)

    def handle_request(self, request):
        response = self._wrapper.handle_request(request)
        if response.status_code == 429:
            retry_header = response.headers.get("retry-after", None)
            delay = int(retry_header) if retry_header else 0.5
            time.sleep(delay)
            return self.handle_request(request)
        return response

    def close(self):
        self._wrapper.close()


class AsyncRetryTransport(httpx.AsyncBaseTransport):
    def __init__(self, **kwargs):
        self._wrapper = httpx.AsyncHTTPTransport(**kwargs)

    async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
        response = await self._wrapper.handle_async_request(request)
        if response.status_code == 429:
            retry_header = response.headers.get("retry-after")
            delay = float(retry_header) if retry_header else 0.5
            await response.aread()
            await asyncio.sleep(delay)
            return await self.handle_async_request(request)
        return response

    async def aclose(self) -> None:
        await self._wrapper.aclose()


@overload
def create_client(
    lusid_url: str,
    token: str,
    *,
    is_async: Literal[False] = False,
) -> httpx.Client: ...


@overload
def create_client(
    lusid_url: str,
    token: str,
    *,
    is_async: Literal[True],
) -> httpx.AsyncClient: ...


def create_client(
    lusid_url: str,
    token: str,
    *,
    is_async: bool = False,
):
    if not token or not lusid_url:
        raise RuntimeError("Both FBN_ACCESS_TOKEN and LUSID_ENV variables need to be set")

    headers = {
        "authorization": f"Bearer {token}",
        "accept": "application/json",
        "content-type": "application/json",
        "X-LUSID-Application": "fbnconfig",
    }

    if is_async:
        return httpx.AsyncClient(
            base_url=lusid_url,
            timeout=60,
            headers=headers,
            event_hooks={"response": [response_hook_async]},
            transport=AsyncRetryTransport(),
        )

    return httpx.Client(
        base_url=lusid_url,
        timeout=60,
        headers=headers,
        event_hooks={"response": [response_hook]},
        transport=RetryTransport(),
    )
