package com.tykj.workflowcore.api.service;

import com.alibaba.fastjson.JSON;
import com.tykj.workflowcore.api.annotations.Callable;
import com.tykj.workflowcore.api.annotations.CallableApi;
import com.tykj.workflowcore.api.entity.ApiInfo;
import com.tykj.workflowcore.api.entity.EntityInfo;
import com.tykj.workflowcore.api.entity.Parameter;
import com.tykj.workflowcore.base.util.ClassUtil;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

import static java.lang.String.format;
import static java.util.Objects.nonNull;

@Slf4j
@Service
public class ApiService {

    private final SpringBeanService springBeanService;

    public ApiService(SpringBeanService springBeanService) {
        this.springBeanService = springBeanService;
    }

    /**
     * 读取主项目中的所有带有@CallAble注解的类中带有@CallAbleApi注解的方法
     * 解析成一个结构化列表数据并返回
     *
     * @return 结构化列表数据，内容为带有指定注解的方法列表，也就是列出主项目中可调用的方法。
     */
    public List<ApiInfo> findAll() {
        //获取所有类
        List<Class<?>> classes = ClassUtil.loadClassByLoader(getClass().getClassLoader());
        return classes.stream()
                .flatMap(clz -> classInfo(clz).stream())
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    private List<ApiInfo> classInfo(Class<?> clz) {
        try {
            //确认Callable注解
            if (clz.isAnnotationPresent(Callable.class)) {
                return Arrays.stream(clz.getMethods())
                        .map(method -> apiInfo(clz.getName(), method))
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ArrayList<>();
    }

    private ApiInfo apiInfo(String className, Method method) {
        //确认CallableApi注解
        if (method.isAnnotationPresent(CallableApi.class)) {
            List<EntityInfo> params = Arrays.stream(method.getParameters())
                    .map(parameter -> entityInfo(parameter.getType(), parameter.getName()))
                    .collect(Collectors.toList());
            Class<?> returnType = method.getReturnType();
            EntityInfo ret = entityInfo(returnType, Strings.EMPTY);
            ApiOperation methodAnnotation = method.getAnnotation(ApiOperation.class);
            String description = Strings.EMPTY;
            if (nonNull(methodAnnotation)) {
                description = methodAnnotation.value();
            }
            return new ApiInfo(className, method.getName(), params, ret, description);
        } else {
            return null;
        }
    }

    private EntityInfo entityInfo(Class<?> clz, String name) {
        String classType = clz.getCanonicalName();
        List<EntityInfo> fields = new ArrayList<>();
        String description = Strings.EMPTY;
        if (isNotBasicClass(clz)) {
            for (Field field : clz.getDeclaredFields()) {
                Class<?> fieldClass = field.getType();
                String fieldName = field.getName();
                field.setAccessible(true);
                //读取swagger描述信息
                ApiModelProperty fieldAnnotation = field.getAnnotation(ApiModelProperty.class);
                if (nonNull(fieldAnnotation)) {
                    description = fieldAnnotation.value();
                }
                //确认类中是否嵌套了相同类型的字段 防止死循环
                boolean isDifferentClass = !Objects.equals(clz, fieldClass);
                EntityInfo fieldClassInfo;
                if (isDifferentClass) {
                    fieldClassInfo = entityInfo(fieldClass, fieldName);
                } else {
                    fieldClassInfo = new EntityInfo(fieldClass.getSimpleName(), field.getName(), new ArrayList<>(), description);
                }
                fields.add(fieldClassInfo);
            }
        }
        return new EntityInfo(
                classType,
                name,
                fields,
                description
        );
    }

    private Boolean isNotBasicClass(Class<?> clz) {
        String packagePath = nonNull(clz.getPackage()) ? clz.getPackage().getName() : Strings.EMPTY;
        return !Objects.equals(packagePath, "java.lang")
                && !Objects.equals(packagePath, "java.util");
    }

    /**
     * 提供参数，调用指定类的指定方法，返回调用后的结果。
     * 出异常时返回null
     *
     * @param className  类名
     * @param apiName    方法名
     * @param parameters 参数数据
     * @return 调用后的返回值
     */
    public Object invoke(String className, String apiName, List<Parameter> parameters) {
        try {
            Class<?> clz = getClass(className);
            Class<?>[] parameterTypes = parameters.stream()
                    .map(Parameter::getClassName)
                    .map(this::getClass)
                    .toArray(Class[]::new);
            Method method = clz.getMethod(apiName, parameterTypes);
            Object bean = springBeanService.getBean(clz);
            Object[] params = parameters.stream()
                    .map(parameter -> toBean(parameter.getInstance(), getClass(parameter.getClassName())))
                    .toArray();
            return method.invoke(bean, params);
        } catch (RuntimeException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            return null;
        }
    }

    private Class<?> getClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            log.error(format("未能找到该类：%s", className));
            throw new RuntimeException();
        }
    }

    /**
     * 把map转成指定类型的JavaBean对象
     */
    public static <T> T toBean(Map<String, Object> map, Class<T> clazz) {
        return JSON.parseObject(JSON.toJSONString(map), clazz);
    }

}