package cd.casic.plugin.core.register;

import cd.casic.plugin.config.PluginProperties;
import cd.casic.plugin.core.register.filter.impl.ControllerFilter;
import cd.casic.plugin.dataobject.pojo.PluginInfo;
import cd.casic.plugin.function.IPluginProcessor;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;

/**
 * @description: TODO
 * @author: mianbin
 * @date: 2025/5/22 20:10
 * @version: 1.0
 */
@Slf4j
@Component
public class ControllerProcessor implements IPluginProcessor {

    RequestMappingHandlerMapping requestMappingHandlerMapping;

    Method getMappingForMethod;

    @Override
    public void initialize() throws Exception {
        // 这里反射获取 getMappingForMethod
        requestMappingHandlerMapping = SpringUtil.getBean(RequestMappingHandlerMapping.class);
        getMappingForMethod = ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingForMethod", Method.class, Class.class);
        getMappingForMethod.setAccessible(true);
    }

    @Override
    public void registry(PluginInfo plugin) throws Exception {
        ApplicationContext applicationContext = plugin.getMainApplicationContext();
        PluginProperties pluginSystemProperties = applicationContext.getBean(PluginProperties.class);
        for (Class<?> aClass : plugin.getGroupClass(ControllerFilter.GROUP_NAME)) {
            setPathPrefix(plugin.getPluginId(), aClass, pluginSystemProperties);
            plugin.addController(aClass);
            Object bean = plugin.getPluginApplicationContext().getBean(aClass);
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                if (method.getAnnotation(RequestMapping.class) != null
                        || method.getAnnotation(GetMapping.class) != null
                        || method.getAnnotation(PostMapping.class) != null
                        || method.getAnnotation(DeleteMapping.class) != null
                        || method.getAnnotation(PutMapping.class) != null
                        || method.getAnnotation(PatchMapping.class) != null) {
                    RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass);
                    requestMappingHandlerMapping.registerMapping(requestMappingInfo, bean, method);
                }
            }
        }
    }

    @Override
    public void unRegistry(PluginInfo plugin) throws Exception {
        for (RequestMappingInfo requestMappingInfo : getRequestMappingInfo(plugin)) {
            requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
        }
    }

    List<RequestMappingInfo> getRequestMappingInfo(PluginInfo plugin) throws Exception {
        List<RequestMappingInfo> requestMappingInfoList = new ArrayList<>();
        for (Class<?> aClass : plugin.getGroupClass(ControllerFilter.GROUP_NAME)) {
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass);
                requestMappingInfoList.add(requestMappingInfo);
            }
        }
        return requestMappingInfoList;
    }

    /**
     * 设置请求路径前缀
     *
     * @param aClass controller 类
     */
    private void setPathPrefix(String pluginId, Class<?> aClass, PluginProperties pluginSystemProperties) {
        RequestMapping requestMapping = aClass.getAnnotation(RequestMapping.class);
        if (requestMapping == null) {
            return;
        }
        String pathPrefix = pluginSystemProperties.getRestPathPrefix();
        if (pluginSystemProperties.isEnablePluginIdAsRestPrefix()) {
            if (StrUtil.isNotEmpty(pathPrefix)) {
                pathPrefix = joiningPath(pathPrefix, pluginId);
            } else {
                pathPrefix = pluginId;
            }
        } else {
            if (StrUtil.isEmpty(pathPrefix)) {
                // 不启用插件id作为路径前缀, 并且路径前缀为空, 则直接返回。
                return;
            }
        }
        handleRequestMapping(requestMapping, pathPrefix);
    }

    @SuppressWarnings("unchecked")
    private void handleRequestMapping(RequestMapping requestMapping, String pathPrefix) {
        InvocationHandler invocationHandler = Proxy.getInvocationHandler(requestMapping);
        Set<String> definePaths = new HashSet<>();
        definePaths.addAll(Arrays.asList(requestMapping.path()));
        definePaths.addAll(Arrays.asList(requestMapping.value()));
        try {
            Field field = invocationHandler.getClass().getDeclaredField("memberValues");
            field.setAccessible(true);
            Map<String, Object> memberValues = (Map<String, Object>) field.get(invocationHandler);
            String[] newPath = new String[definePaths.size()];
            int i = 0;
            for (String definePath : definePaths) {
                // 解决插件启用、禁用后, 路径前缀重复的问题。
                if (definePath.contains(pathPrefix)) {
                    newPath[i++] = definePath;
                } else {
                    newPath[i++] = joiningPath(pathPrefix, definePath);
                }
            }
            if (newPath.length == 0) {
                newPath = new String[]{pathPrefix};
            }
            memberValues.put("path", newPath);
            memberValues.put("value", new String[]{});
        } catch (Exception e) {
            log.error("Define Plugin RestController pathPrefix error : {}", e.getMessage(), e);
        }
    }

    /**
     * 拼接路径
     *
     * @param path1 路径1
     * @param path2 路径2
     * @return 拼接的路径
     */
    private String joiningPath(String path1, String path2) {
        if (path1 != null && path2 != null) {
            if (path1.endsWith("/") && path2.startsWith("/")) {
                return path1 + path2.substring(1);
            } else if (!path1.endsWith("/") && !path2.startsWith("/")) {
                return path1 + "/" + path2;
            } else {
                return path1 + path2;
            }
        } else if (path1 != null) {
            return path1;
        } else if (path2 != null) {
            return path2;
        } else {
            return "";
        }
    }
}
