package cn.novalon.manage.gateway.discovery;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 服务发现服务
 * 
 * 文件定义：实现服务实例的发现、监控和管理
 * 涉及业务：服务实例查询、健康检查、服务状态监控
 * 
 * 核心功能：
 * 1. 服务实例查询
 * 2. 服务健康检查
 * 3. 服务状态监控
 * 4. 服务实例缓存
 * 
 * @author 张翔
 * @date 2026-03-26
 */
@Service
public class ServiceDiscoveryService {

    private static final Logger logger = LoggerFactory.getLogger(ServiceDiscoveryService.class);

    private final ReactiveDiscoveryClient reactiveDiscoveryClient;
    private final DiscoveryClient discoveryClient;

    private final Map<String, List<ServiceInstance>> serviceCache = new ConcurrentHashMap<>();
    private final Map<String, Long> lastUpdateTime = new ConcurrentHashMap<>();

    private static final long CACHE_TTL_MS = 30000;

    public ServiceDiscoveryService(
            ReactiveDiscoveryClient reactiveDiscoveryClient,
            DiscoveryClient discoveryClient) {
        this.reactiveDiscoveryClient = reactiveDiscoveryClient;
        this.discoveryClient = discoveryClient;

        initializeServiceCache();
    }

    private void initializeServiceCache() {
        logger.info("Initializing service cache");
        
        discoveryClient.getServices().forEach(serviceId -> {
            List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
            if (!instances.isEmpty()) {
                serviceCache.put(serviceId, instances);
                lastUpdateTime.put(serviceId, System.currentTimeMillis());
                logger.debug("Cached {} instances for service: {}", instances.size(), serviceId);
            }
        });

        logger.info("Service cache initialized with {} services", serviceCache.size());
    }

    public Flux<ServiceInstance> getInstances(String serviceId) {
        if (serviceId == null || serviceId.isEmpty()) {
            logger.warn("Service ID is null or empty");
            return Flux.empty();
        }

        if (isCacheValid(serviceId)) {
            List<ServiceInstance> cachedInstances = serviceCache.get(serviceId);
            if (cachedInstances != null && !cachedInstances.isEmpty()) {
                logger.debug("Returning {} cached instances for service: {}", 
                        cachedInstances.size(), serviceId);
                return Flux.fromIterable(cachedInstances);
            }
        }

        logger.debug("Fetching instances for service: {}", serviceId);
        
        return reactiveDiscoveryClient.getInstances(serviceId)
                .doOnNext(instance -> logger.debug("Found instance: {}:{} for service: {}", 
                        instance.getHost(), instance.getPort(), serviceId))
                .collectList()
                .doOnNext(instances -> {
                    serviceCache.put(serviceId, instances);
                    lastUpdateTime.put(serviceId, System.currentTimeMillis());
                    logger.info("Updated cache with {} instances for service: {}", 
                            instances.size(), serviceId);
                })
                .flatMapMany(Flux::fromIterable);
    }

    public Flux<String> getServices() {
        return reactiveDiscoveryClient.getServices()
                .doOnNext(serviceId -> logger.debug("Found service: {}", serviceId));
    }

    public Mono<ServiceInstance> getFirstInstance(String serviceId) {
        return getInstances(serviceId)
                .next()
                .doOnNext(instance -> logger.debug("Returning first instance for service: {}", serviceId));
    }

    public Mono<ServiceInstance> getInstanceByHost(String serviceId, String host) {
        if (host == null || host.isEmpty()) {
            logger.warn("Host is null or empty");
            return Mono.empty();
        }

        return getInstances(serviceId)
                .filter(instance -> host.equals(instance.getHost()))
                .next()
                .doOnNext(instance -> logger.debug("Found instance with host {} for service: {}", 
                        host, serviceId));
    }

    public Mono<ServiceInstance> getInstanceByPort(String serviceId, int port) {
        if (port <= 0) {
            logger.warn("Invalid port: {}", port);
            return Mono.empty();
        }

        return getInstances(serviceId)
                .filter(instance -> port == instance.getPort())
                .next()
                .doOnNext(instance -> logger.debug("Found instance with port {} for service: {}", 
                        port, serviceId));
    }

    public Mono<Map<String, List<ServiceInstance>>> getAllServicesWithInstances() {
        return getServices()
                .flatMap(serviceId -> 
                        getInstances(serviceId)
                                .collectList()
                                .map(instances -> Map.entry(serviceId, instances))
                )
                .collectMap(Map.Entry::getKey, Map.Entry::getValue);
    }

    public Mono<Integer> getInstanceCount(String serviceId) {
        return getInstances(serviceId)
                .count()
                .map(Long::intValue);
    }

    public Mono<Boolean> isServiceAvailable(String serviceId) {
        return getInstanceCount(serviceId)
                .map(count -> count > 0)
                .doOnNext(available -> logger.debug("Service {} availability: {}", 
                        serviceId, available));
    }

    public void refreshServiceCache(String serviceId) {
        if (serviceId == null || serviceId.isEmpty()) {
            logger.warn("Service ID is null or empty");
            return;
        }

        logger.info("Refreshing cache for service: {}", serviceId);
        
        reactiveDiscoveryClient.getInstances(serviceId)
                .collectList()
                .subscribe(
                        instances -> {
                            serviceCache.put(serviceId, instances);
                            lastUpdateTime.put(serviceId, System.currentTimeMillis());
                            logger.info("Refreshed cache with {} instances for service: {}", 
                                    instances.size(), serviceId);
                        },
                        error -> logger.error("Failed to refresh cache for service: {}", 
                                serviceId, error)
                );
    }

    public void refreshAllServices() {
        logger.info("Refreshing cache for all services");
        
        reactiveDiscoveryClient.getServices()
                .flatMap(serviceId -> 
                        reactiveDiscoveryClient.getInstances(serviceId)
                                .collectList()
                                .doOnNext(instances -> {
                                    serviceCache.put(serviceId, instances);
                                    lastUpdateTime.put(serviceId, System.currentTimeMillis());
                                })
                )
                .subscribe(
                        instances -> logger.debug("Refreshed {} instances", instances.size()),
                        error -> logger.error("Failed to refresh all services", error),
                        () -> logger.info("All services cache refreshed")
                );
    }

    public void clearServiceCache() {
        logger.info("Clearing service cache");
        serviceCache.clear();
        lastUpdateTime.clear();
        initializeServiceCache();
    }

    private boolean isCacheValid(String serviceId) {
        Long lastUpdate = lastUpdateTime.get(serviceId);
        if (lastUpdate == null) {
            return false;
        }

        long currentTime = System.currentTimeMillis();
        return (currentTime - lastUpdate) < CACHE_TTL_MS;
    }

    public int getCachedServiceCount() {
        return serviceCache.size();
    }

    public int getCachedInstanceCount(String serviceId) {
        List<ServiceInstance> instances = serviceCache.get(serviceId);
        return instances != null ? instances.size() : 0;
    }
}
