package com.wei.base.springframework.swagger.config;

import com.google.common.collect.Maps;
import com.wei.base.springframework.core.web.config.ResponseHandlerConfigure;
import com.wei.base.springframework.util.StringUtil;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import springfox.documentation.oas.mappers.ServiceModelToOpenApiMapperImpl;
import springfox.documentation.service.Documentation;

import java.beans.PropertyDescriptor;
import java.util.Map;

/**
 * swagger页面展示包装类封装
 *
 * @author : weierming
 * @date : 2020/9/16
 */
@Primary
@Component
public class Swagger3ServiceModelToOpenApiMapperImpl extends ServiceModelToOpenApiMapperImpl {

    @Autowired
    private ResponseHandlerConfigure responseHandlerConfigure;

    // 返回包装对象类型
    private static final String RESTFUL_SCHEMA_TYPE = "object";

    // 请求路径对象地址
    private static final String COMPONENTS_SCHEMAS_PATH = "#/components/schemas/%s";

    @Override
    public OpenAPI mapDocumentation(Documentation from) {
        OpenAPI openAPI = super.mapDocumentation(from);
        Components components = openAPI.getComponents();
        Map<String, Schema> schemas = components.getSchemas();

        Class<?> restfulClass = responseHandlerConfigure.getRestfulVO().getClass();
        Map<String, String> pathsResponsesObjectMap = getPathsResponsesObjectMap(schemas, restfulClass);
        replacePathsPathsResponsesObject(openAPI.getPaths(), pathsResponsesObjectMap, restfulClass);
        return openAPI;
    }

    /**
     * 获取路径返回对象转换ma
     *
     * @param schemas      Components中的schemas
     * @param restfulClass 返回对象class
     * @return key 路径原对象 value 封装后的对象
     */
    private Map<String, String> getPathsResponsesObjectMap(Map<String, Schema> schemas, Class<?> restfulClass) {
        if (MapUtils.isEmpty(schemas)) {
            return Maps.newHashMap();
        }

        Map<String, String> map = Maps.newHashMapWithExpectedSize(schemas.size());
        String restfulVOName = responseHandlerConfigure.getRestfulVOName();
        Map<String, Schema> newSchemas = Maps.newHashMapWithExpectedSize(schemas.size());
        for (Map.Entry<String, Schema> entry : schemas.entrySet()) {
            Schema schema = entry.getValue();
            // 判断返回对象是否已经包含默认包装类格式
            if (StringUtil.containsIgnoreCase(entry.getKey(), restfulVOName)) {
                continue;
            }

            // 判断对象集合里该对象是否已存在
            String restfulSchemaName = restfulVOName + String.format("«%s»", schema.getName());
            String schemaName = String.format(COMPONENTS_SCHEMAS_PATH, schema.getName());
            map.put(schemaName, String.format(COMPONENTS_SCHEMAS_PATH, restfulSchemaName));
            // 判断转换的对象在Components的schemas中是否存在,如果存在则不用继续往schemas中添加对象
            if (schemas.containsKey(restfulSchemaName)) {
                continue;
            }

            Schema restfulSchema = getRestfulSchema(schema, restfulClass, schemaName);
            newSchemas.put(restfulSchemaName, restfulSchema);
        }

        schemas.putAll(newSchemas);
        return map;
    }

    /**
     * 获取自定义封装返回的schema对象
     *
     * @param schema       原路径返回的schema
     * @param restfulClass 返回对象类
     * @return 自定义封装返回的schema对象
     */
    private Schema getRestfulSchema(Schema schema, Class<?> restfulClass, String schemaName) {
        // 通过反射获取返回对象的属性
        PropertyDescriptor[] propertyDescriptors = ReflectUtils.getBeanProperties(restfulClass);
        Schema restfulSchema = new Schema();
        if (propertyDescriptors == null) {
            return restfulSchema;
        }

        Map<String, Schema> restfulVOProperties = Maps.newHashMap();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            String name = propertyDescriptor.getName();
            // 如果schema为空则把data去掉只返回状态码和内容
            if (schema == null && StringUtil.equals(name, "data")) {
                continue;
            }

            Schema restfulPropertiesSchema = new Schema();

            restfulPropertiesSchema.setName(name);
            restfulPropertiesSchema.setTitle(name);
            if (StringUtil.equals(name, "data") && StringUtil.isNotBlank(schemaName)) {
                restfulPropertiesSchema.set$ref(schemaName);
            } else {
                String typeName = propertyDescriptor.getPropertyType().getTypeName();
                restfulPropertiesSchema.setType(StringUtil.lowerCase(StringUtil.getClassName(typeName)));
            }
            restfulVOProperties.put(name, restfulPropertiesSchema);
        }

        // 返回自定义返回对象名称
        String restfulSchemaName = StringUtil.getClassName(restfulClass.getTypeName());
        if (schema != null) {
            // 拼接泛型对象
            restfulSchemaName += String.format("«%s»", schema.getName());
        }

        restfulSchema.setName(restfulSchemaName);
        restfulSchema.setTitle(restfulSchemaName);
        restfulSchema.setType(RESTFUL_SCHEMA_TYPE);
        restfulSchema.setProperties(restfulVOProperties);
        return restfulSchema;
    }

    /**
     * 替换 路径中的Responses返回对象
     *
     * @param paths                   路径对象
     * @param pathsResponsesObjectMap 路径替换对象map,key 路径原对象 value 封装后的对象
     * @param restfulClass            返回对象class
     */
    private void replacePathsPathsResponsesObject(Paths paths, Map<String, String> pathsResponsesObjectMap,
                                                  Class<?> restfulClass) {
        if (MapUtils.isEmpty(paths)) {
            return;
        }

        for (Map.Entry<String, PathItem> mapEntry : paths.entrySet()) {
            Operation operation = getOperation(mapEntry.getValue());
            if (operation == null) {
                continue;
            }

            Content content = getContent(operation, restfulClass);
            if (content == null) {
                continue;
            }

            for (Map.Entry<String, MediaType> contentEntry : content.entrySet()) {
                Schema mediaTypeSchema = contentEntry.getValue().getSchema();
                String ref = mediaTypeSchema.get$ref();
                if (!pathsResponsesObjectMap.containsKey(ref)) {
                    continue;
                }

                mediaTypeSchema.set$ref(pathsResponsesObjectMap.get(ref));
            }
        }
    }

    /**
     * 根据请求方式获取请求地址对应的Operation
     *
     * @param pathItem 路径对象
     * @return 返回路径对用的请求操作对象
     */
    private Operation getOperation(PathItem pathItem) {
        if (pathItem.getGet() != null) {
            return pathItem.getGet();
        } else if (pathItem.getPost() != null) {
            return pathItem.getPost();
        } else if (pathItem.getPut() != null) {
            return pathItem.getPut();
        } else if (pathItem.getDelete() != null) {
            return pathItem.getDelete();
        } else if (pathItem.getOptions() != null) {
            return pathItem.getOptions();
        } else if (pathItem.getHead() != null) {
            return pathItem.getHead();
        } else if (pathItem.getPatch() != null) {
            return pathItem.getPatch();
        } else if (pathItem.getTrace() != null) {
            return pathItem.getTrace();
        } else {
            return null;
        }
    }

    /**
     * 获取Content
     *
     * @param operation
     * @return
     */
    private Content getContent(Operation operation, Class<?> restfulClass) {
        if (operation == null) {
            return null;
        }

        ApiResponses responses = operation.getResponses();
        ApiResponse apiResponse = responses.get(String.valueOf(HttpStatus.OK.value()));
        Content content = apiResponse.getContent();
        // 方法放回viod时content会为空
        if (content.isEmpty()) {
            MediaType mediaType = new MediaType();
            mediaType.schema(getRestfulSchema(null, restfulClass, null));

            // 添加返回对象
            content.addMediaType(org.springframework.http.MediaType.ALL_VALUE, mediaType);
        }

        return content;
    }
}