import logging
from typing import Optional

from requests import Response, Session as BaseSession
from requests.adapters import HTTPAdapter
from requests.exceptions import RequestException
from urllib3.util import Retry

logger = logging.getLogger(__name__)

DEFAULT_TIMEOUT = 5


class TimeoutHTTPAdapter(HTTPAdapter):
    timeout: int = DEFAULT_TIMEOUT

    def __init__(self, *args, **kwargs):
        if "timeout" in kwargs:
            self.timeout = kwargs["timeout"]
            del kwargs["timeout"]
        super().__init__(*args, **kwargs)

    def send(self, request, *args, **kwargs):
        if "timeout" not in kwargs:
            kwargs["timeout"] = self.timeout
        return super().send(request, *args, **kwargs)


def default_retry(max_retries: int = 5):
    return Retry(
        total=max_retries,
        backoff_factor=2,
        status_forcelist=[413, 429, 500, 502, 503, 504],
    )


class Session(BaseSession):
    base_url: Optional[str]

    def __init__(
        self,
        base_url: Optional[str] = None,
        retry: Optional[Retry] = None,
    ):
        super().__init__()
        self.base_url = base_url

        adapter = TimeoutHTTPAdapter(max_retries=retry)

        self.mount("http://", adapter)
        self.mount("https://", adapter)

    def request(self, method, url, *args, **kwargs):
        """Send the request after generating the complete URL."""
        url = self.create_url(url)
        return super().request(method, url, *args, **kwargs)

    def create_url(self, url):
        """Create the URL based off this partial path."""
        if self.base_url is None:
            return url
        return f"{self.base_url.rstrip('/')}/{url.lstrip('/')}"


# pylint: disable=too-few-public-methods
class AbstractApiClient:
    session: Session
    base_url: str

    def __init__(
        self,
        base_url: str,
        retry: Optional[Retry] = None,
    ):
        self.base_url = base_url
        self.session = Session(
            base_url=base_url,
            retry=retry,
        )

    def _request(
        self,
        method,
        url,
        **kwargs,
    ) -> Response:
        try:
            response = self.session.request(method, url, **kwargs)
            response.raise_for_status()
            return response

        except RequestException as exception:
            logger.error(exception)
            raise exception