package ink.wgink.common.rpc.rest.handler;

import ink.wgink.annotation.rpc.rest.RemoteService;
import ink.wgink.annotation.rpc.rest.method.RemoteDeleteMethod;
import ink.wgink.annotation.rpc.rest.method.RemoteGetMethod;
import ink.wgink.annotation.rpc.rest.method.RemotePostMethod;
import ink.wgink.annotation.rpc.rest.method.RemotePutMethod;
import ink.wgink.annotation.rpc.rest.params.*;
import ink.wgink.common.rpc.rest.pojo.RemoteFileInputStream;
import ink.wgink.common.rpc.rest.pojo.RemoteRequest;
import ink.wgink.common.rpc.rest.request.RestRemoteRequest;
import ink.wgink.exceptions.ParamsException;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.util.RegexUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName: RestRemoterHandler
 * @Description: Rest远程调用
 * @Author: wanggeng
 * @Date: 2021/9/18 5:14 下午
 * @Version: 1.0
 */
public class RestRemoteHandler implements InvocationHandler {
    private static final Pattern PATH_VARIABLE = Pattern.compile("\\{[^\\s\\}]+\\}");

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return handle(method, args);
    }

    /**
     * 处理
     *
     * @param method
     * @param args
     * @return
     */
    public Object handle(Method method, Object[] args) {
        if (method.getParameters().length == 0) {
            throw new ParamsException("方法没有参数");
        }
        RemoteService remoteService = method.getDeclaringClass().getAnnotation(RemoteService.class);
        String baseUri = remoteService.value();
        String remotePath = getRemotePath(method.getParameters(), args);
        checkRemotePath(remotePath);
        Map<String, String> pathVariableParams = getPathVariableParams(method.getParameters(), args);
        Map<String, String> queryVariableParams = getQueryVariableParams(method.getParameters(), args);
        Map<String, String> queryMapParams = getQueryMapParams(method.getParameters(), args);
        Map<String, String> headerVariableParams = getHeaderVariableParams(method.getParameters(), args);
        MultiValueMap<String, Object> formVariableParams = null;
        String uri;
        Object requestBody = null;
        Map<String, RemoteFileInputStream> fileInputStreamVariableParams = null;
        Map<String, File> fileVariableParams = null;
        RequestMethod requestMethod;
        if (method.isAnnotationPresent(RemoteGetMethod.class)) {
            // GET请求
            requestMethod = RequestMethod.GET;
            RemoteGetMethod remoteGet = method.getAnnotation(RemoteGetMethod.class);
            uri = remoteGet.value();
        } else if (method.isAnnotationPresent(RemoteDeleteMethod.class)) {
            // DELETE请求
            requestMethod = RequestMethod.DELETE;
            RemoteDeleteMethod deleteMethod = method.getAnnotation(RemoteDeleteMethod.class);
            uri = deleteMethod.value();
        } else if (method.isAnnotationPresent(RemotePostMethod.class)) {
            // POST请求
            requestMethod = RequestMethod.POST;
            RemotePostMethod postMethod = method.getAnnotation(RemotePostMethod.class);
            uri = postMethod.value();
            // 1. 先判断是否有文件
            fileVariableParams = getFileParams(method.getParameters(), args);
            if (fileVariableParams.isEmpty()) {
                // 2. 判断是否是文件流
                fileInputStreamVariableParams = getFileInputStreamParams(method.getParameters(), args);
            }
            // 3. 判断表单
            formVariableParams = getFormVariableParams(method.getParameters(), args);
            if ((fileInputStreamVariableParams == null || fileInputStreamVariableParams.isEmpty()) &&
                    fileVariableParams.isEmpty() &&
                    formVariableParams.isEmpty()) {
                // 4. 文件与表单都不存在、判断json
                requestBody = getRequestBody(method.getParameters(), args);
            }
        } else if (method.isAnnotationPresent(RemotePutMethod.class)) {
            // PUT请求
            requestMethod = RequestMethod.PUT;
            RemotePutMethod putMethod = method.getAnnotation(RemotePutMethod.class);
            uri = putMethod.value();
            requestBody = getRequestBody(method.getParameters(), args);
        } else {
            return null;
        }
        List<String> pathVariable = listPathVariables(uri);
        checkPathVariables(pathVariable);
        checkPathVariableParams(pathVariable, pathVariableParams);
        uri = buildUri(baseUri, uri, pathVariable, pathVariableParams);
        String remoteFullPath = buildFullUri(remotePath, uri, queryVariableParams, queryMapParams);

        // 发起请求
        RemoteRequest remoteRequest = new RemoteRequest();
        remoteRequest.setRequestMethod(requestMethod);
        remoteRequest.setRemoteUri(remoteFullPath);
        remoteRequest.setHeaders(headerVariableParams);
        remoteRequest.setFormVariableParams(formVariableParams);
        remoteRequest.setFileVariableParams(fileVariableParams);
        remoteRequest.setFileInputStreamVariableParams(fileInputStreamVariableParams);
        remoteRequest.setJsonBody(requestBody);
        remoteRequest.setResultType(method.getGenericReturnType());
        return new RestRemoteRequest().request(remoteRequest);
    }

    /**
     * 路径变量列表
     *
     * @param uri 路径
     * @return
     */
    private List<String> listPathVariables(String uri) {
        List<String> pathVariables = new ArrayList<>();
        Matcher matcher = PATH_VARIABLE.matcher(uri);
        while (matcher.find()) {
            String variable = matcher.group();
            pathVariables.add(variable.substring(1, variable.length() - 1));
        }
        return pathVariables;
    }

    /**
     * 路径参数
     *
     * @param parameters
     * @return
     */
    private Map<String, String> getPathVariableParams(Parameter[] parameters, Object[] args) {
        Map<String, String> pathVariableParamsMap = new HashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (!parameter.isAnnotationPresent(RemotePathParams.class)) {
                continue;
            }
            RemotePathParams annotation = parameter.getAnnotation(RemotePathParams.class);
            String variableName = annotation.value();
            if (StringUtils.isBlank(variableName)) {
                throw new ParamsException("参数 " + parameter.getName() + " 名称不能为空");
            }
            Object arg = args[i];
            if (arg == null) {
                throw new SystemException("路径参数不能为空: " + variableName);
            }
            pathVariableParamsMap.put(variableName, String.valueOf(arg));
        }
        return pathVariableParamsMap;
    }

    /**
     * 请求参数
     *
     * @param parameters
     * @param args
     * @return
     */
    private Map<String, String> getQueryVariableParams(Parameter[] parameters, Object[] args) {
        Map<String, String> queryVariableParamsMap = new HashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (!parameter.isAnnotationPresent(RemoteQueryParams.class)) {
                continue;
            }
            RemoteQueryParams annotation = parameter.getAnnotation(RemoteQueryParams.class);
            String variableName = annotation.value();
            if (StringUtils.isBlank(variableName)) {
                throw new ParamsException("参数 " + parameter.getName() + " 名称不能为空");
            }
            Object arg = args[i];
            if (arg == null) {
                continue;
            }
            queryVariableParamsMap.put(variableName, String.valueOf(arg));
        }
        return queryVariableParamsMap;
    }

    /**
     * 文件
     *
     * @param parameters
     * @param args
     * @return
     */
    private Map<String, File> getFileParams(Parameter[] parameters, Object[] args) {
        Map<String, File> fileParams = new HashMap<>(0);
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (!parameter.isAnnotationPresent(RemoteFileParams.class)) {
                continue;
            }
            RemoteFileParams annotation = parameter.getAnnotation(RemoteFileParams.class);
            String variableName = annotation.value();
            if (StringUtils.isBlank(variableName)) {
                throw new ParamsException("参数 " + parameter.getName() + " 名称不能为空");
            }
            Object arg = args[i];
            if (arg == null) {
                throw new ParamsException("参数 " + parameter.getName() + " 不能为空");
            }
            if (!(arg instanceof File)) {
                throw new ParamsException("参数 " + parameter.getName() + " 类型需为 java.io.File");
            }
            fileParams.put(variableName, (File) arg);
        }
        return fileParams;
    }

    /**
     * 文件流
     *
     * @param parameters
     * @param args
     * @return
     */
    private Map<String, RemoteFileInputStream> getFileInputStreamParams(Parameter[] parameters, Object[] args) {
        Map<String, RemoteFileInputStream> inputStreamParams = new HashMap<>(0);
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (!parameter.isAnnotationPresent(RemoteFileInputStreamParams.class)) {
                continue;
            }
            RemoteFileInputStreamParams annotation = parameter.getAnnotation(RemoteFileInputStreamParams.class);
            String variableName = annotation.value();
            if (StringUtils.isBlank(variableName)) {
                throw new ParamsException("参数 " + parameter.getName() + " 名称不能为空");
            }
            Object arg = args[i];
            if (arg == null) {
                throw new ParamsException("参数 " + parameter.getName() + " 不能为空");
            }
            if (!(arg instanceof RemoteFileInputStream)) {
                throw new ParamsException("参数 " + parameter.getName() + " 类型需为 ink.wgink.common.rpc.rest.pojo.RemoteFileInputStream");
            }
            inputStreamParams.put(variableName, (RemoteFileInputStream) arg);
        }
        return inputStreamParams;
    }

    /**
     * 表单参数
     *
     * @param parameters
     * @param args
     * @return
     */
    private MultiValueMap<String, Object> getFormVariableParams(Parameter[] parameters, Object[] args) {
        MultiValueMap<String, Object> formVariableParamsMap = new LinkedMultiValueMap<>();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (!parameter.isAnnotationPresent(RemoteFormParams.class)) {
                continue;
            }
            RemoteFormParams annotation = parameter.getAnnotation(RemoteFormParams.class);
            String variableName = annotation.value();
            if (StringUtils.isBlank(variableName)) {
                throw new ParamsException("表单参数 " + parameter.getName() + " 名称不能为空");
            }
            Object arg = args[i];
            if (arg == null) {
                continue;
            }
            formVariableParamsMap.add(variableName, String.valueOf(arg));
        }
        return formVariableParamsMap;
    }

    /**
     * 请求头
     *
     * @param parameters
     * @param args
     * @return
     */
    private Map<String, String> getHeaderVariableParams(Parameter[] parameters, Object[] args) {
        Map<String, String> headerVariableParamsMap = new HashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (!parameter.isAnnotationPresent(RemoteHeaderParams.class)) {
                continue;
            }
            RemoteHeaderParams annotation = parameter.getAnnotation(RemoteHeaderParams.class);
            String variableName = annotation.value();
            if (StringUtils.isBlank(variableName)) {
                throw new ParamsException("参数 " + parameter.getName() + " 名称不能为空");
            }
            Object arg = args[i];
            if (arg == null) {
                continue;
            }
            headerVariableParamsMap.put(variableName, String.valueOf(arg));
        }
        return headerVariableParamsMap;
    }

    /**
     * 获取请求体，只取第一个
     *
     * @param parameters 方法参数
     * @param args
     * @return
     */
    private Object getRequestBody(Parameter[] parameters, Object[] args) {
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (!parameter.isAnnotationPresent(RemoteJsonBodyParams.class)) {
                continue;
            }
            if (args[i] == null) {
                continue;
            }
            return args[i];
        }
        return null;
    }


    /**
     * 查询Map参数
     *
     * @param parameters
     * @param args
     * @return
     * @throws UnsupportedEncodingException
     */
    private Map<String, String> getQueryMapParams(Parameter[] parameters, Object[] args) {
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (!parameter.isAnnotationPresent(RemoteQueryParamsMap.class)) {
                continue;
            }
            if (parameter.getType() != Map.class) {
                continue;
            }
            if (args[i] == null) {
                continue;
            }
            Map<String, Object> argMap = (Map<String, Object>) args[i];
            Map<String, String> queryMap = new HashMap<>();
            for (Map.Entry<String, Object> kv : argMap.entrySet()) {
                queryMap.put(kv.getKey(), String.valueOf(kv.getValue()));
            }
            return queryMap;
        }
        return new HashMap<>();
    }

    /**
     * 获取远程地址，只取第一个
     *
     * @param parameters 方法参数
     * @param args       参数
     * @return
     */
    private String getRemotePath(Parameter[] parameters, Object[] args) {
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if (!parameter.isAnnotationPresent(RemoteServerParams.class)) {
                continue;
            }
            if (args[i] == null) {
                continue;
            }
            return String.valueOf(args[i]);
        }
        return null;
    }

    /**
     * 检查路径变量
     *
     * @param pathVariables 路劲变量列表
     */
    private void checkPathVariables(List<String> pathVariables) {
        for (int i = 0; i < pathVariables.size() - 1; i++) {
            for (int j = i + 1; j < pathVariables.size(); j++) {
                if (StringUtils.equals(pathVariables.get(i), pathVariables.get(j))) {
                    throw new ParamsException("路径变量 " + pathVariables.get(i) + "[" + i + "] 与 " + pathVariables.get(j) + "[" + j + "] 重复");
                }
            }
        }
    }

    /**
     * 检查路径变量与参数
     *
     * @param pathVariables      路劲变量列表
     * @param pathVariableParams 路径参数Map
     */
    private void checkPathVariableParams(List<String> pathVariables, Map<String, String> pathVariableParams) {
        if (pathVariables.size() != pathVariableParams.size()) {
            throw new SystemException("路径变量与参数数量不一致");
        }
        for (String pathVariable : pathVariables) {
            boolean isExist = false;
            for (Map.Entry<String, String> kv : pathVariableParams.entrySet()) {
                if (StringUtils.equals(pathVariable, kv.getKey())) {
                    isExist = true;
                    break;
                }
            }
            if (!isExist) {
                throw new ParamsException("路径变量" + pathVariable + " 不存在参数");
            }
        }
    }

    /**
     * 校验远程请求路径
     *
     * @param remotePath
     */
    private void checkRemotePath(String remotePath) {
        if (StringUtils.isBlank(remotePath)) {
            throw new ParamsException("远程请求路径不能为空");
        }
        if (!RegexUtil.isUrl(remotePath)) {
            throw new ParamsException("远程请求路径格式错误");
        }
    }

    /**
     * 构建uri
     *
     * @param uri
     * @param pathVariables
     * @param pathVariableParams
     * @return
     */
    private String buildUri(String baseUri, String uri, List<String> pathVariables, Map<String, String> pathVariableParams) {
        String resultBaseUri = baseUri;
        if (!StringUtils.isBlank(baseUri)) {
            for (String pathVariable : pathVariables) {
                resultBaseUri = resultBaseUri.replace("{" + pathVariable + "}", pathVariableParams.get(pathVariable));
            }
            resultBaseUri = resultBaseUri.startsWith("/") ? resultBaseUri : "/" + resultBaseUri;
        }
        String resultUri = uri;
        for (String pathVariable : pathVariables) {
            resultUri = resultUri.replace("{" + pathVariable + "}", pathVariableParams.get(pathVariable));
        }
        return resultBaseUri + (resultUri.startsWith("/") ? resultUri : "/" + resultUri);
    }

    /**
     * 构建完整路径
     *
     * @param remotePath          远程调用地址
     * @param uri                 请求路径
     * @param queryVariableParams 查询参数
     * @param queryMapParams      查询Map参数
     * @return
     * @throws UnsupportedEncodingException
     */
    private String buildFullUri(String remotePath, String uri, Map<String, String> queryVariableParams, Map<String, String> queryMapParams) {
        String remote = remotePath.endsWith("/") ? remotePath.substring(0, remotePath.length() - 1) : remotePath;
        if (queryVariableParams.isEmpty() && queryMapParams.isEmpty()) {
            return remotePath + uri;
        }
        StringBuilder queryParams = new StringBuilder();
        for (Map.Entry<String, String> kv : queryVariableParams.entrySet()) {
            if (queryParams.length() > 0) {
                queryParams.append("&");
            }
            queryParams.append(kv.getKey()).append("=").append(kv.getValue());
        }
        for (Map.Entry<String, String> kv : queryMapParams.entrySet()) {
            if (queryParams.length() > 0) {
                queryParams.append("&");
            }
            queryParams.append(kv.getKey()).append("=").append(kv.getValue());
        }
        return remote + uri + "?" + queryParams;
    }

}
