# 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 httpx
from authlib.integrations.httpx_client import OAuth2Client
from httpx import Timeout, ConnectError

from rapidata.api_client.exceptions import ApiException, ApiValueError


class RESTResponse(io.IOBase):

    def __init__(self, resp: httpx.Response) -> None:
        self.response = resp
        self.status = resp.status_code
        self.reason = resp.reason_phrase
        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[OAuth2Client] = None

    def setup_oauth_client_credentials(
        self, client_id: str, client_secret: str, token_endpoint: str, scope: str
    ):
        client_args = self._get_session_defaults()
        self.session = OAuth2Client(
            client_id=client_id,
            client_secret=client_secret,
            token_endpoint=token_endpoint,
            scope=scope,
            **client_args,
        )

        try:
            self.session.fetch_token()
        except ConnectError as e:
            if self._is_certificate_validation_error(e):
                exit(self._get_ssl_verify_error_message())
            else:
                raise

    def setup_oauth_with_token(
        self,
        client_id: str | None,
        client_secret: str | None,
        token: dict,
        token_endpoint: str,
        leeway: int = 60,
    ):
        client_args = self._get_session_defaults()
        self.session = OAuth2Client(
            token=token,
            token_endpoint=token_endpoint,
            client_id=client_id,
            client_secret=client_secret,
            leeway=leeway,
            **client_args,
        )

    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 = Timeout(timeout=_request_timeout)
            elif isinstance(_request_timeout, tuple) and len(_request_timeout) == 2:
                connect_timeout, read_timeout = _request_timeout
                timeout = Timeout(timeout=connect_timeout, read=read_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,
                        content=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, content=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,
                        content=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, timeout=timeout, headers=headers)

        except httpx.HTTPError as e:
            msg = "\n".join([type(e).__name__, str(e)])
            raise ApiException(status=0, reason=msg)

        except ConnectError as e:
            if self._is_certificate_validation_error(e):
                exit(self._get_ssl_verify_error_message())
            else:
                raise

        return RESTResponse(r)

    def _get_session_defaults(self):
        client_kwargs = {
            "verify": (
                self.configuration.ssl_ca_cert
                if self.configuration.ssl_ca_cert
                else self.configuration.verify_ssl
            )
        }

        if self.configuration.proxy:
            client_kwargs["proxy"] = self.configuration.proxy

            existing_headers = client_kwargs.pop("headers")
            if self.configuration.proxy_headers:
                for key, value in self.configuration.proxy_headers.items():
                    existing_headers[key] = value
            client_kwargs["headers"] = existing_headers

        if self.configuration.retries is not None:
            transport = httpx.HTTPTransport(retries=self.configuration.retries)
            client_kwargs["transport"] = transport

        return client_kwargs

    @staticmethod
    def _is_certificate_validation_error(error: ConnectError) -> bool:
        """
        Check if the error is related to certificate validation.
        """
        return error.args[0].startswith("[SSL: CERTIFICATE_VERIFY_FAILED]")

    @staticmethod
    def _get_ssl_verify_error_message() -> str:
        return """
        We encountered an issue while trying to verify the SSL certificate.
        This often happens on macOS when using the default Python installation provided by Apple,
        which lacks the required certificates to perform secure HTTPS requests.
        To resolve this, please perform the following steps:
        1. Make sure you are using the latest version of the Rapidata Package: `pip install --upgrade rapidata`
        2. If you are using the default Python installation from Apple, consider switching to a different Python distribution, such as Homebrew or pyenv.
        3. If you prefer to continue using the default Python, you can run the following command in your terminal:
           `/Applications/Python\\ 3.1*/Install\\ Certificates.command`
        
        For more details on this issue, please refer to the following link:
        https://stackoverflow.com/questions/42098126/mac-osx-python-ssl-sslerror-ssl-certificate-verify-failed-certificate-verify
        
        If the issue persists, please reach out to us at 'info@rapidata.ai', we're happy to help you.
        """

