package com.ruoyi.web.core.config;


import cn.hutool.core.collection.CollectionUtil;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.StringUtils;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import springfox.documentation.builders.ExampleBuilder;
import springfox.documentation.builders.ResponseBuilder;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ResponseBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.ResponseContext;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

// Swagger响应字段类型映射插件 zhao
@Configuration
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1001)
public class SwaggerResponsePlugin implements ResponseBuilderPlugin
{
    @Autowired
    private TypeResolver typeResolver;

    private static final String DEFAULT_ROOT_NAME = "* 响应根对象";

    private static final String DEFAULT_RESPONSE_NAME = "响应对象";

    @Override
    public void apply(ResponseContext responseContext)
    {
        try
        {
            ResolvedType returnType = responseContext.getOperationContext().getReturnType();
            Class<?> type = returnType.getErasedType();

            if(TableDataInfo.class.equals(type))
            {
                HandleReturnObject_TableDataInfo(responseContext);
            }
            else if(AjaxResult.class.equals(type))
            {
                HandleReturnObject_AjaxResult(responseContext);
            }
            else if(R.class.equals(type))
            {
                HandleReturnObject_R(responseContext);
            }
//            else
//            {
//                HandleReturnObject(responseContext);
//            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public boolean supports(DocumentationType documentationType)
    {
        return true;
    }

    private List<ResponseTypeMap> GetAnnotations(ResponseContext responseContext)
    {
        OperationContext operationContext = responseContext.getOperationContext();
        List<ResponseTypeMap> res = new ArrayList<>();

        Optional<ResponseTypeMap> annotation = operationContext.findAnnotation(ResponseTypeMap.class);
        annotation.ifPresent(res::add);

        Optional<ResponseTypeMapGroup> annotations = operationContext.findAnnotation(ResponseTypeMapGroup.class);
        annotations.ifPresent(responseTypeMaps -> {
            res.addAll(Arrays.asList(responseTypeMaps.value()));
        });
        return res;
    }

    private void HandleReturnObject(ResponseContext responseContext)
    {
        ResolvedType returnType = responseContext.getOperationContext().getReturnType();
        Class<?> type = returnType.getErasedType();
        ResponseBuilder responseBuilder = responseContext.responseBuilder();

        String name = DEFAULT_ROOT_NAME;

        // UNKNOWN USAGE
        responseContext.getDocumentationContext()
                .getAdditionalModels()
                .add(typeResolver.resolve(type));  //向documentContext的Models中添加我们新生成的Class

        ExampleBuilder builder = new ExampleBuilder()
                .summary(name)
                .value(ClassToJson(type))
                .mediaType(DEFAULT_RESPONSE_NAME)
                .id(name)
                ;
        responseBuilder.examples(Collections.singletonList(builder.build()));

        List<ResponseTypeMap> responseTypeMaps = GetAnnotations(responseContext);
        responseTypeMaps.forEach((x) -> HandleAnnotation(responseContext, x));
    }

    private void HandleReturnObject_TableDataInfo(ResponseContext responseContext)
    {
        List<ResponseTypeMap> responseTypeMaps = GetAnnotations(responseContext);
        Optional<ResponseTypeMap> first = responseTypeMaps.stream().filter((x) -> "rows[0]".equals(x.name())).findFirst();
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("code", "消息状态码 [int]");
        map.put("msg", "消息内容 [string]");
        if(first.isPresent())
        {
            ResponseTypeMap responseTypeMap = first.get();
            Object m = GetClassFields(responseTypeMap.type(), responseTypeMap.genericType());
            List<Object> f = Collections.singletonList(m);
            map.put("rows", f);

            responseTypeMaps.remove(responseTypeMap);
        }
        else
        {
            map.put("rows", "列表数据 [Array]");
        }
        map.put("total", "总记录数 [long]");
        ExampleBuilder builder = new ExampleBuilder()
                .summary(DEFAULT_ROOT_NAME)
                .value(SwaggerDocUtils.ToVisualJson(map))
                .mediaType(DEFAULT_RESPONSE_NAME)
                .id(DEFAULT_ROOT_NAME)
                ;
        responseContext.responseBuilder().examples(Collections.singletonList(builder.build()));

        responseTypeMaps.forEach((x) -> HandleAnnotation(responseContext, x));
    }

    private void HandleReturnObject_AjaxResult(ResponseContext responseContext)
    {
        List<ResponseTypeMap> responseTypeMaps = GetAnnotations(responseContext);
        Optional<ResponseTypeMap> first = responseTypeMaps.stream().filter((x) -> "data".equals(x.name())).findFirst();
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("code", "状态码 [int]");
        map.put("msg", "返回内容 [string]");
        if(first.isPresent())
        {
            ResponseTypeMap responseTypeMap = first.get();
            Object m = GetClassFields(responseTypeMap.type(), responseTypeMap.genericType());
            map.put("data", m);

            responseTypeMaps.remove(responseTypeMap);
        }
        else
        {
            map.put("data", "具体数据 [object]");
        }

        responseTypeMaps.forEach((x) -> {
            Object m = GetClassFields(x.type(), x.genericType());
            map.put(x.name(), m);
        });

        ExampleBuilder builder = new ExampleBuilder()
                .summary(DEFAULT_ROOT_NAME)
                .value(SwaggerDocUtils.ToVisualJson(map))
                .mediaType(DEFAULT_RESPONSE_NAME)
                .id(DEFAULT_ROOT_NAME)
                ;
        responseContext.responseBuilder().examples(Collections.singletonList(builder.build()));

    }

    private void HandleReturnObject_R(ResponseContext responseContext)
    {
        ResolvedType returnType = responseContext.getOperationContext().getReturnType();
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("code", "状态码 [int]");
        map.put("msg", "返回内容 [string]");
        List<ResponseTypeMap> responseTypeMaps = GetAnnotations(responseContext);
        List<ResolvedType> typeParameters = returnType.getTypeBindings().getTypeParameters();
        if(CollectionUtil.isNotEmpty(typeParameters))
        {
            ResolvedType resolvedType = typeParameters.get(0);
            Class<?> erasedType = resolvedType.getErasedType();
            List<ResolvedType> innerTypeParameters = resolvedType.getTypeParameters();
            Object m;
            if(CollectionUtil.isNotEmpty(innerTypeParameters))
            {
                m = GetClassFields(erasedType, innerTypeParameters.get(0).getErasedType());
            }
            else
                m = GetClassFields(erasedType);
            map.put("data", m);
        }
        else
        {
            Optional<ResponseTypeMap> first = responseTypeMaps.stream().filter((x) -> "data".equals(x.name())).findFirst();
            if(first.isPresent())
            {
                ResponseTypeMap responseTypeMap = first.get();
                Object m = GetClassFields(responseTypeMap.type(), responseTypeMap.genericType());
                map.put("data", m);

                responseTypeMaps.remove(responseTypeMap);
            }
            else
            {
                map.put("data", "具体数据 [object]");
            }
        }

        responseTypeMaps.forEach((x) -> {
            Object m = GetClassFields(x.type(), x.genericType());
            map.put(x.name(), m);
        });

        ExampleBuilder builder = new ExampleBuilder()
                .summary(DEFAULT_ROOT_NAME)
                .value(SwaggerDocUtils.ToVisualJson(map))
                .mediaType(DEFAULT_RESPONSE_NAME)
                .id(DEFAULT_ROOT_NAME)
                ;
        responseContext.responseBuilder().examples(Collections.singletonList(builder.build()));
    }

    private void HandleAnnotation(ResponseContext responseContext, ResponseTypeMap responseTypeMap)
    {
        ResponseBuilder responseBuilder = responseContext.responseBuilder();

        String name = responseTypeMap.name();
        Class<?> type = responseTypeMap.type();
        String mediaType = responseTypeMap.mediaType();
        String desc = responseTypeMap.desc();

        // UNKNOWN USAGE
        responseContext.getDocumentationContext()
                .getAdditionalModels()
                .add(typeResolver.resolve(type));  //向documentContext的Models中添加我们新生成的Class

        ExampleBuilder builder = new ExampleBuilder()
                .description(desc)
                .summary(" - " + name)
                .value(ClassToJson(type))
                .mediaType(mediaType)
                .id(name)
                ;
        responseBuilder.examples(Collections.singletonList(builder.build()));
    }

    private String ClassToJson(Class<?> type, Class<?>...genericType)
    {
        return SwaggerDocUtils.ToVisualJson(GetClassFields(type, genericType));
    }

    private Object GetClassFields(Class<?> clazz, Class<?>...genericType)
    {
        if(Collection.class.isAssignableFrom(clazz))
        {
            List<Object> list = new ArrayList<>();
            Class<?> c;
            if(null != genericType && genericType.length > 0)
            {
                c = genericType[0];
                Map<String, Object> map = new LinkedHashMap<>();
                FetchClassFields(c, map, new HashSet<>());
                if(!map.isEmpty())
                    list.add(map);
                else
                {
                    list.add("数据对象1 [" + c.getSimpleName() + "]");
                    list.add("数据对象2 [" + c.getSimpleName() + "]");
                    list.add("数据对象3 [" + c.getSimpleName() + "]");
                    list.add("数据对象 ......");
                }
            }
            else
            {
                list.add("数据对象1 [Any]");
                list.add("数据对象2 [Any]");
                list.add("数据对象3 [Any]");
                list.add("数据对象 ......");
            }
            return list;
        }
        else if(SwaggerDocUtils.IgnoreClass(clazz))
        {
            return SwaggerDocUtils.ConvJSTypeName(clazz);
        }
        else
        {
            Map<String, Object> map = new LinkedHashMap<>();
            FetchClassFields(clazz, map, new HashSet<>());
            return map;
        }
    }

    private void FetchClassFields(Class<?> clazz, Map<String, Object> map, Set<Field> ignoreClass)
    {
        if(SwaggerDocUtils.IgnoreClass(clazz))
            return;

        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field field : declaredFields)
        {
            String name = field.getName();
            if(map.containsKey(name))
                continue; // 基类不覆盖派生类

            if(SwaggerDocUtils.IgnoreField(field))
                continue;

            Class<?> t = field.getType();

            if(!ignoreClass.contains(field) && Collection.class.isAssignableFrom(t))
            {
                Set<Field> ic = new HashSet<>();
                ic.addAll(ignoreClass);
                ic.add(field);
                map.put(name, Collections.singletonList(FetchCollectionFields(field.getGenericType(), ic)));
                continue;
            }

            if(!ignoreClass.contains(field) && BaseEntity.class.isAssignableFrom(t))
            {
                Set<Field> ic = new HashSet<>();
                ic.addAll(ignoreClass);
                ic.add(field);
                Map<String, Object> m = new LinkedHashMap<>();
                FetchClassFields(t, m, ic);
                map.put(name, m);
                continue;
            }

            String desc = "";
            String type = SwaggerDocUtils.ConvJSTypeName(t);
            String what = "";
            Excel annotation = field.getAnnotation(Excel.class);
            if(null != annotation)
            {
                what = annotation.name();
                String dictType = annotation.dictType();
                if(StringUtils.isNotEmpty(dictType))
                    desc += "字典:" + dictType + " ";
                String readConverterExp = annotation.readConverterExp();
                if(StringUtils.isNotEmpty(readConverterExp))
                    desc += "转换表达式:" + readConverterExp + " ";
                String dateFormat = annotation.dateFormat();
                if(StringUtils.isNotEmpty(dateFormat))
                    desc += "日期格式:" + dateFormat + " ";
            }
            ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
            if(null != apiModelProperty)
            {
                what = apiModelProperty.value();
                desc += apiModelProperty.required() ? "必填" : "可选";
                desc += " ";
            }

            String value = desc + " [" + what + " " +  type + "]";
            map.put(name, value);
        }

        Class<?> superclass = clazz.getSuperclass();

        FetchClassFields(superclass, map, ignoreClass);
    }

    private Object FetchCollectionFields(Type genericType, Set<Field> ignoreClass)
    {
        Map<String, Object> map = new LinkedHashMap<>();
        if (genericType instanceof ParameterizedType) {
            ParameterizedType ptype = (ParameterizedType)genericType;
            Type[] actualTypeArguments = ptype.getActualTypeArguments();
            if(null != actualTypeArguments && actualTypeArguments.length > 0)
            {
                Type actualTypeArgument = actualTypeArguments[0];
                if(actualTypeArgument instanceof Class<?>)
                {
                    Class<?> c = (Class<?>) actualTypeArgument;
                    FetchClassFields(c, map, ignoreClass);
                }
                else if(actualTypeArgument instanceof ParameterizedType)
                {
                    ParameterizedType pt = (ParameterizedType)actualTypeArgument;
                    Type rawType = pt.getRawType();
                    if(rawType instanceof Class<?> && Collection.class.isAssignableFrom((Class<?>)rawType))
                    {
                        List<Object> list = new ArrayList<>();
                        list.add(FetchCollectionFields(pt, ignoreClass));
                        return list;
                    }
                }
            }
        }
        return map;
    }
}
