# coding: utf-8

"""
    Rapidata.Dataset

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

    The version of the OpenAPI document: v1
    Generated by OpenAPI Generator (https://openapi-generator.tech)

    Do not edit the class manually.
"""  # noqa: E501

import io
import json
import re
from typing import Dict, Optional

import requests
from authlib.integrations.requests_client import OAuth2Session
from requests.adapters import HTTPAdapter
from urllib3 import Retry

from rapidata.api_client.exceptions import ApiException, ApiValueError


class RESTResponse(io.IOBase):

    def __init__(self, resp: requests.Response) -> None:
        self.response = resp
        self.status = resp.status_code
        self.reason = resp.reason
        self.data = None

    def read(self):
        if self.data is None:
            self.data = self.response.content
        return self.data

    def getheaders(self) -> Dict[str, str]:
        """Returns a dictionary of the response headers."""
        return dict(self.response.headers)

    def getheader(self, name, default=None) -> Optional[str]:
        """Returns a given response header."""
        return self.response.headers.get(name, default)


RESTResponseType = RESTResponse


class RESTClientObject:

    def __init__(self, configuration) -> None:
        self.configuration = configuration

        self.session: Optional[OAuth2Session] = None

    def setup_oauth_client_credentials(
            self, client_id: str, client_secret: str, token_endpoint: str, scope: str
    ):
        self.session = OAuth2Session(
            client_id=client_id,
            client_secret=client_secret,
            token_endpoint=token_endpoint,
            scope=scope,
        )
        self._configure_session_defaults()
        self.session.fetch_token()

    def setup_oauth_with_token(self,
                               client_id: str | None,
                               client_secret: str | None,
                               token: dict,
                               token_endpoint: str,
                               leeway: int = 60):
        self.session = OAuth2Session(
            token=token,
            token_endpoint=token_endpoint,
            client_id=client_id,
            client_secret=client_secret,
            leeway=leeway,
        )
        self._configure_session_defaults()

    def request(
            self,
            method,
            url,
            headers=None,
            body=None,
            post_params=None,
            _request_timeout=None,
    ):
        """Perform requests.

        :param method: http request method
        :param url: http request url
        :param headers: http request headers
        :param body: request json body, for `application/json`
        :param post_params: request post parameters,
                            `application/x-www-form-urlencoded`
                            and `multipart/form-data`
        :param _request_timeout: timeout setting for this request. If one
                                 number provided, it will be total request
                                 timeout. It can also be a pair (tuple) of
                                 (connection, read) timeouts.
        """
        method = method.upper()
        assert method in ["GET", "HEAD", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"]

        if post_params and body:
            raise ApiValueError(
                "body parameter cannot be used with post_params parameter."
            )

        post_params = post_params or {}
        headers = headers or {}

        if not self.session:
            raise ApiValueError(
                "OAuth2 session is not initialized. Please initialize it before making requests."
            )

        session = self.session

        timeout = None
        if _request_timeout:
            if isinstance(_request_timeout, (int, float)):
                timeout = _request_timeout
            elif isinstance(_request_timeout, tuple) and len(_request_timeout) == 2:
                timeout = _request_timeout

        try:
            if method in ["POST", "PUT", "PATCH", "OPTIONS", "DELETE"]:
                content_type = headers.get("Content-Type")

                if not content_type or re.search("json", content_type, re.IGNORECASE):
                    request_body = None
                    if body is not None:
                        request_body = json.dumps(body)
                    r = session.request(method, url, data=request_body, timeout=timeout, headers=headers)

                elif content_type == 'application/x-www-form-urlencoded':
                    r = session.request(method, url, data=post_params, timeout=timeout, headers=headers)

                elif content_type == 'multipart/form-data':
                    del headers['Content-Type']
                    files = []
                    data = {}

                    for key, value in post_params:
                        if isinstance(value, tuple) and len(value) >= 2:
                            # This is a file tuple (filename, file_data, [content_type])
                            filename, file_data = value[0], value[1]
                            content_type = value[2] if len(value) > 2 else None
                            files.append((key, (filename, file_data, content_type)))
                        elif isinstance(value, dict):
                            # JSON-serialize dictionary values
                            if key in data:
                                # If we already have this key, handle as needed
                                # (convert to list or append to existing list)
                                if not isinstance(data[key], list):
                                    data[key] = [data[key]]
                                data[key].append(json.dumps(value))
                            else:
                                data[key] = json.dumps(value)
                        else:
                            # Regular form data
                            if key in data:
                                if not isinstance(data[key], list):
                                    data[key] = [data[key]]
                                data[key].append(value)
                            else:
                                data[key] = value
                    r = session.request(method, url, files=files, data=data, timeout=timeout, headers=headers)

                elif isinstance(body, str) or isinstance(body, bytes):
                    r = session.request(method, url, data=body, timeout=timeout, headers=headers)

                elif headers['Content-Type'].startswith('text/') and isinstance(body, bool):
                    request_body = 'true' if body else 'false'
                    r = session.request(method, url, data=request_body, timeout=timeout, headers=headers)

                else:
                    msg = '''Cannot prepare a request message for provided arguments.
                             Please check that your arguments match declared content type.'''
                    raise ApiException(status=0, reason=msg)

            else:
                r = session.request(method, url, params={}, timeout=timeout, headers=headers)

        except requests.exceptions.SSLError as e:
            msg = '\n'.join([type(e).__name__, str(e)])
            raise ApiException(status=0, reason=msg)

        return RESTResponse(r)

    def _configure_session_defaults(self):
        self.session.verify = (
            self.configuration.ssl_ca_cert
            if self.configuration.ssl_ca_cert
            else self.configuration.verify_ssl
        )

        if self.configuration.cert_file and self.configuration.key_file:
            self.session.cert = (
                self.configuration.cert_file,
                self.configuration.key_file,
            )

        if self.configuration.retries is not None:
            retry = Retry(
                total=self.configuration.retries,
                backoff_factor=0.3,
                status_forcelist=[429, 500, 502, 503, 504],
            )
            adapter = HTTPAdapter(max_retries=retry)
            self.session.mount("https://", adapter)
            # noinspection HttpUrlsUsage
            self.session.mount("http://", adapter)

        if self.configuration.proxy:
            self.session.proxies = {
                "http": self.configuration.proxy,
                "https": self.configuration.proxy,
            }

            if self.configuration.proxy_headers:
                for key, value in self.configuration.proxy_headers.items():
                    self.session.headers[key] = value
