package com.cmvr.edge.client.quic;

import com.cmvr.device.domain.DeDeviceTerminalConfig;
import com.cmvr.device.service.IDeDeviceTerminalConfigService;
import com.cmvr.edge.client.manage.GrpcServiceManager;
import com.cmvr.quic.contract.QuicEventBus;
import com.cmvr.quic.gateway.v1.NodeEvent;
import com.cmvr.quic.gateway.v1.NodeEventType;
import com.cmvr.quic.gateway.v1.NodeSnapshot;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/** 直接订阅进程内 QUIC 节点事件，并同步已绑定终端的运行状态和 gRPC 地址。 */
@Service
@RequiredArgsConstructor
public class QuicGatewayNodeSyncService {

    private static final Logger log = LoggerFactory.getLogger(QuicGatewayNodeSyncService.class);
    private static final long HEARTBEAT_PERSIST_INTERVAL_MS = 15000L;

    private final IDeDeviceTerminalConfigService terminalConfigService;
    private final GrpcServiceManager grpcServiceManager;
    private final QuicEventBus quicEventBus;

    @Value("${cmvr.quic.enabled:false}")
    private boolean enabled;

    /** observed-source 使用 QUIC 来源 IP；advertised 使用节点主动上报的 gRPC host。 */
    @Value("${cmvr.quic.grpc-host-source:observed-source}")
    private String grpcHostSource;

    private final AtomicBoolean stopping = new AtomicBoolean(false);
    private final AtomicBoolean grpcHostSourceWarningLogged = new AtomicBoolean(false);
    private final Set<String> unknownNodes = ConcurrentHashMap.newKeySet();
    private final Set<String> grpcAddressMismatchNodes = ConcurrentHashMap.newKeySet();
    private final ConcurrentHashMap<String, Long> lastPersistedAt = new ConcurrentHashMap<String, Long>();
    private final ConcurrentHashMap<String, String> synchronizedTerminals =
            new ConcurrentHashMap<String, String>();
    private ExecutorService eventExecutor;
    private QuicEventBus.Subscription subscription;

    @PostConstruct
    public void start() {
        if (!enabled) {
            log.info("QUIC 节点状态同步未启用");
            return;
        }
        eventExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable, "quic-node-sync");
                thread.setDaemon(true);
                return thread;
            }
        });
        subscription = quicEventBus.watchNodes(true, this::enqueueEvent);
        log.info("已通过进程内事件总线订阅 QUIC 节点事件");
    }

    private void enqueueEvent(final NodeEvent event) {
        ExecutorService executor = eventExecutor;
        if (stopping.get() || executor == null) {
            return;
        }
        try {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    handleEventSafely(event);
                }
            });
        } catch (RejectedExecutionException ignored) {
            if (!stopping.get()) {
                log.warn("QUIC 节点事件线程已停止，忽略事件，nodeId={}", event.getNode().getNodeId());
            }
        }
    }

    private void handleEventSafely(NodeEvent event) {
        try {
            syncNode(event);
        } catch (RuntimeException error) {
            log.error("同步 QUIC 终端状态失败，nodeId={}，eventType={}",
                    event.getNode().getNodeId(), event.getType(), error);
        }
    }

    private void syncNode(NodeEvent event) {
        NodeSnapshot node = event.getNode();
        if (node.getNodeId().isEmpty()) {
            return;
        }
        DeDeviceTerminalConfig terminal = terminalConfigService.selectByQuicNodeId(node.getNodeId());
        if (terminal == null) {
            if (unknownNodes.add(node.getNodeId())) {
                log.warn("QUIC 节点尚未绑定平台终端，忽略运行状态同步，nodeId={}，remoteIp={}",
                        node.getNodeId(), node.getObservedSourceIp());
            }
            return;
        }
        unknownNodes.remove(node.getNodeId());

        boolean offline = event.getType() == NodeEventType.NODE_EVENT_TYPE_OFFLINE || !node.getOnline();
        if (offline) {
            synchronizedTerminals.remove(node.getNodeId());
        } else {
            synchronizedTerminals.put(node.getNodeId(), terminal.getId());
        }
        String grpcHost = offline ? null : resolveGrpcHost(node);
        Long grpcPort = offline || node.getGrpcEndpoint().getPort() == 0
                ? null : Long.valueOf(node.getGrpcEndpoint().getPort());
        boolean endpointChanged = !offline && grpcHost != null && grpcPort != null
                && (!grpcHost.equals(terminal.getHost()) || !grpcPort.equals(terminal.getPort()));
        long now = System.currentTimeMillis();
        Long previousPersisted = lastPersistedAt.get(node.getNodeId());
        boolean immediate = event.getType() == NodeEventType.NODE_EVENT_TYPE_REGISTERED
                || event.getType() == NodeEventType.NODE_EVENT_TYPE_OFFLINE || endpointChanged;
        if (!immediate && previousPersisted != null
                && now - previousPersisted.longValue() < HEARTBEAT_PERSIST_INTERVAL_MS) {
            return;
        }

        terminalConfigService.updateQuicRuntimeState(terminal.getId(), grpcHost, grpcPort,
                node.getObservedSourceIp(), node.getSessionId(), node.getBootId(),
                node.getSoftwareVersion(), offline ? "0" : "1",
                new Date(event.getEventTimeUnixMs() == 0 ? now : event.getEventTimeUnixMs()));
        lastPersistedAt.put(node.getNodeId(), now);
        if (offline) {
            lastPersistedAt.remove(node.getNodeId());
        }
        if (endpointChanged || offline) {
            grpcServiceManager.invalidateTerminal(terminal.getId());
        }
        log.info("QUIC 终端状态已同步，terminalId={}，nodeId={}，online={}，grpc={}:{}",
                terminal.getId(), node.getNodeId(), !offline,
                grpcHost == null ? terminal.getHost() : grpcHost,
                grpcPort == null ? terminal.getPort() : grpcPort);
    }

    private String resolveGrpcHost(NodeSnapshot node) {
        String advertisedHost = emptyToNull(node.getGrpcEndpoint().getHost());
        if ("advertised".equalsIgnoreCase(grpcHostSource)) {
            return advertisedHost;
        }
        if (!"observed-source".equalsIgnoreCase(grpcHostSource)
                && grpcHostSourceWarningLogged.compareAndSet(false, true)) {
            log.warn("未知的 QUIC gRPC 地址来源配置，按 observed-source 处理，value={}", grpcHostSource);
        }
        String observedHost = emptyToNull(node.getObservedSourceIp());
        if (observedHost == null) {
            log.warn("QUIC 节点缺少来源 IP，回退到节点上报地址，nodeId={}，advertisedHost={}",
                    node.getNodeId(), advertisedHost);
            return advertisedHost;
        }
        if (advertisedHost != null && !observedHost.equals(advertisedHost)
                && grpcAddressMismatchNodes.add(node.getNodeId())) {
            log.info("QUIC 节点上报的 gRPC 地址与来源 IP 不同，使用来源 IP，nodeId={}，"
                            + "observedHost={}，advertisedHost={}",
                    node.getNodeId(), observedHost, advertisedHost);
        } else if (observedHost.equals(advertisedHost)) {
            grpcAddressMismatchNodes.remove(node.getNodeId());
        }
        return observedHost;
    }

    private void markSynchronizedTerminalsOffline() {
        for (Map.Entry<String, String> entry : synchronizedTerminals.entrySet()) {
            try {
                terminalConfigService.updateQuicRuntimeState(entry.getValue(), null, null,
                        null, null, null, null, "0", new Date());
                grpcServiceManager.invalidateTerminal(entry.getValue());
            } catch (RuntimeException error) {
                log.error("标记 QUIC 终端离线失败，nodeId={}，terminalId={}",
                        entry.getKey(), entry.getValue(), error);
            }
        }
        synchronizedTerminals.clear();
        lastPersistedAt.clear();
    }

    private static String emptyToNull(String value) {
        return value == null || value.trim().isEmpty() ? null : value.trim();
    }

    @PreDestroy
    public void stop() {
        if (!stopping.compareAndSet(false, true)) {
            return;
        }
        QuicEventBus.Subscription currentSubscription = subscription;
        if (currentSubscription != null) {
            currentSubscription.close();
        }
        ExecutorService executor = eventExecutor;
        if (executor != null) {
            executor.shutdown();
            try {
                if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException interrupted) {
                executor.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
        markSynchronizedTerminalsOffline();
    }
}
