package com.ytpm.util;

import com.ytpm.handle.DynamicFeignClientFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * FeignClient公共调用类
 * @author Marx
 * @date 2025/8/19 17:22
 */
@Slf4j
@Component
public class FeignClientInvoker {

    private final ApplicationContext applicationContext;
    private final Map<String, Object> feignClientCache = new ConcurrentHashMap<>();
    private final Map<String, Method> methodCache = new ConcurrentHashMap<>();
    @Value("${risk.feign.timeout-ms:2000}")
    private int feignTimeoutMs;
    @Value("${risk.feign.max-concurrent:50}")
    private int maxConcurrent;
    private Semaphore bulkhead;
    @Resource(name = "riskScheduledExecutorService")
    private ScheduledExecutorService scheduledExecutorService;
    @Resource
    private DynamicFeignClientFactory feignClientFactory;

    public FeignClientInvoker(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 初始化许可证，采用公平锁竞争
     */
    @PostConstruct
    public void init() {
        this.bulkhead = new Semaphore(Math.max(1, maxConcurrent), true);
    }

    /**
     * 动态调用FeignClient方法
     * @param serviceName FeignClient的name属性值
     * @param methodName 要调用的方法名
     * @param args 方法参数
     * @return 调用结果
     */
    public Object invoke(String serviceName, String methodName, Object... args) {
        boolean acquired = bulkhead.tryAcquire();
        if (!acquired) {
            throw new RuntimeException("调用FeignClient被限流(并发超限): " + serviceName);
        }
        try {
            Object feignClient = getFeignClient(serviceName);
            Method method = getTargetMethod(feignClient, methodName, args);
            return CompletableFuture.supplyAsync(() -> {
                try {
                    return method.invoke(feignClient, args);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }, scheduledExecutorService).get(feignTimeoutMs, TimeUnit.MILLISECONDS);
        } catch (java.util.concurrent.TimeoutException te) {
            // 重新获取新实例并尝试调用
            log.warn("service[{}]feign调用方法{}超时,尝试重新获取feignClient", serviceName, methodName);
            try {
                feignClientCache.remove(serviceName);
                Object feignClient = getFeignClient(serviceName);
                Method method = getTargetMethod(feignClient, methodName, args);
                return CompletableFuture.supplyAsync(() -> {
                    try {
                        return method.invoke(feignClient, args);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }, scheduledExecutorService).get(feignTimeoutMs, TimeUnit.MILLISECONDS);
            } catch (java.util.concurrent.TimeoutException te2) {
                throw new RuntimeException("调用FeignClient超时: " + serviceName + ", 方法: " + methodName);
            } catch (Exception e) {
                throw new RuntimeException("调用FeignClient失败: " + serviceName, e);
            } finally {
                bulkhead.release();
            }
        } catch (Exception e) {
            throw new RuntimeException("调用FeignClient失败: " + serviceName, e);
        } finally {
            bulkhead.release();
        }
    }

    private Object getFeignClient(String serviceName) {
        if (feignClientCache.containsKey(serviceName)) {
            return feignClientCache.get(serviceName);
        }
        Object feignClient = null;
        try {
            // 默认查询声明实例
            Map<String, Object> feignClients = applicationContext.getBeansWithAnnotation(FeignClient.class);
            for (Object bean : feignClients.values()) {
                Class<?>[] interfaces = bean.getClass().getInterfaces();
                for (Class<?> iface : interfaces) {
                    FeignClient annotation = iface.getAnnotation(FeignClient.class);
                    if (annotation != null) {
                        String annName = annotation.name().isEmpty() ? annotation.value() : annotation.name();
                        if (serviceName.equals(annName)) {
                            feignClient = bean;
                            break;
                        }
                    }
                }
                if (feignClient != null) {
                    break;
                }
            }
            // 构建动态FeignClient
            if (feignClient == null) {
                Class<?> clazz = com.ytpm.question.base.BaseFeign.class;
                if (serviceName.contains("ios")) {
                    clazz = com.ytpm.lemonios.feign.base.BaseFeign.class;
                }
                feignClient = feignClientFactory.getClient(serviceName, clazz);
            }
            feignClientCache.put(serviceName, feignClient);
        } catch (Exception e) {
            throw new IllegalArgumentException("未找到服务: " + serviceName);
        }
        if (feignClient == null) {
            throw new IllegalArgumentException("未找到服务: " + serviceName);
        }
        return feignClient;
    }

    private Method getTargetMethod(Object feignClient, String methodName, Object[] args)
        throws NoSuchMethodException {
        Class<?> proxyClass = feignClient.getClass();
        Class<?>[] interfaces = proxyClass.getInterfaces();

        Method resolved = null;
        for (Class<?> iface : interfaces) {
            resolved = findCompatibleMethod(iface, methodName, args);
            if (resolved != null) {
                String cacheKey = iface.getName() + "#" + resolved.getName() + signatureOf(resolved.getParameterTypes());
                Method cached = methodCache.putIfAbsent(cacheKey, resolved);
                return cached != null ? cached : resolved;
            }
        }
        // 兜底：在代理类上尝试（不推荐，但避免因代理结构差异导致失败）
        resolved = findCompatibleMethod(proxyClass, methodName, args);
        if (resolved != null) {
            String cacheKey = proxyClass.getName() + "#" + resolved.getName() + signatureOf(resolved.getParameterTypes());
            Method cached = methodCache.putIfAbsent(cacheKey, resolved);
            return cached != null ? cached : resolved;
        }
        throw new NoSuchMethodException("未找到方法: " + methodName);
    }

    private Method findCompatibleMethod(Class<?> type, String methodName, Object[] args) {
        Method[] methods = type.getMethods();
        for (Method m : methods) {
            if (!m.getName().equals(methodName)) {
                continue;
            }
            Class<?>[] paramTypes = m.getParameterTypes();
            if (paramTypes.length != (args == null ? 0 : args.length)) {
                continue;
            }
            boolean match = true;
            for (int i = 0; i < paramTypes.length; i++) {
                Object arg = args[i];
                if (arg == null) {
                    continue; // 允许null匹配任意引用类型
                }
                if (!wrap(paramTypes[i]).isAssignableFrom(arg.getClass())) {
                    match = false;
                    break;
                }
            }
            if (match) {
                return m;
            }
        }
        return null;
    }

    private Class<?> wrap(Class<?> type) {
        if (!type.isPrimitive()) {
            return type;
        }
        if (type == int.class) return Integer.class;
        if (type == long.class) return Long.class;
        if (type == boolean.class) return Boolean.class;
        if (type == double.class) return Double.class;
        if (type == float.class) return Float.class;
        if (type == char.class) return Character.class;
        if (type == byte.class) return Byte.class;
        if (type == short.class) return Short.class;
        return type;
    }

    private String signatureOf(Class<?>[] paramTypes) {
        StringBuilder sb = new StringBuilder("(");
        for (int i = 0; i < paramTypes.length; i++) {
            if (i > 0) sb.append(",");
            sb.append(paramTypes[i].getName());
        }
        sb.append(")");
        return sb.toString();
    }
}