from json import JSONDecodeError

from requests import request
from tenacity import retry, retry_if_exception, stop_after_attempt, wait_exponential

from chainalysis._exceptions import (
    BadRequest,
    DataSolutionsAPIException,
    ForbiddenException,
    NotFoundException,
    RateLimitExceededException,
    UnauthorizedException,
)

def get_headers(api_key: str) -> dict:
    """
    Generate headers for an HTTP request.

    This function creates a dictionary of headers required for an HTTP request,
    including the API key for authorization and JSON content-type headers which
    indicate what sort of information will be sent with the request.

    :param api_key: The API key used for authenticating the request.
    :type api_key: str
    :return: A dictionary containing the headers for the HTTP request.
    :rtype: dict
    """
    return {
        "X-API-KEY": api_key,
        "Content-Type": "application/json",
        "Accept": "application/json",
        "X-Request-Source": "chainalysis-python-sdk",
    }

def add_headers(headers: dict, new_headers) -> dict:
    """
    Add additional headers to the existing headers dictionary.

    This function adds a custom header to the provided headers dictionary,
    which can be useful for tracking the source of requests or for other
    purposes.

    :param headers: The existing headers dictionary.
    :type headers: dict
    :return: The updated headers dictionary with the new header added.
    :rtype: dict
    """
    for key, value in new_headers.items():
        if key not in headers:
            headers[key] = value

def retry_condition(exception):
    if isinstance(
        exception,
        (BadRequest, UnauthorizedException, ForbiddenException, NotFoundException),
    ):
        return False
    return True


@retry(
    wait=wait_exponential(multiplier=1, min=4, max=10),
    retry=retry_if_exception(retry_condition),
    stop=stop_after_attempt(10),
)
def issue_request(
    url: str,
    api_key: str,
    params: dict = {},
    body: dict = {},
    method: str = "GET",
    headers: dict = {},
) -> dict:
    """
    Helper method to issue a request to the Data Solutions API.
    This method will automatically retry the request, and handle
    basic error checking.

    :param url: The URL to send the request to.
    :type url: str
    :param api_key: The API key used for authenticating the request.
    :type api_key: str
    :param params: The parameters to send with the request.
    :type params: dict, optional
    :param body: The body of the request.
    :type body: dict, optional
    :param method: The HTTP method to use for the request.
    :type method: str, optional
    :return: The JSON response from the API.
    :rtype: dict
    """
    # Encode params and body into a single variable data
    data = {**(params or {}), **(body or {})}

    headers = get_headers(api_key)

    add_headers(headers, headers)

    response = request(
        method,
        url,
        headers=headers,
        json=data,
    )

    if response.status_code >= 300:
        try:  # Try to decode the json response of the error
            json_output = response.json()
            error_message = json_output.get("message")

        except JSONDecodeError:
            raise DataSolutionsAPIException(
                message="Unexpected response from the API - response was not JSON",
            )
        if response.status_code == 400:
            raise BadRequest(
                message=error_message,
            )
        if response.status_code == 401:
            raise UnauthorizedException(
                message=error_message,
            )
        if response.status_code == 403:
            raise ForbiddenException(
                message=error_message,
            )
        if response.status_code == 404:
            raise NotFoundException(
                message=error_message,
            )
        if response.status_code == 429:
            raise RateLimitExceededException(
                message=error_message,
            )
        raise DataSolutionsAPIException(
            message=error_message,
        )

    try:  # Try to decode the json response of the successful request
        response_json = response.json()
    except JSONDecodeError:
        raise DataSolutionsAPIException(
            message="Unexpected response from the API - response was not JSON",
        )

    return response_json
