# coding: utf-8
"""
    Kubernetes

    No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)  # noqa: E501

    OpenAPI spec version: v1.10.6

    Generated by: https://github.com/swagger-api/swagger-codegen.git
"""

import datetime
import json
import mimetypes
import os
import re
import ssl
from collections import namedtuple
from urllib.parse import quote, urlencode, urlparse, urlunparse

import aiohttp
import certifi

import aiokubernetes as k8s

# All API responses will be wrapped into this tuple.
ApiResponse = namedtuple('ApiResponse', 'http obj')


def get_websocket_url(url):
    parts = urlparse(url)
    assert parts.scheme in ('http', 'https'), f'Unknown scheme <{parts.scheme}>'
    new_scheme = 'ws' if parts.scheme == 'http' else 'wss'
    return urlunparse(parts._replace(scheme=new_scheme))


class ApiClient(object):
    """Generic API client for Swagger client library builds.

    Swagger generic API client. This client handles the client-
    server communication, and is invariant across implementations. Specifics of
    the methods and models for each application are generated from the Swagger
    templates.

    NOTE: This class is auto generated by the swagger code generator program.
    Ref: https://github.com/swagger-api/swagger-codegen
    Do not edit the class manually.

    :param: configuration: .Configuration object for this client
    :param: header_name: a header to pass when making calls to the API.
    :param: header_value: a header value to pass when making calls to
        the API.
    :param: cookie: a cookie to include in the header when making calls
        to the API
    """

    PRIMITIVE_TYPES = (float, bool, bytes, int, str)

    def __init__(self, configuration, header_name=None, header_value=None,
                 cookie=None):

        self.default_headers = {}
        if header_name is not None:
            self.default_headers[header_name] = header_value
        self.cookie = cookie

        # Set default User-Agent.
        self.user_agent = 'Swagger-Codegen/1.0/python'

        self.configuration = configuration

        # ca_certs
        if configuration.ssl_ca_cert:
            ca_certs = configuration.ssl_ca_cert
        else:
            # if not set certificate file, use Mozilla's root certificates.
            ca_certs = certifi.where()

        ssl_context = ssl.create_default_context(cafile=ca_certs)
        if configuration.cert_file:
            ssl_context.load_cert_chain(
                configuration.cert_file, keyfile=configuration.key_file
            )

        connector = aiohttp.TCPConnector(
            limit=4,
            ssl_context=ssl_context,
            verify_ssl=configuration.verify_ssl
        )

        # https pool manager
        self.session = aiohttp.ClientSession(connector=connector)

    @property
    def user_agent(self):
        """User agent for this API client"""
        return self.default_headers['User-Agent']

    @user_agent.setter
    def user_agent(self, value):
        self.default_headers['User-Agent'] = value

    async def close(self):
        await self.session.close()

    def set_default_header(self, header_name, header_value):
        self.default_headers[header_name] = header_value

    async def call_api(
            self, resource_path, method, path_params=None,
            query_params=None, header_params=None, body=None, post_params=None,
            files=None, response_type=None, auth_settings=None,
            _return_http_data_only=None, collection_formats=None,
            _preload_content=True, _request_timeout=None):
        """Makes the HTTP request (synchronous) and returns deserialized data.

        :param: resource_path: Path to method endpoint.
        :param: method: Method to call.
        :param: path_params: Path parameters in the url.
        :param: query_params: Query parameters in the url.
        :param: header_params: Header parameters to be
            placed in the request header.
        :param: body: Request body.
        :param: post_params dict: Request post form parameters,
            for `application/x-www-form-urlencoded`, `multipart/form-data`.
        :param: auth_settings list: Auth Settings names for the request.
        :param: response: Response data type.
        :param: files dict: key -> filename, value -> filepath,
            for `multipart/form-data`.
        :param: _return_http_data_only: response data without head status code
                                       and headers
        :param: collection_formats: dict of collection formats for path, query,
            header, and post parameters.
        :param: _preload_content: if False, the urllib3.HTTPResponse object will
                                 be returned without reading/decoding response
                                 data. Default is True.
        :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.
        :return:
            fixme: docu is wrong
            If async parameter is True,
            the request will be called asynchronously.
            The method will return the request thread.
            If parameter async is False or missing,
            then the method will return the response directly.
        """
        config = self.configuration

        # header parameters
        header_params = header_params or {}
        header_params.update(self.default_headers)
        if self.cookie:
            header_params['Cookie'] = self.cookie
        if header_params:
            header_params = dict(self.sanitize_for_serialization(header_params))

        # path parameters
        if path_params:
            path_params = dict(self.sanitize_for_serialization(path_params))
            for k, v in path_params.items():
                # specified safe chars, encode everything
                resource_path = resource_path.replace(
                    '{%s}' % k,
                    quote(str(v), safe=config.safe_chars_for_path_param)
                )

        # query parameters
        if query_params:
            query_params = dict(self.sanitize_for_serialization(query_params))

        # post parameters
        if post_params or files:
            post_params = self.prepare_post_parameters(post_params, files)
            post_params = dict(self.sanitize_for_serialization(post_params))

        # auth setting
        self.update_params_for_auth(header_params, query_params, auth_settings)

        # body
        if body:
            body = self.sanitize_for_serialization(body)

        # request url
        url = self.configuration.host + resource_path

        kwargs = {
            'query_params': query_params,
            'headers': header_params,
            'post_params': post_params,
            'body': body,
            '_request_timeout': _request_timeout
        }

        # For Websockets, return the raw HTTP response immediately. The
        # returned object is a `_WSRequestContextManager` and the caller can
        # use it as a context manager to process the Websocket data as it
        # streams in.
        if url.lower().endswith('/exec'):
            response = await self.websocket_request(self.session, url, **kwargs)
            return ApiResponse(http=response, obj=None)

        # Make the request and wait for a response.
        response_data = await self.http_request(self.session, method, url, **kwargs)

        # Deserialize the response if the caller requested it. This is almost
        # always True, the only notable exception being the Watch class, which
        # has to read the response line by line from the raw return value (each
        # line corresponds to one event).
        if _preload_content:
            if response_type is None:
                return_data = None
            else:
                assert response_type != "file"

                # fetch data from response object
                data = await response_data.json()
                return_data = k8s.swagger.deserialize(data, response_type)
        else:
            return_data = None

        return ApiResponse(http=response_data, obj=return_data)

    @staticmethod
    async def websocket_request(session, url, query_params=None, headers=None, **kw):
        """Create Websocket connection to `url` and return it.

        NOTE: the `kw` arguments are meaningless and will be ignored. They only
        exist for compatibility with the `http_request` method.

        :param: session: An `aiohttp` session object.
        :param: url: http request url
        :param: query_params: query parameters in the url
        :param: headers: http request headers

        """

        # Expand command parameter list to individual command params
        if query_params:
            new_query_params = []
            for key, value in query_params.items():
                if key == 'command' and isinstance(value, list):
                    for command in value:
                        new_query_params.append((key, command))
                else:
                    new_query_params.append((key, value))
            query_params = new_query_params

        headers = headers or {}
        if 'sec-websocket-protocol' not in headers:
            headers['sec-websocket-protocol'] = 'v4.channel.k8s.io'

        if query_params:
            url += '?' + urlencode(query_params)

        url = get_websocket_url(url)
        return session.ws_connect(url, headers=headers)

    @staticmethod
    async def http_request(session, method, url, query_params=None, headers=None,
                           body=None, post_params=None, _request_timeout=None):
        """Make HTTP request to `url` and return its response.

        :param: method: http request method
        :param: url: http request url
        :param: query_params: query parameters in the 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 ValueError("<body> and <post_params> cannot both be set.")

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

        if 'Content-Type' not in headers:
            headers['Content-Type'] = 'application/json'

        args = {
            "method": method,
            "url": url,
            # fixup: where is this used?
            "timeout": _request_timeout or 5 * 60,
            "headers": headers
        }

        if query_params:
            args["url"] += '?' + urlencode(query_params)

        # Automatically encode the data to Json if the headers indicate it is Json.
        # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
        if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
            if re.search('json', headers['Content-Type'], re.IGNORECASE):
                if body is not None:
                    body = json.dumps(body)
                args["data"] = body
        return await session.request(**args)

    def prepare_post_parameters(self, post_params=None, files=None):
        """Builds form parameters.

        :param: post_params: Normal form parameters.
        :param: files: File parameters.
        :return: Form parameters with files.
        """
        params = []

        if post_params:
            params = post_params

        if files:
            for k, v in files.items():
                if not v:
                    continue
                file_names = v if type(v) is list else [v]
                for n in file_names:
                    with open(n, 'rb') as f:
                        filename = os.path.basename(f.name)
                        filedata = f.read()
                        mimetype = (mimetypes.guess_type(filename)[0] or
                                    'application/octet-stream')
                        params.append(
                            tuple([k, tuple([filename, filedata, mimetype])]))

        return params

    def select_header_accept(self, accepts):
        """Returns `Accept` based on an array of accepts provided.

        :param: accepts: List of headers.
        :return: Accept (e.g. application/json).
        """
        if not accepts:
            return

        accepts = [x.lower() for x in accepts]

        if 'application/json' in accepts:
            return 'application/json'
        else:
            return ', '.join(accepts)

    def select_header_content_type(self, content_types):
        """Returns `Content-Type` based on an array of content_types provided.

        :param: content_types: List of content-types.
        :return: Content-Type (e.g. application/json).
        """
        if not content_types:
            return 'application/json'

        content_types = [x.lower() for x in content_types]

        if 'application/json' in content_types or '*/*' in content_types:
            return 'application/json'
        else:
            return content_types[0]

    def update_params_for_auth(self, headers, querys, auth_settings):
        """Updates header and query params based on authentication setting.

        :param: headers: Header parameters dict to be updated.
        :param: querys: Query parameters tuple list to be updated.
        :param: auth_settings: Authentication setting identifiers list.
        """
        if not auth_settings:
            return

        for auth in auth_settings:
            auth_setting = self.configuration.auth_settings().get(auth)
            if auth_setting:
                if not auth_setting['value']:
                    continue
                elif auth_setting['in'] == 'header':
                    headers[auth_setting['key']] = auth_setting['value']
                elif auth_setting['in'] == 'query':
                    querys.append((auth_setting['key'], auth_setting['value']))
                else:
                    raise ValueError(
                        'Authentication token must be in `query` or `header`'
                    )

    def sanitize_for_serialization(self, obj):
        """Builds a JSON POST object.

        If obj is None, return None.
        If obj is str, int, long, float, bool, return directly.
        If obj is datetime.datetime, datetime.date
            convert to string in iso8601 format.
        If obj is list, sanitize each element in the list.
        If obj is dict, return the dict.
        If obj is swagger model, return the properties dict.

        :param: obj: The data to serialize.
        :return: The serialized form of data.
        """
        if obj is None:
            return None
        elif isinstance(obj, self.PRIMITIVE_TYPES):
            return obj
        elif isinstance(obj, list):
            return [self.sanitize_for_serialization(sub_obj) for sub_obj in obj]
        elif isinstance(obj, tuple):
            return tuple(self.sanitize_for_serialization(sub_obj) for sub_obj in obj)
        elif isinstance(obj, (datetime.datetime, datetime.date)):
            return obj.isoformat()

        if isinstance(obj, dict):
            obj_dict = obj
        else:
            # Convert model obj to dict except attributes `swagger_types`,
            # `attribute_map` and attributes which value is not None. Convert
            # attribute name to json key in model definition for request.
            obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
                        for attr, _ in obj.swagger_types.items()
                        if getattr(obj, attr) is not None}

        return {k: self.sanitize_for_serialization(v) for k, v in obj_dict.items()}
