package ink.wgink.common.service.api.impl;

import com.alibaba.fastjson.JSONObject;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.common.service.api.IApiInfoService;
import ink.wgink.exceptions.ParamsException;
import ink.wgink.exceptions.SearchException;
import ink.wgink.interfaces.consts.ISystemConstant;
import ink.wgink.pojo.dtos.permission.SystemApiDTO;
import ink.wgink.properties.ServerProperties;
import ink.wgink.util.RegexUtil;
import ink.wgink.util.date.DateUtil;
import io.swagger.models.*;
import io.swagger.models.parameters.BodyParameter;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import springfox.documentation.service.Documentation;
import springfox.documentation.spring.web.DocumentationCache;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URLEncoder;
import java.util.*;

/**
 * @ClassName: ApiInfoServiceImpl
 * @Description: API
 * @Author: wanggeng
 * @Date: 2021/8/15 4:06 下午
 * @Version: 1.0
 */
@Service
public class ApiInfoServiceImpl extends DefaultBaseService implements IApiInfoService {

    private static String API_TYPE_GET = "get";
    private static String API_TYPE_POST = "post";
    private static String API_TYPE_PUT = "put";
    private static String API_TYPE_DELETE = "delete";

    @Autowired
    private DocumentationCache documentationCache;
    @Autowired
    private ServiceModelToSwagger2Mapper serviceModelToSwagger2Mapper;
    @Autowired
    private ServerProperties serverProperties;
    private Configuration configuration = new Configuration(Configuration.getVersion());

    @PostConstruct
    private void init() {
        configuration.setClassForTemplateLoading(ApiInfoServiceImpl.class, "/templates");
    }

    @Override
    public SystemApiDTO getSystemApi(String group, String apiType, HttpServletRequest request) {
        boolean apiTypeFlag = StringUtils.equalsIgnoreCase(API_TYPE_POST, apiType) ||
                StringUtils.equalsIgnoreCase(API_TYPE_DELETE, apiType) ||
                StringUtils.equalsIgnoreCase(API_TYPE_PUT, apiType) ||
                StringUtils.equalsIgnoreCase(API_TYPE_GET, apiType);
        if (!apiTypeFlag) {
            throw new ParamsException("api类型错误，只能是 post、delete、put、get 其中一种");
        }
        Documentation documentation = documentationCache.documentationByGroup(group);
        if (documentation == null) {
            throw new SearchException("无任何接口");
        }
        Swagger swagger = serviceModelToSwagger2Mapper.mapDocumentation(documentation);
        // 标签
        List<Tag> tags = swagger.getTags();
        List<SystemApiDTO.ApiDTO> apis = new ArrayList<>();
        for (Map.Entry<String, Path> kv : swagger.getPaths().entrySet()) {
            String keyPath = kv.getKey();
            Path valuePath = kv.getValue();
            Operation operation = null;
            if (StringUtils.equals(API_TYPE_POST, apiType)) {
                operation = valuePath.getPost();
            } else if (StringUtils.equals(API_TYPE_DELETE, apiType)) {
                operation = valuePath.getDelete();
            } else if (StringUtils.equals(API_TYPE_PUT, apiType)) {
                operation = valuePath.getPut();
            } else if (StringUtils.equals(API_TYPE_GET, apiType)) {
                operation = valuePath.getGet();
            }
            if (operation == null) {
                continue;
            }
            String apiTag = null;
            for (Tag tag : tags) {
                if (StringUtils.equals(tag.getName(), operation.getTags().get(0))) {
                    apiTag = tag.getDescription().replaceAll("\\s", "");
                }
            }
            if (apiTag == null) {
                continue;
            }
            apis.add(new SystemApiDTO.ApiDTO(RegexUtil.replacePathParams(keyPath, "*"), operation.getSummary(), apiTag));
        }
        SystemApiDTO systemApiDTO = new SystemApiDTO();
        systemApiDTO.setSystemTag(request.getContextPath());
        systemApiDTO.setApis(apis);
        return systemApiDTO;
    }

    @Override
    public Map<String, Object> getApi(List<String> apiTypes) {
        Map<String, Object> apiDocMap = new HashMap<>();
        List<Map<String, Object>> apiGroups = new ArrayList<>();
        if (apiTypes != null) {
            for (String apiType : apiTypes) {
                String name = null;
                if (StringUtils.equalsIgnoreCase(apiType, "SYSTEM")) {
                    name = "系统接口";
                } else if (StringUtils.equalsIgnoreCase(apiType, "APP")) {
                    name = "APP接口";
                } else if (StringUtils.equalsIgnoreCase(apiType, "RESOURCE")) {
                    name = "资源接口";
                } else if (StringUtils.equalsIgnoreCase(apiType, "WECHAT")) {
                    name = "微信接口";
                }
                if (name == null) {
                    continue;
                }
                Map<String, Object> systemDocMap = new HashMap<>();
                systemDocMap.put("name", name);
                systemDocMap.put("api", getApi(apiType.toUpperCase()));
                apiGroups.add(systemDocMap);
            }
        }
        apiDocMap.put("title", serverProperties.getSystemTitle());
        apiDocMap.put("apiGroups", apiGroups);
        apiDocMap.put("author", "XXXXXX");
        apiDocMap.put("date", DateUtil.getDay());
        apiDocMap.put("version", "v1.0");
        return apiDocMap;
    }

    @Override
    public void get(HttpServletRequest request, HttpServletResponse response) throws IOException, TemplateException {
        String apiTypes = request.getParameter("apiTypes");
        Template template = getTemplate("ftl/api-template.ftl");
        response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode("接口文档.doc", "UTF-8"));
        Map<String, Object> apiDocMap = getApi(StringUtils.isBlank(apiTypes) ? null : Arrays.asList(apiTypes.split(",")));
        Writer writer = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), ISystemConstant.CHARSET_UTF8));
        template.process(apiDocMap, writer);
    }

    /**
     * 获取API
     *
     * @param groupName
     * @return
     */
    private Map<String, Object> getApi(String groupName) {
        Documentation documentation = documentationCache.documentationByGroup(groupName);
        if (documentation == null) {
            return null;
        }
        Swagger swagger = serviceModelToSwagger2Mapper.mapDocumentation(documentation);
        Info info = swagger.getInfo();
        String host = swagger.getHost();
        String basePath = swagger.getBasePath();
        List<Tag> tags = swagger.getTags();
        Map<String, Path> paths = swagger.getPaths();
        Map<String, Model> definitions = swagger.getDefinitions();

        List<Map<String, Object>> apiControllers = new ArrayList<>();
        Map<String, Object> apiMap = new HashMap<>();
        apiMap.put("info", info);
        apiMap.put("host", host);
        apiMap.put("basePath", basePath);
        apiMap.put("apiControllers", apiControllers);
        apiMap.put("definitions", definitions);
        for (Tag tag : tags) {
            Map<String, Object> apiController = new HashMap<>();
            apiController.put("name", tag.getName().indexOf("-") > 0 ? tag.getName().split("\\-")[1] : tag.getName());
            apiController.put("description", tag.getDescription());
            apiController.put("apis", listApiMap(tag.getName(), paths, definitions));
            apiControllers.add(apiController);
        }
        return apiMap;
    }

    /**
     * api列表
     *
     * @param apiController
     * @param paths
     * @return
     */
    private List<Map<String, Object>> listApiMap(String apiController, Map<String, Path> paths, Map<String, Model> definitions) {
        List<Map<String, Object>> pathMaps = new ArrayList<>();
        for (Map.Entry<String, Path> kv : paths.entrySet()) {
            String apiUrl = kv.getKey();
            Path path = kv.getValue();

            Map<HttpMethod, Operation> operationMap = path.getOperationMap();
            for (Map.Entry<HttpMethod, Operation> operationEntry : operationMap.entrySet()) {
                HttpMethod httpMethod = operationEntry.getKey();
                Operation operation = operationEntry.getValue();
                if (StringUtils.equalsIgnoreCase(apiController, operation.getTags().get(0))) {
                    Map<String, Object> methodOperationMap = new HashMap<>();
                    methodOperationMap.put("apiUrl", apiUrl);
                    methodOperationMap.put("method", httpMethod.name());
                    methodOperationMap.put("summary", operation.getSummary());
                    methodOperationMap.put("description", operation.getDescription() == null ? "无" : operation.getDescription());
                    methodOperationMap.put("consumes", operation.getConsumes());
                    methodOperationMap.put("produces", operation.getProduces());
                    // 请求参数
                    List<Map<String, Object>> parameters = listRequestParameter(operation);
                    methodOperationMap.put("parameters", parameters);
                    methodOperationMap.put("parameterRefs", listRef(parameters, definitions));
                    // 请求示例
                    List<Map<String, Object>> parameterExamples = listExample(parameters, definitions);
                    methodOperationMap.put("parameterExamples", parameterExamples);
                    // 响应参数
                    List<Map<String, Object>> responses = listResponseMap(operation);
                    methodOperationMap.put("responses", responses);
                    methodOperationMap.put("responseRefs", listRef(responses, definitions));
                    // 响应示例
                    List<Map<String, Object>> responseExamples = listExample(responses, definitions);
                    methodOperationMap.put("responseExamples", responseExamples);
                    pathMaps.add(methodOperationMap);
                }
            }
        }
        return pathMaps;
    }

    /**
     * 请求参数
     *
     * @param operation
     * @return
     */
    private List<Map<String, Object>> listRequestParameter(Operation operation) {
        List<Map<String, Object>> requestParameterList = new ArrayList<>();
        List<Parameter> parameters = operation.getParameters();
        for (Parameter parameter : parameters) {
            Map<String, Object> requestParameter = new HashMap<>();
            requestParameter.put("class", parameter.getClass());
            requestParameter.put("name", parameter.getName());
            requestParameter.put("description", StringUtils.isBlank(parameter.getDescription()) ? "" : parameter.getDescription());
            String type = StringUtils.isBlank(parameter.getPattern()) ? "string" : parameter.getPattern();
            requestParameter.put("in", parameter.getIn());
            if (StringUtils.equals(parameter.getIn(), "body")) {
                type = "object";
            }
            requestParameter.put("type", type);
            requestParameter.put("source", parameter);
            requestParameter.put("access", parameter.getAccess());
            String ref = "无";
            String simpleRef = "无";
            if (parameter instanceof BodyParameter) {
                BodyParameter bodyParameter = (BodyParameter) parameter;
                Model schema = bodyParameter.getSchema();
                if (schema != null) {
                    requestParameter.put("schemaClass", schema.getClass());
                    if (schema instanceof RefModel) {
                        RefModel refModel = (RefModel) schema;
                        ref = refModel.get$ref();
                        simpleRef = refModel.getSimpleRef();
                    }
                }
            }
            requestParameter.put("ref", ref);
            requestParameter.put("simpleRef", simpleRef);
            requestParameterList.add(requestParameter);
        }
        return requestParameterList;
    }

    /**
     * 响应结果
     *
     * @param operation
     * @return
     */
    private List<Map<String, Object>> listResponseMap(Operation operation) {
        Map<String, Response> responses = operation.getResponses();
        List<Map<String, Object>> responseMaps = new ArrayList<>();
        for (Map.Entry<String, Response> responseEntry : responses.entrySet()) {
            String code = responseEntry.getKey();
            Response response = responseEntry.getValue();
            Map<String, Object> responseMap = new HashMap<>();
            responseMap.put("code", code);
            responseMap.put("description", response.getDescription());
            Property schema = response.getSchema();
            String ref = "无";
            String simpleRef = "无";
            String type = "无";
            if (schema != null) {
                responseMap.put("schemaClass", schema.getClass());
                type = schema.getType();
                if (schema instanceof ArrayProperty) {
                    ArrayProperty arrayProperty = (ArrayProperty) schema;
                    Property items = arrayProperty.getItems();
                    if (items instanceof RefProperty) {
                        RefProperty refProperty = (RefProperty) items;
                        ref = refProperty.get$ref();
                        simpleRef = refProperty.getSimpleRef();
                    }
                } else if (schema instanceof RefProperty) {
                    RefProperty refProperty = (RefProperty) schema;
                    ref = refProperty.get$ref();
                    simpleRef = refProperty.getSimpleRef();
                }
            }
            responseMap.put("ref", ref);
            responseMap.put("simpleRef", simpleRef);
            responseMap.put("type", StringUtils.equals(type, "ref") ? "object" : type);
            responseMaps.add(responseMap);
        }
        return responseMaps;
    }

    /**
     * 引用对象列表
     *
     * @param listParameter
     * @param definitions
     * @return
     */
    private List<Map<String, Object>> listRef(List<Map<String, Object>> listParameter, Map<String, Model> definitions) {
        List<Map<String, Object>> refs = new ArrayList<>();
        for (Map<String, Object> parameter : listParameter) {
            if (StringUtils.equals(parameter.get("ref").toString(), "无")) {
                continue;
            }
            String simpleRef = parameter.get("simpleRef").toString();
            setRef(refs, simpleRef, definitions);
        }
        return refs;
    }

    /**
     * 设置引用
     *
     * @param refs
     * @param simpleRef
     * @param definitions
     */
    private void setRef(List<Map<String, Object>> refs, String simpleRef, Map<String, Model> definitions) {
        for (Map.Entry<String, Model> definitionKV : definitions.entrySet()) {
            if (StringUtils.equals(definitionKV.getKey(), simpleRef)) {
                Model model = definitionKV.getValue();
                if (model instanceof ModelImpl) {
                    ModelImpl modelImpl = (ModelImpl) model;
                    Map<String, Property> modelProperties = model.getProperties();
                    if (modelProperties == null) {
                        return;
                    }

                    Map<String, Object> requestParameterRef = new HashMap<>();
                    // 这里提前添加为了保证递归时的顺序
                    refs.add(requestParameterRef);
                    requestParameterRef.put("class", model.getClass());
                    requestParameterRef.put("name", definitionKV.getKey());
                    requestParameterRef.put("type", modelImpl.getType());
                    requestParameterRef.put("modelProperties", modelProperties);
                    List<Map<String, Object>> modelPropertiesList = new ArrayList<>();
                    // 这里提前添加为了保证递归时的顺序
                    requestParameterRef.put("modelPropertiesList", modelPropertiesList);
                    for (Map.Entry<String, Property> modelPropertiesKV : modelProperties.entrySet()) {
                        Property modelPropertiesValue = modelPropertiesKV.getValue();
                        Map<String, Object> modelPropertiesMap = new HashMap<>();
                        modelPropertiesMap.put("name", modelPropertiesKV.getKey());
                        modelPropertiesMap.put("description", StringUtils.isBlank(modelPropertiesValue.getDescription()) ? "无" : modelPropertiesValue.getDescription());
                        modelPropertiesMap.put("type", modelPropertiesValue.getType());
                        modelPropertiesMap.put("example", modelPropertiesValue.getExample() == null ? "无" : modelPropertiesValue);
                        modelPropertiesMap.put("class", modelPropertiesValue.getClass());
                        modelPropertiesList.add(modelPropertiesMap);
                        // 递归完成引用类的构建
                        RefProperty subRefProperty = getRefProperty(modelPropertiesValue);
                        String subSimpleRef = "无";
                        if (subRefProperty != null) {
                            subSimpleRef = subRefProperty.getSimpleRef();
                            // 相同引用不递归处理
                            if (!StringUtils.equals(simpleRef, subSimpleRef)) {
                                setRef(refs, subSimpleRef, definitions);
                            }
                        }
                        modelPropertiesMap.put("simpleRef", subSimpleRef);
                    }
                }
                return;
            }
        }
    }

    /**
     * 获取对象示例
     *
     * @param listParameter
     * @param definitions
     * @return
     */
    private List<Map<String, Object>> listExample(List<Map<String, Object>> listParameter, Map<String, Model> definitions) {
        List<Map<String, Object>> exampleList = new ArrayList<>();
        for (Map<String, Object> parameter : listParameter) {
            if (StringUtils.equals(parameter.get("ref").toString(), "无")) {
                continue;
            }
            String simpleRef = parameter.get("simpleRef").toString();
            Map<String, Object> exampleMap = new HashMap<>();
            exampleMap.put("simpleRef", simpleRef);
            // 得到对象示例
            Map<String, Object> example = new HashMap<>();
            setExample(example, simpleRef, definitions);
            // 带格式的JSON输出
            exampleMap.put("example", JSONObject.toJSONString(example));
            exampleList.add(exampleMap);
        }
        return exampleList;
    }

    private void setExample(Map<String, Object> exampleMap, String simpleRef, Map<String, Model> definitions) {
        for (Map.Entry<String, Model> definitionKV : definitions.entrySet()) {
            if (StringUtils.equals(definitionKV.getKey(), simpleRef)) {
                Model model = definitionKV.getValue();
                if (model instanceof ModelImpl) {
                    Map<String, Property> modelProperties = model.getProperties();
                    if (modelProperties == null) {
                        return;
                    }
                    for (Map.Entry<String, Property> modelPropertiesKV : modelProperties.entrySet()) {
                        Property modelPropertiesValue = modelPropertiesKV.getValue();
                        exampleMap.put(modelPropertiesKV.getKey(), "");
                        // 递归完成引用类的构建
                        RefProperty subRefProperty = getRefProperty(modelPropertiesValue);
                        if (subRefProperty != null) {
                            String subSimpleRef = subRefProperty.getSimpleRef();
                            // 相同引用不递归处理
                            if (!StringUtils.equals(simpleRef, subSimpleRef)) {
                                Map<String, Object> subExampleMap = new HashMap<>();
                                if (StringUtils.equals(modelPropertiesValue.getType(), "array")) {
                                    List<Map<String, Object>> subExampleList = new ArrayList<>();
                                    subExampleList.add(subExampleMap);
                                    exampleMap.put(modelPropertiesKV.getKey(), subExampleList);
                                } else {
                                    exampleMap.put(modelPropertiesKV.getKey(), subExampleMap);
                                }
                                setExample(subExampleMap, subSimpleRef, definitions);
                            }
                        }
                    }
                }
                return;
            }
        }
    }

    /**
     * 获取引用属性
     *
     * @param modelPropertiesValue
     * @return
     */
    private RefProperty getRefProperty(Property modelPropertiesValue) {
        RefProperty subRefProperty = null;
        if (modelPropertiesValue instanceof ArrayProperty) {
            ArrayProperty arrayProperty = (ArrayProperty) modelPropertiesValue;
            Property items = arrayProperty.getItems();
            if (items instanceof RefProperty) {
                subRefProperty = (RefProperty) items;
            }
        } else if (modelPropertiesValue instanceof RefProperty) {
            subRefProperty = (RefProperty) modelPropertiesValue;
        }
        return subRefProperty;
    }

    private Template getTemplate(String fileName) throws IOException {
        return configuration.getTemplate(fileName, ISystemConstant.CHARSET_UTF8);
    }

}
