package org.matrix.autotest.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.matrix.autotest.swaggerEntity.ParameterVo;
import org.matrix.autotest.swaggerEntity.PathInfoVo;
import org.matrix.autotest.swaggerEntity.ResponseVo;
import org.matrix.database.entity.Environment;
import org.matrix.database.service.IEnvironmentService;
import org.matrix.database.vo.CommonPage;
import org.matrix.exception.GlobalException;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author mry
 */
@RestController
@RequestMapping("/swaggers")
@CrossOrigin
@Api(tags = "Swagger接口读取与解析")
public class SwaggerController {

    private final IEnvironmentService environmentService;

    public SwaggerController(IEnvironmentService environmentService) {
        this.environmentService = environmentService;
    }

    /**
     * 解析所有的接口
     *
     * @param swaggerJson swagger中的json数据
     * @return 封装好的接口信息
     */
    public static List<PathInfoVo> getPathInfo(JSONObject swaggerJson) {
        long i = 0L;
        JSONObject paths = swaggerJson.getJSONObject("paths");
        String host = String.valueOf(swaggerJson.get("host"));
        String basePath = String.valueOf(swaggerJson.get("basePath"));
        List<PathInfoVo> list = new ArrayList<>();
        //所有的definitions
        Map<String, JSONObject> refMap = getDefinitions(swaggerJson);
        if (paths != null) {
            for (Map.Entry<String, Object> stringObjectEntry : paths.entrySet()) {
                String pathUrl = stringObjectEntry.getKey();
                //请求方式
                JSONObject pathJson = paths.getJSONObject(pathUrl);
                Set<String> methodSets = pathJson.keySet();
                if (CollectionUtils.isNotEmpty(methodSets)) {
                    for (String httpMethod : methodSets) {
                        PathInfoVo pathInfo = new PathInfoVo();
                        pathInfo.setId(i++);
                        pathInfo.setPathUrl(pathUrl);
                        pathInfo.setHttpMethod(httpMethod);
                        JSONObject methodJson = pathJson.getJSONObject(httpMethod);
                        String summary = methodJson.getString("summary");
                        String tags = methodJson.getString("tags");
                        String description = methodJson.getString("description");
                        pathInfo.setDescription(description);
                        pathInfo.setTags(tags);
                        pathInfo.setSummary(summary);
                        JSONArray parameters = methodJson.getJSONArray("parameters");
                        JSONObject responses = methodJson.getJSONObject("responses");
                        List<ParameterVo> reqParameters = getParameter(parameters, refMap);
                        pathInfo.setReqList(reqParameters);
                        List<ResponseVo> respList = getResponse(responses, refMap);
                        pathInfo.setRespList(respList);
                        pathInfo.setHost(host);
                        pathInfo.setBasePath(basePath);
                        list.add(pathInfo);
                    }
                }
            }
        }
        return list;
    }

    /**
     * 提取公共的$ref解析
     *
     * @param childMap properties中封装的json
     * @return swagger中$ref值对应的value对象名称
     */
    private static String getRef(JSONObject childMap) {
        String childRef = childMap.getString("$ref");
        return childRef.substring(14);
    }

    /**
     * 解析响应数据
     *
     * @param responses 响应参数
     * @param refMap    所有的definitions
     * @return 封装好的响应数据
     */
    public static List<ResponseVo> getResponse(JSONObject responses, Map<String, JSONObject> refMap) {
        List<ResponseVo> respParameters = new ArrayList<>();
        if (responses != null && responses.containsKey("200")) {
            //只解析200的数据
            JSONObject successJson = responses.getJSONObject("200");
            if (successJson.containsKey("schema")) {
                JSONObject schema = successJson.getJSONObject("schema");
                String schemaType = schema.getString("type");
                String ref = "";
                if (schema.containsKey("$ref")) {
                    ref = schema.getString("$ref");
                }
                ResponseVo resp = new ResponseVo();
                if ("array".equalsIgnoreCase(schemaType)) {
                    JSONObject items = schema.getJSONObject("items");
                    if (items.containsKey("$ref")) {
                        ref = schema.getString("$ref");
                        resp.setType(schemaType);
                    } else {
                        List<ResponseVo> childRespList = new ArrayList<>();
                        ResponseVo childResp = new ResponseVo();
                        childResp.setName("");
                        childResp.setType(items.getString("type"));
                        childRespList.add(childResp);
                        resp.setChildResp(childRespList);
                    }
                } else {
                    resp.setType(StringUtils.isBlank(schemaType) ? "object" : schemaType);
                }
                if (StringUtils.isNotBlank(ref)) {
                    String def = ref.substring(14);
                    JSONObject defJson = refMap.get(def);
                    JSONObject properties = defJson.getJSONObject("properties");
                    Set<String> respKeys = properties.keySet();
                    List<ResponseVo> childRespList = new ArrayList<>();
                    for (String key : respKeys) {
                        JSONObject respMap = properties.getJSONObject(key);
                        ResponseVo childResp = new ResponseVo();
                        childResp.setName(key);
                        childResp.setDescription(respMap.getString("description"));
                        String respType = respMap.getString("type");
                        childResp.setType(StringUtils.isBlank(respType) ? "object" : respType);
                        childResp.setRequired(respMap.getBooleanValue("required"));
                        if (respMap.containsKey("$ref")) {
                            String childDef = getRef(respMap);
                            JSONObject childDefJson = refMap.get(childDef);
                            JSONObject childProperties = childDefJson.getJSONObject("properties");
                            getRef(refMap, childProperties, childResp, childDef, childDefJson);
                        } else if ("array".equalsIgnoreCase(respType)) {
                            JSONObject items = respMap.getJSONObject("items");
                            if (items.containsKey("$ref")) {
                                String itemDef = getRef(items);
                                JSONObject itemDefJson = refMap.get(itemDef);
                                JSONObject childProperties = itemDefJson.getJSONObject("properties");
                                getRef(refMap, childProperties, childResp, itemDef, itemDefJson);
                            }
                        }
                        childRespList.add(childResp);
                        resp.setChildResp(childRespList);
                    }
                    respParameters.add(resp);
                }
            }
        }
        return respParameters;
    }

    /**
     * 递归响应数据
     *
     * @param refMap       所有的definitions
     * @param parentVoName 上级ref的名称，与上级相同不继续递归（树结构）
     * @return 封装好的响应数据
     */
    public static List<ResponseVo> getRef(Map<String, JSONObject> refMap, JSONObject childProperties, ResponseVo parentResp, String parentVoName, JSONObject childJson) {
        Set<String> childSet = childProperties.keySet();
        List<ResponseVo> childResp = new ArrayList<>();
        for (String key : childSet) {
            JSONObject childMap = childProperties.getJSONObject(key);
            ResponseVo resp = new ResponseVo();
            resp.setName(key);
            resp.setDescription(childMap.getString("description"));
            String childType = childMap.getString("type");
            resp.setType(StringUtils.isNotBlank(childType) ? childType : childJson.getString("type"));
            resp.setRequired(childMap.getBooleanValue("required"));
            childResp.add(resp);
            parentResp.setChildResp(childResp);
            if (childMap.containsKey("$ref")) {
                String childDef = getRef(childMap);
                JSONObject childDefJson = refMap.get(childDef);
                JSONObject pro = childDefJson.getJSONObject("properties");
                //additionalProperties
                if (pro != null && !childDef.equalsIgnoreCase(parentVoName)) {
                    getRef(refMap, pro, resp, childDef, childDefJson);
                }
            } else if ("array".equalsIgnoreCase(childType)) {
                JSONObject items = childMap.getJSONObject("items");
                if (items.containsKey("$ref")) {
                    String itemDef = getRef(items);
                    JSONObject itemDefJson = refMap.get(itemDef);
                    JSONObject pro = itemDefJson.getJSONObject("properties");
                    if (pro != null && !itemDef.equalsIgnoreCase(parentVoName)) {
                        getRef(refMap, pro, resp, itemDef, itemDefJson);
                    }
                }
            }
        }
        return childResp;
    }

    /**
     * 解析请求参数
     *
     * @param parameters 参数
     * @param refMap     所有的definitions
     * @return 封装好的参数信息
     */
    public static List<ParameterVo> getParameter(JSONArray parameters, Map<String, JSONObject> refMap) {
        List<ParameterVo> reqParameters = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(parameters)) {
            for (int i = 0; i < parameters.size(); i++) {
                JSONObject paramJson = parameters.getJSONObject(i);
                ParameterVo param = JSON.parseObject(JSON.toJSONString(paramJson), ParameterVo.class);
                if (paramJson.containsKey("schema")) {
                    JSONObject schema = paramJson.getJSONObject("schema");
                    String schemaType = schema.getString("type");
                    String ref = "";
                    if (schema.containsKey("$ref")) {
                        ref = schema.getString("$ref");
                    }
                    if ("array".equalsIgnoreCase(schemaType)) {
                        JSONObject items = schema.getJSONObject("items");
                        if (items.containsKey("$ref")) {
                            ref = items.getString("$ref");
                            param.setType(schemaType);
                        } else {
                            List<ParameterVo> childParamList = new ArrayList<>();
                            ParameterVo childParam = new ParameterVo();
                            childParam.setName("");
                            childParam.setType(items.getString("type"));
                            childParamList.add(childParam);
                            param.setType(schemaType);
                            param.setChildParam(childParamList);
                        }
                    } else {
                        param.setType(schemaType);
                    }
                    if (StringUtils.isNotBlank(ref)) {
                        String def = ref.substring(14);
                        JSONObject defJson = refMap.get(def);
                        if (defJson != null) {
                            if (!StringUtils.isNotBlank(param.getType())) {
                                param.setType(defJson.getString("type"));
                            }
                            JSONObject properties = defJson.getJSONObject("properties");
                            Set<String> propertiesSet = properties.keySet();
                            List<ParameterVo> childParamList = new ArrayList<>();
                            for (String key : propertiesSet) {
                                ParameterVo childParam = new ParameterVo();
                                childParam.setName(key);
                                JSONObject proMap = properties.getJSONObject(key);
                                //根据type判断是否是array
                                String type = proMap.getString("type");
                                childParam.setDescription(StringUtils.isNotBlank(proMap.getString("description")) ? proMap.getString("description") : "");
                                childParam.setType(StringUtils.isBlank(type) ? "object" : type);
                                childParam.setRequired(proMap.getBooleanValue("required"));
                                if (proMap.containsKey("$ref")) {
                                    String childDef = getRef(proMap);
                                    JSONObject childDefJson = refMap.get(childDef);
                                    JSONObject childProperties = childDefJson.getJSONObject("properties");
                                    if (childProperties != null) {
                                        getParamRef(refMap, childProperties, childParam, childDef, childDefJson);
                                    }
                                } else if ("array".equalsIgnoreCase(type)) {
                                    JSONObject items = proMap.getJSONObject("items");
                                    if (items.containsKey("$ref")) {
                                        String itemDef = getRef(items);
                                        JSONObject itemDefJson = refMap.get(itemDef);
                                        JSONObject pro = itemDefJson.getJSONObject("properties");
                                        if (pro != null) {
                                            getParamRef(refMap, pro, childParam, itemDef, itemDefJson);
                                        }
                                    }
                                }
                                childParamList.add(childParam);
                                param.setChildParam(childParamList);
                            }
                        }
                    }
                }
                reqParameters.add(param);
            }
        }
        return reqParameters;
    }

    /**
     * 解析$ref中的数据，也就是参数详情
     *
     * @param refMap          所有的definitions
     * @param childProperties 解析后$ref值中的的属性
     * @param parentResp      参数信息
     * @param parentVoName    swagger中$ref值对应的value对象名称
     * @param childJson       $ref中下一级的$ref或者是经过递归已经拿到的json
     */
    public static void getParamRef(Map<String, JSONObject> refMap, JSONObject childProperties, ParameterVo parentResp, String parentVoName, JSONObject childJson) {
        List<ParameterVo> paramList = new ArrayList<>();
        Set<String> childSet = childProperties.keySet();
        for (String key : childSet) {
            JSONObject childMap = childProperties.getJSONObject(key);
            ParameterVo resp = new ParameterVo();
            resp.setDescription(childMap.getString("description"));
            String childType = childMap.getString("type");
            resp.setName(key);
            resp.setType(StringUtils.isNotBlank(childType) ? childType : childJson.getString("type"));
            resp.setRequired(childMap.getBooleanValue("required"));
            paramList.add(resp);
            parentResp.setChildParam(paramList);
            if (childMap.containsKey("$ref")) {
                String childDef = getRef(childMap);
                JSONObject childDefJson = refMap.get(childDef);
                JSONObject pro = childDefJson.getJSONObject("properties");
                //additionalProperties
                if (pro != null && !childDef.equalsIgnoreCase(parentVoName)) {
                    getParamRef(refMap, pro, resp, childDef, childDefJson);
                }
            } else if ("array".equalsIgnoreCase(childType)) {
                JSONObject items = childMap.getJSONObject("items");
                if (items.containsKey("$ref")) {
                    String itemDef = getRef(items);
                    JSONObject itemDefJson = refMap.get(itemDef);
                    JSONObject pro = itemDefJson.getJSONObject("properties");
                    if (pro != null && !itemDef.equalsIgnoreCase(parentVoName)) {
                        getParamRef(refMap, pro, resp, itemDef, itemDefJson);
                    }
                }
            }
        }
    }

    /**
     * 获取所有的关联参数对象
     *
     * @param swaggerJson swagger/v2/api的json信息
     * @return 所有的definitions，传递的参数格式
     */
    public static Map<String, JSONObject> getDefinitions(JSONObject swaggerJson) {
        Map<String, JSONObject> map = new HashMap<>();
        JSONObject definitions = swaggerJson.getJSONObject("definitions");
        Set<String> definitionSet = definitions.keySet();
        for (String def : definitionSet) {
            map.put(def, definitions.getJSONObject(def));
        }
        return map;
    }

    /**
     * 读取swagger地址里的JSON信息
     *
     * @param url swagger地址
     * @return swagger中的JSON数据
     */
    public String loadJson(String url) {
        BufferedReader reader;
        StringBuilder json = new StringBuilder();
        try {
            URL urlObject = new URL(url);
            URLConnection uc = urlObject.openConnection();
            InputStream inputStream = uc.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            String inputLine;
            while ((inputLine = reader.readLine()) != null) {
                json.append(inputLine);
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new GlobalException(String.format("请求swagger数据失败,您读的swagger地址ip为 %s", url));
        }
        return json.toString();
    }

    /**
     * 根据标签名称模糊查询list集合
     *
     * @param name 查询条件: 标签名称
     * @param list 查询的集合
     * @return 根据tag模糊查询的结果
     */
    public List<PathInfoVo> tagSearch(String name, List<PathInfoVo> list) {
        @SuppressWarnings(value = "all") List<PathInfoVo> results = new ArrayList();
        Pattern pattern = Pattern.compile(name, Pattern.CASE_INSENSITIVE);
        for (PathInfoVo o : list) {
            Matcher matcher = pattern.matcher(o.getTags());
            if (matcher.find()) {
                results.add(o);
            }
        }
        return results;
    }

    /**
     * 根据接口名称模糊查询list集合
     *
     * @param name 查询条件: 接口名称
     * @param list 查询的集合
     * @return 根据summary模糊查询的结果
     */
    public List<PathInfoVo> nameSearch(String name, List<PathInfoVo> list) {
        @SuppressWarnings(value = "all") List<PathInfoVo> results = new ArrayList();
        Pattern pattern = Pattern.compile(name, Pattern.CASE_INSENSITIVE);
        for (PathInfoVo o : list) {
            Matcher matcher = pattern.matcher(o.getSummary());
            if (matcher.find()) {
                results.add(o);
            }
        }
        return results;
    }

    /**
     * 根据url糊查询list集合
     *
     * @param name 查询条件: url
     * @param list 查询的集合
     * @return 根据url模糊查询的结果
     */
    public List<PathInfoVo> urlSearch(String name, List<PathInfoVo> list) {
        @SuppressWarnings(value = "all") List<PathInfoVo> results = new ArrayList();
        Pattern pattern = Pattern.compile(name, Pattern.CASE_INSENSITIVE);
        for (PathInfoVo o : list) {
            Matcher matcher = pattern.matcher(o.getPathUrl());
            if (matcher.find()) {
                results.add(o);
            }
        }
        return results;
    }

    /**
     * 查询快速添加的接口
     * name -> summary
     * url  -> methodUrl
     * label -> tags
     *
     * @param projectId 项目id
     * @param id        环境id
     * @param pageSize  每页多少条数据
     * @param pageNum   当前第几页
     * @param name      查询条件: 标签名称
     * @param url       查询条件: 方法的url
     * @return Swagger中的数据
     */
    @GetMapping
    @Transactional(rollbackFor = Exception.class)
    @ApiOperation(value = "查询快速添加的接口")
    public CommonPage<List<PathInfoVo>> parameter(
            Long projectId,
            Long id,
            @RequestParam(defaultValue = "10") int pageSize,
            @RequestParam(defaultValue = "1") int pageNum,
            @RequestParam(required = false, defaultValue = "") String name,
            @RequestParam(required = false, defaultValue = "") String url,
            @RequestParam(required = false, defaultValue = "") String tag) {
        //将所有的默认选项置为false
        environmentService.setIsDefaultByWrapper(false, Wrappers.lambdaQuery(Environment.class).eq(Environment::getProjectId, projectId));
        //将选中的环境置为true,后续默认选择这个环境
        environmentService.setIsDefaultByWrapper(true, Wrappers.lambdaQuery(Environment.class).eq(Environment::getId, id));
        Environment environment = environmentService.getById(id);
        String swaggerUrl;
        try {
            swaggerUrl = String.format("%s/v2/api-docs", environment.getIp());
        } catch (NullPointerException e) {
            e.printStackTrace();
            throw new GlobalException("请先选择环境");
        }
        //获得json字符串
        String json = loadJson(swaggerUrl);
        JSONObject swaggerJson = JSON.parseObject(json, Feature.DisableCircularReferenceDetect);
        List<PathInfoVo> list = getPathInfo(swaggerJson);
        List<PathInfoVo> swaggerSearch = list;
        List<PathInfoVo> swaggerUrlSearch;
        List<PathInfoVo> swaggerTagSearch;
        if ("".equals(tag) && "".equals(name)) {
            swaggerSearch = urlSearch(url, list);
        }
        if ("".equals(name) && "".equals(url)) {
            swaggerSearch = tagSearch(tag, list);
        }
        if ("".equals(url) && "".equals(tag)) {
            swaggerSearch = nameSearch(name, list);
        }
        if ("".equals(name)) {
            swaggerUrlSearch = urlSearch(url, list);
            swaggerSearch = tagSearch(tag, swaggerUrlSearch);
        }
        if ("".equals(tag)) {
            swaggerUrlSearch = urlSearch(url, list);
            swaggerSearch = nameSearch(name, swaggerUrlSearch);
        }
        if ("".equals(url)) {
            swaggerTagSearch = tagSearch(tag, list);
            swaggerSearch = nameSearch(name, swaggerTagSearch);
        }
        if (!"".equals(url) && !"".equals(tag) && !"".equals(name)) {
            swaggerUrlSearch = urlSearch(url, list);
            swaggerTagSearch = tagSearch(tag, swaggerUrlSearch);
            swaggerSearch = nameSearch(name, swaggerTagSearch);
        }
        int total = swaggerSearch.size();
        List<PathInfoVo> subList = swaggerSearch.subList(pageSize * (pageNum - 1), (Math.min((pageNum * pageSize), total)));
        CommonPage<List<PathInfoVo>> listCommonPage = new CommonPage<>();
        listCommonPage.setList(subList);
        listCommonPage.setTotal(total);
        listCommonPage.setPageNum(pageNum);
        listCommonPage.setPageSize(pageSize);
        return listCommonPage;
    }
}
