
package com.seer.teach.common.config.feign;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.cloud.nacos.balancer.NacosBalancer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Slf4j
public class SameIpLoadBalancerClient implements ReactorServiceInstanceLoadBalancer {

    private final List<String> localIps;

    /**
     * 用于获取 serviceId 对应的服务实例的列表
     */
    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    /**
     * 需要获取的服务实例名
     *
     * 暂时用于打印 logger 日志
     */
    private final String serviceId;
    /**
     * 被代理的 ReactiveLoadBalancer 对象
     */
    private final ReactiveLoadBalancer<ServiceInstance> reactiveLoadBalancer;

    public SameIpLoadBalancerClient(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, ReactiveLoadBalancer<ServiceInstance> reactiveLoadBalancer) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.serviceId = serviceId;
        this.reactiveLoadBalancer = reactiveLoadBalancer;
        this.localIps = getAllLocalIps();
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map(list -> getInstanceResponse(list, request));
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {
        // 如果服务实例为空，则直接返回
        if (CollUtil.isEmpty(instances)) {
            log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId);
            return new EmptyResponse();
        }

        List<ServiceInstance> chooseInstances = filterBySameIp(instances);
        boolean isSameIpRouting = !CollUtil.isEmpty(chooseInstances) && chooseInstances != instances;

        // 打印负载均衡决策信息
        logLoadBalanceDecision(instances, chooseInstances, isSameIpRouting,request);

        if (CollUtil.isEmpty(chooseInstances)) {
            chooseInstances = instances;
        }

        return new DefaultResponse(NacosBalancer.getHostByRandomWeight3(chooseInstances));
    }

    private void logLoadBalanceDecision(List<ServiceInstance> allInstances,
                                        List<ServiceInstance> sameIpInstances,
                                        boolean isSameIpRouting, Request request) {
        if (log.isInfoEnabled()) {
            // 获取请求URL
            URI url = extractUrlFromRequest(request);
            String httpMethod = extractMethodFromRequest(request);

            log.info("[LoadBalancer][{}][{}] 总实例数: {}, 同IP实例数: {}, 选择策略: {}, URL: {}, Method: {}",
                    serviceId, isSameIpRouting ? "同IP路由" : "随机路由", allInstances.size(), sameIpInstances.size(), isSameIpRouting ? "优先同IP实例" : "随机选择实例", url, httpMethod);

            // 打印所有实例信息
            allInstances.forEach(instance ->
                    log.debug("[LoadBalancer][{}] 实例信息 - Host: {}, Port: {}", serviceId, instance.getHost(), instance.getPort()));

            // 如果有选中的实例，打印请求信息和选中的实例
            if (!sameIpInstances.isEmpty()) {
                ServiceInstance chosenInstance = sameIpInstances.get(0);
                String fullUrl = "http://" + chosenInstance.getHost() + ":" + chosenInstance.getPort() + url.getPath();
                log.info("[LoadBalancer][{}] 选中实例 - 请求URL: {}", serviceId,fullUrl);
            }
        }
    }

    private URI extractUrlFromRequest(Request request) {
        try {
            if (request.getContext() instanceof RequestDataContext) {
                RequestDataContext requestDataContext = (RequestDataContext)request.getContext();
                RequestData clientRequest = requestDataContext.getClientRequest();
                return clientRequest.getUrl();
            }
        } catch (Exception e) {
            log.debug("Failed to extract URL from req", e);
        }
        return null;
    }

    private String extractMethodFromRequest(Request request) {
        try {
            if (request.getContext() instanceof RequestDataContext) {
                RequestDataContext requestDataContext = (RequestDataContext)request.getContext();
                RequestData clientRequest = requestDataContext.getClientRequest();
                return clientRequest.getHttpMethod().toString() ;
            }
        } catch (Exception e) {
            log.debug("Failed to extract HTTP method from req", e);
        }
        return "Unknown Method";
    }


    private List<ServiceInstance> filterBySameIp(List<ServiceInstance> instances) {
        // 查找与消费者IP相同的实例
        List<ServiceInstance> sameIpInstances = instances.stream()
                .filter(instance -> localIps.contains(instance.getHost()))
                .collect(Collectors.toList());

        if (!sameIpInstances.isEmpty()) {
            return sameIpInstances;
        }

        return instances;
    }

    private List<String> getAllLocalIps() {
        List<String> ipList = new ArrayList<>();
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface iface = interfaces.nextElement();
                // 跳过回环接口和未启用的接口
                if (iface.isLoopback() || !iface.isUp()) {
                    continue;
                }

                Enumeration<InetAddress> addresses = iface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    // 只考虑IPv4地址
                    if (addr.getHostAddress().contains(":")) {
                        continue;
                    }
                    ipList.add(addr.getHostAddress());
                }
            }
        } catch (SocketException e) {
            log.warn("Could not determine local IP addresses, using empty list", e);
        }

        log.debug("Local IP addresses: {}", ipList);
        return Collections.unmodifiableList(ipList);
    }
}