package com.njzscloud.common.core.http;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.util.ReflectUtil;
import com.njzscloud.common.core.http.annotation.GetEndpoint;
import com.njzscloud.common.core.http.annotation.PostEndpoint;
import com.njzscloud.common.core.http.annotation.RemoteServer;
import com.njzscloud.common.core.http.constant.HttpMethod;
import com.njzscloud.common.core.http.interceptor.RequestInterceptor;
import com.njzscloud.common.core.http.interceptor.ResponseInterceptor;
import com.njzscloud.common.core.http.resolver.*;
import com.njzscloud.common.core.http.support.RequestInfo;
import com.njzscloud.common.core.http.support.ResponseInfo;
import com.njzscloud.common.core.http.support.ResponseResult;
import com.njzscloud.common.core.tuple.Tuple2;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 服务装饰器, 用于 "接口注解式" HTTP 请求配置, 使用 cglib 进行接口装饰
 * <p>注: 在使用异步请求时, 接口方法的返回值不能是 void, 除非 HTTP 请求确实没有返回值</p>
 */
@SuppressWarnings("unchecked")
public class HttpClientDecorator {
    private static final SimpleCache<Class<?>, Object> ENHANCER_CACHE = new SimpleCache<>();

    private final HttpClient HTTP_CLIENT;

    public HttpClientDecorator(HttpClient httpClient) {
        HTTP_CLIENT = httpClient;
    }

    public <T> T decorate(Class<T> clazz) {
        return (T) ENHANCER_CACHE.get(clazz, () -> {
            MethodInterceptor methodInterceptor = new MethodInterceptorImpl(clazz, HTTP_CLIENT);
            Enhancer enhancer = new Enhancer();
            enhancer.setInterfaces(new Class<?>[]{clazz});
            enhancer.setCallback(methodInterceptor);
            return enhancer.create();
        });
    }

    @SuppressWarnings("RedundantLengthCheck")
    private static class MethodInterceptorImpl implements MethodInterceptor {
        private static final Pattern ADDR_PATTERN = Pattern.compile("(?<protocol>http(?:s)?):\\/\\/(?<host>[0-9a-zA-Z_\\-\\.]+)(?::(?<port>[0-9]+))?(?<baseUrl>\\/\\S*)*");
        private final HttpClient HTTP_CLIENT;
        private final String baseUrl;
        private final RequestInterceptor requestInterceptor;
        private final ResponseInterceptor responseInterceptor;

        public MethodInterceptorImpl(Class<?> clazz, HttpClient httpClient) {
            RemoteServer anno = clazz.getAnnotation(RemoteServer.class);
            baseUrl = anno.value();
            Matcher matcher = ADDR_PATTERN.matcher(baseUrl);
            Assert.isTrue(matcher.matches(), "地址不合法");
            Class<? extends RequestInterceptor> requestedInterceptorClazz = anno.requestInterceptor();
            Class<? extends ResponseInterceptor> responseInterceptorClazz = anno.responseInterceptor();
            requestInterceptor = ReflectUtil.newInstance(requestedInterceptorClazz);
            responseInterceptor = ReflectUtil.newInstance(responseInterceptorClazz);

            HTTP_CLIENT = httpClient;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            HttpMethod httpMethod;
            String urlTpl;
            String desc;

            GetEndpoint getEndpoint = method.getAnnotation(GetEndpoint.class);
            if (getEndpoint == null) {
                PostEndpoint postEndpoint = method.getAnnotation(PostEndpoint.class);
                if (postEndpoint == null) {
                    //
                    return null;
                } else {
                    httpMethod = HttpMethod.POST;
                    urlTpl = baseUrl + postEndpoint.value();
                    desc = postEndpoint.desc();
                }
            } else {
                httpMethod = HttpMethod.GET;
                urlTpl = baseUrl + getEndpoint.value();
                desc = getEndpoint.desc();
            }

            Object[] additional = requestInterceptor.process(httpMethod, urlTpl, args);

            Parameter[] parameters = method.getParameters();

            PathParamResolver pathParamResolver = new PathParamResolver();
            QueryParamResolver queryParamResolver = new QueryParamResolver();
            HeaderParamResolver headerParamResolver = new HeaderParamResolver();
            BodyParamResolver bodyParamResolver = null;
            JsonBodyParamResolver jsonBodyParamResolver = null;
            XmlBodyParamResolver xmlBodyParamResolver = null;
            FormBodyParamResolver formBodyParamResolver = null;
            MultiBodyParamResolver multiBodyParamResolver = null;

            List<ParamResolver<? extends Annotation, ?>> paramResolvers = ListUtil.list(false,
                    pathParamResolver,
                    queryParamResolver,
                    headerParamResolver
            );

            if (httpMethod != HttpMethod.GET) {
                jsonBodyParamResolver = new JsonBodyParamResolver();
                xmlBodyParamResolver = new XmlBodyParamResolver();
                formBodyParamResolver = new FormBodyParamResolver();
                multiBodyParamResolver = new MultiBodyParamResolver();
                bodyParamResolver = new BodyParamResolver();
                paramResolvers.add(jsonBodyParamResolver);
                paramResolvers.add(xmlBodyParamResolver);
                paramResolvers.add(formBodyParamResolver);
                paramResolvers.add(multiBodyParamResolver);
                paramResolvers.add(bodyParamResolver);
            }

            if (additional != null) {
                for (Object o : additional) {
                    for (ParamResolver<? extends Annotation, ?> resolver : paramResolvers) {
                        resolver.resolve(o);
                    }
                }
            }

            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                for (ParamResolver<? extends Annotation, ?> resolver : paramResolvers) {
                    resolver.resolve(parameter, args[i]);
                }
            }

            urlTpl = pathParamResolver.resolve(httpMethod, urlTpl);
            urlTpl = queryParamResolver.resolve(httpMethod, urlTpl);
            Map<String, String> headers = headerParamResolver.resolve(httpMethod, urlTpl);
            Tuple2<String, byte[]> body = null;
            if (httpMethod != HttpMethod.GET) {
                body = jsonBodyParamResolver.resolve(httpMethod, urlTpl);
                if (body == null) body = xmlBodyParamResolver.resolve(httpMethod, urlTpl);
                if (body == null) body = formBodyParamResolver.resolve(httpMethod, urlTpl);
                if (body == null) body = multiBodyParamResolver.resolve(httpMethod, urlTpl);
                if (body == null) body = bodyParamResolver.resolve(httpMethod, urlTpl);
                if (body == null) body = Tuple2.create("", new byte[0]);
            }

            ResponseInfo responseInfo = HTTP_CLIENT.execute(httpMethod, urlTpl, headers, body);

            RequestInfo requestInfo = RequestInfo.create(desc, httpMethod, urlTpl, headers, body);

            Type genericReturnType = method.getGenericReturnType();

            Type returnType;
            Type rawType;

            if (genericReturnType instanceof ParameterizedType) {
                ParameterizedType typeWrap = (ParameterizedType) genericReturnType;

                rawType = typeWrap.getRawType();

                if (rawType == ResponseResult.class) {
                    returnType = typeWrap.getActualTypeArguments()[0];
                } else {
                    returnType = typeWrap;
                }
            } else {
                returnType = genericReturnType;
                rawType = genericReturnType;
            }

            Object res = responseInterceptor.process(requestInfo, responseInfo, returnType);

            if (ResponseResult.class == rawType) {
                return ResponseResult
                        .builder()
                        .code(responseInfo.code)
                        .status(responseInfo.message)
                        .headers(responseInfo.header)
                        .body(res)
                        .e(responseInfo.e)
                        .build();
            }

            return res;
        }
    }

}
