package com.huangzj.comm.crosslink;

import com.huangzj.base.cmd.CrossRpcInvoker;
import com.huangzj.base.frame.LengthPrefixedProtobuf;
import com.huangzj.cmd.gen.CmdRpcEnvelope;
import com.huangzj.cmd.gen.CrossLinkHeartbeatCts;
import com.huangzj.cmd.gen.CrossLinkRegisterCts;
import com.huangzj.cmd.gen.CrossLinkRegisterStc;
import com.huangzj.cmd.gen.PbService;
import com.huangzj.cmd.gen.Service4_Method;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import jakarta.annotation.PreDestroy;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 区服 → 跨服：单条长连接、启动后注册、定时心跳；业务 {@link #roundTrip} 通过 {@link CmdRpcEnvelope#getLinkSeq()} 多路复用。
 *
 * <p>跨服回包进入 <strong>区服</strong> 进程，由 {@link CrossServerInboundHandler} 解析后 {@link #dispatchInbound}；不直接写给游戏客户端。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CrossTcpClient implements CrossRpcInvoker {

    private final CrossLinkProperties properties;
    private final EventLoopGroup group = new NioEventLoopGroup(1);

    private final ConcurrentHashMap<Integer, CompletableFuture<CmdRpcEnvelope>> pendingBySeq = new ConcurrentHashMap<>();
    private final AtomicInteger seqGen = new AtomicInteger(1);

    private final Object connectLock = new Object();

    private volatile Channel channel;
    private volatile boolean linkRegistered;

    /** 生成非 0 序号：0 在 proto3 里表示「未设置」，不能用作 link_seq 去匹配 pending。 */
    private static int nonZeroSeq(AtomicInteger gen) {
        int v = gen.incrementAndGet();
        if (v == 0) {
            v = gen.incrementAndGet();
        }
        return v;
    }

    private int nextSeq() {
        return nonZeroSeq(seqGen);
    }

    /**
     * 建链（幂等）。与注册共用同一把锁，避免「边连边注册」交错。 新 TCP 建立后必须重新走注册，故先把
     * linkRegistered 置 false。
     */
    private void ensureConnected() throws InterruptedException {
        synchronized (connectLock) {
            if (channel != null && channel.isActive()) {
                return;
            }
            // 旧连接已不可用或首次连接：后续必须重新 Service_4 Register
            linkRegistered = false;
            log.info("Connecting cross TCP (long link) {}:{}", properties.getHost(), properties.getPort());
            CrossServerInboundHandler inbound = new CrossServerInboundHandler(this);
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            // 与跨服约定一致：4 字节大端长度 + CmdRpcEnvelope
                            ch.pipeline().addLast(LengthPrefixedProtobuf.newTcpFrameDecoder());
                            ch.pipeline().addLast(inbound);
                        }
                    });
            ChannelFuture f = b.connect(properties.getHost(), properties.getPort()).sync();
            channel = f.channel();
            log.info("Cross TCP connected {}", channel.remoteAddress());
            // 对端或本端关闭时：清 channel、作废注册态，并把仍在等的 Future 全部失败掉，防止永久阻塞
            channel
                    .closeFuture()
                    .addListener(cf -> {
                        channel = null;
                        linkRegistered = false;
                        failAllPending(new IOException("cross TCP disconnected"));
                        log.warn("Cross TCP channel closed");
                    });
        }
    }

    /**
     * 双检锁：无锁快速路径（已注册）+ 与建链同锁的注册临界区，保证同一时刻只有一条注册在进行。 注册响应同样带
     * link_seq，由 {@link #dispatchInbound} 完成 Future；finally 再 remove 一次防止超时等异常泄漏 map 条目。
     */
    private void ensureRegistered() throws Exception {
        if (linkRegistered) {
            return;
        }
        synchronized (connectLock) {
            if (linkRegistered) {
                return;
            }
            if (channel == null || !channel.isActive()) {
                throw new IllegalStateException("cross channel not active");
            }
            CrossLinkRegisterCts cts =
                    CrossLinkRegisterCts.newBuilder()
                            .setServerId(properties.getServerId() == null ? "" : properties.getServerId())
                            .setToken(properties.getToken() == null ? "" : properties.getToken())
                            .build();
            int id = nextSeq();
            CompletableFuture<CmdRpcEnvelope> f = new CompletableFuture<>();
            pendingBySeq.put(id, f);
            try {
                CmdRpcEnvelope env = CmdRpcEnvelope.newBuilder()
                        .setServiceId(PbService.Service_4_VALUE)
                        .setMethodId(Service4_Method.Register_VALUE)
                        .setPayload(cts.toByteString())
                        .setLinkSeq(id)
                        .build();
                channel.writeAndFlush(LengthPrefixedProtobuf.encodeMessage(channel.alloc(), env));
                // 注册允许稍长等待：跨服进程若刚启动，首次解析/建表可能略慢
                CmdRpcEnvelope res = f.get(10, TimeUnit.SECONDS);
                CrossLinkRegisterStc st = CrossLinkRegisterStc.parseFrom(res.getPayload());
                if (!st.getOk()) {
                    linkRegistered = false;
                    throw new IllegalStateException("cross register rejected: " + st.getMessage());
                }
                linkRegistered = true;
                log.info("Cross link registered ok serverId={}", properties.getServerId());
            } finally {
                pendingBySeq.remove(id, f);
            }
        }
    }

    /** Spring 就绪后主动建链并完成注册，缩短首包 RTT（在区服客户端 TCP 绑定之后执行）。 */
    @Order(Ordered.LOWEST_PRECEDENCE)
    @EventListener(ApplicationReadyEvent.class)
    public void warmupCrossLink() {
        try {
            // 失败不阻止 Spring 启动：首条业务 roundTrip 或心跳会再次尝试建链+注册
            ensureConnected();
            ensureRegistered();
        } catch (Exception e) {
            log.warn("Cross link warmup failed (is CrossModule up?): {}", e.toString());
        }
    }

    /**
     * 发一帧等一帧；自动分配 {@link CmdRpcEnvelope.Builder#setLinkSeq(int)}，与跨服回包对齐。
     */
    @Override
    public CmdRpcEnvelope roundTrip(CmdRpcEnvelope request) throws Exception {
        // 业务调用前保证链路可用；link_seq 由本客户端统一分配，覆盖调用方传入值（避免冲突）
        ensureConnected();
        ensureRegistered();
        int id = nextSeq();
        CompletableFuture<CmdRpcEnvelope> f = new CompletableFuture<>();
        pendingBySeq.put(id, f);
        try {
            CmdRpcEnvelope out = request.toBuilder().setLinkSeq(id).build();
            Channel ch = channel;
            if (ch == null || !ch.isActive()) {
                throw new IOException("cross channel inactive");
            }
            ch.writeAndFlush(LengthPrefixedProtobuf.encodeMessage(ch.alloc(), out));
            // 回包在 IO 线程里 complete，当前线程阻塞等待；超时由调用方感知
            return f.get(5, TimeUnit.SECONDS);
        } finally {
            // 正常时 dispatchInbound 已按 seq remove；此处兜底防超时/异常导致 map 堆积
            pendingBySeq.remove(id, f);
        }
    }

    /**
     * 供调度器定时调用：与业务 roundTrip 共用 pending 表与 link_seq 规则。 失败时主动关连接，迫使后续走
     * ensureConnected 重建，避免半开连接一直占着注册态。
     */
    public void sendHeartbeat() {
        try {
            ensureConnected();
            ensureRegistered();
            int id = nextSeq();
            CompletableFuture<CmdRpcEnvelope> f = new CompletableFuture<>();
            pendingBySeq.put(id, f);
            try {
                CrossLinkHeartbeatCts cts =
                        CrossLinkHeartbeatCts.newBuilder().setClientTimeMs(System.currentTimeMillis()).build();
                CmdRpcEnvelope env = CmdRpcEnvelope.newBuilder()
                        .setServiceId(PbService.Service_4_VALUE)
                        .setMethodId(Service4_Method.Heartbeat_VALUE)
                        .setPayload(cts.toByteString())
                        .setLinkSeq(id)
                        .build();
                Channel ch = channel;
                if (ch == null || !ch.isActive()) {
                    return;
                }
                ch.writeAndFlush(LengthPrefixedProtobuf.encodeMessage(ch.alloc(), env));
                // 心跳要快失败：超时说明对端卡死或网络异常，交给 catch 关连接
                f.get(3, TimeUnit.SECONDS);
                if (log.isDebugEnabled()) {
                    log.debug("Cross heartbeat ok");
                }
            } finally {
                pendingBySeq.remove(id, f);
            }
        } catch (Exception e) {
            log.warn("Cross heartbeat failed: {}", e.toString());
            linkRegistered = false;
            Channel ch = channel;
            if (ch != null) {
                ch.close();
            }
        }
    }

    /** Netty IO 线程调用：按 link_seq 唤醒对应阻塞中的 roundTrip/注册/心跳。 */
    public void dispatchInbound(CmdRpcEnvelope env) {
        int seq = env.getLinkSeq();
        if (seq == 0) {
            // 区服↔跨服约定必须带 seq；0 无法配对，多半是异常包或旧版本
            log.warn("Cross inbound ignored: link_seq=0 service={} method={}", env.getServiceId(), env.getMethodId());
            return;
        }
        CompletableFuture<CmdRpcEnvelope> cf = pendingBySeq.remove(seq);
        if (cf != null) {
            cf.complete(env);
        } else {
            // 重复包、乱序或已超时被 finally 清理后迟到到达
            log.warn("Cross inbound no pending for link_seq={}", seq);
        }
    }

    /** 连接断开或 pipeline 异常：让所有等待方立即收到异常，避免业务线程永久卡在 get()。 */
    public void failAllPending(Throwable t) {
        pendingBySeq.values().forEach(f -> f.completeExceptionally(t));
        pendingBySeq.clear();
    }

    @PreDestroy
    public void stop() {
        Channel ch = channel;
        if (ch != null) {
            ch.close();
        }
        // 与 Netty 惯例一致：尽快结束线程组，不拉长停机时间
        group.shutdownGracefully(0, 1, TimeUnit.SECONDS).syncUninterruptibly();
    }
}
