# -*- coding: utf-8 -*-
import inspect
from functools import wraps
from string import Formatter

from pydantic.main import BaseModel

from easi_py_common.client.rest_client import RestClient, RestClientResponseDecoder

service_endpoint = {}

HTTP_METHOD_GET = "GET"
HTTP_METHOD_POST = "POST"
HTTP_METHOD_PUT = "PUT"
HTTP_METHOD_DELETE = "DELETE"


def service_client(
    url,
    timeout=(5, 5),
    http_adapter=None,
    before_request=None,
    response_decoder: RestClientResponseDecoder = None,
):
    def wrapper(f):
        key = "{}.{}".format(f.__module__, f.__name__)

        service_endpoint[key] = RestClient(
            url,
            timeout=timeout,
            http_adapter=http_adapter,
            before_request=before_request,
            response_decoder=response_decoder,
        )

        @wraps(f)
        def wrapped(*args, **kwargs):
            return f(*args, **kwargs)

        return wrapped

    return wrapper


def service_mapping(uri, method, timeout=None, data_by_alias: bool = True, data_key="data"):
    def wrapper(f):
        full_arg_spec = inspect.getfullargspec(f)
        is_method = full_arg_spec.args and full_arg_spec.args[0] == "self"
        if not is_method:
            raise Exception("{} is invalid service_mapping", f.__name__)

        dst = full_arg_spec.annotations.get("return")
        has_format_names = [fn for _, fn, _, _ in Formatter().parse(uri) if fn is not None]
        has_format_names.append("self")  # 参数列表过滤self

        def __get_request_params(named_args):
            request_named_args = {k: v for k, v in named_args.items() if k not in has_format_names}
            data = None
            if method == HTTP_METHOD_POST or method == HTTP_METHOD_PUT:
                if data_key in request_named_args:
                    data = request_named_args.pop(data_key)
                if data is None:
                    data = {}

            return request_named_args, data

        def __get_function_args(args, kwargs):
            named_args = inspect.getcallargs(f, *args, **kwargs)
            named_args.update(named_args.pop(full_arg_spec.varkw, {}))
            if (
                (method == HTTP_METHOD_GET or method == HTTP_METHOD_DELETE)
                and data_key in named_args
                and isinstance(named_args[data_key], BaseModel)
            ):
                params_data = named_args.pop(data_key)
                named_args.update(params_data.dict(by_alias=data_by_alias))
            return named_args

        @wraps(f)
        def wrapped(*args, **kwargs):
            name = "{}.{}".format(args[0].__class__.__module__, args[0].__class__.__name__)
            if name not in service_endpoint:
                raise Exception("{}.{} is not have config endpoint", f.__module__, f.__name__)

            named_args = __get_function_args(args, kwargs)
            _uri = uri.format(**named_args)
            params, data = __get_request_params(named_args)

            client = service_endpoint[name]
            if method == HTTP_METHOD_GET:
                return client.get(_uri, params=params, timeout=timeout, dst=dst)
            elif method == HTTP_METHOD_POST:
                return client.post(
                    _uri,
                    json_data=data,
                    params=params,
                    timeout=timeout,
                    dst=dst,
                    request_by_alias=data_by_alias,
                )
            elif method == HTTP_METHOD_PUT:
                return client.put(
                    _uri,
                    json_data=data,
                    params=params,
                    timeout=timeout,
                    dst=dst,
                    request_by_alias=data_by_alias,
                )
            elif method == HTTP_METHOD_DELETE:
                return client.delete(_uri, params=params, timeout=timeout, dst=dst)
            else:
                raise Exception("{}.{} is invalid method", f.__module__, f.__name__)

        return wrapped

    return wrapper


def service_mapping_get(uri, timeout=None, data_by_alias: bool = True, data_key="data"):
    return service_mapping(
        uri, method=HTTP_METHOD_GET, timeout=timeout, data_by_alias=data_by_alias, data_key=data_key
    )


def service_mapping_post(uri, timeout=None, data_by_alias: bool = True, data_key="data"):
    return service_mapping(
        uri,
        method=HTTP_METHOD_POST,
        timeout=timeout,
        data_by_alias=data_by_alias,
        data_key=data_key,
    )


def service_mapping_put(uri, timeout=None, request_by_alias: bool = True, data_key="data"):
    return service_mapping(
        uri,
        method=HTTP_METHOD_PUT,
        timeout=timeout,
        data_by_alias=request_by_alias,
        data_key=data_key,
    )


def service_mapping_delete(uri, timeout=None, request_by_alias: bool = True, data_key="data"):
    return service_mapping(
        uri,
        method=HTTP_METHOD_DELETE,
        timeout=timeout,
        data_by_alias=request_by_alias,
        data_key=data_key,
    )
