package com.lingniu.ingest.protocol.gb32960.inbound;

import com.lingniu.ingest.core.dispatcher.Dispatcher;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960FrameDecoder;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder;
import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import java.io.File;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.concurrent.ThreadFactory;

/**
 * GB/T 32960 Netty Server 启动器。
 *
 * <p>EventLoop 只做解码与投递 {@link Dispatcher}，业务处理由 Disruptor 侧的虚拟线程承担。
 * 端口是否启动由 {@code lingniu.ingest.gb32960.enabled} 控制，生产 32960 线应确认该开关、
 * 端口、防火墙和上游平台账号同时匹配。
 *
 * <p>支持可选 TLS：{@link Gb32960Properties.Tls#isEnabled()} = true 时，pipeline 前置
 * {@code SslHandler}，加载服务端证书 + 私钥 + 受信 CA，默认要求客户端证书（双向 TLS）。
 */
public class Gb32960NettyServer implements InitializingBean, DisposableBean {

    private static final Logger log = LoggerFactory.getLogger(Gb32960NettyServer.class);

    private final int port;
    private final int workerThreads;
    private final int idleReadSeconds;
    private final Gb32960MessageDecoder messageDecoder;
    private final Dispatcher dispatcher;
    private final Gb32960AccessService accessService;
    private final Gb32960AckService ackService;
    private final Gb32960FrameDiagnostics diagnostics;
    private final Gb32960ConnectionDiagnostics connectionDiagnostics;
    private final Gb32960Properties.Tls tlsConfig;

    private EventLoopGroup boss;
    private EventLoopGroup workers;
    private Channel serverChannel;
    private SslContext sslContext;

    public Gb32960NettyServer(int port, int workerThreads, int idleReadSeconds,
                              Gb32960MessageDecoder messageDecoder,
                              Dispatcher dispatcher,
                              Gb32960AccessService accessService,
                              Gb32960AckService ackService,
                              Gb32960FrameDiagnostics diagnostics,
                              Gb32960ConnectionDiagnostics connectionDiagnostics,
                              Gb32960Properties.Tls tlsConfig) {
        this.port = port;
        this.workerThreads = workerThreads;
        this.idleReadSeconds = idleReadSeconds;
        this.messageDecoder = messageDecoder;
        this.dispatcher = dispatcher;
        this.accessService = accessService;
        this.ackService = ackService;
        this.diagnostics = diagnostics;
        this.connectionDiagnostics = connectionDiagnostics;
        this.tlsConfig = tlsConfig;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (tlsConfig != null && tlsConfig.isEnabled()) {
            this.sslContext = buildSslContext(tlsConfig);
            log.info("[gb32960] TLS enabled cert={} clientAuth={}",
                    tlsConfig.getCertPath(),
                    tlsConfig.isRequireClientAuth() ? "REQUIRED" : "OPTIONAL");
        }

        ThreadFactory bossTf = runnable -> new Thread(runnable, "gb32960-boss");
        ThreadFactory wkTf = runnable -> new Thread(runnable, "gb32960-worker");
        this.boss = new NioEventLoopGroup(1, bossTf);
        this.workers = new NioEventLoopGroup(workerThreads == 0
                ? Runtime.getRuntime().availableProcessors() * 2 : workerThreads, wkTf);

        ServerBootstrap b = new ServerBootstrap();
        b.group(boss, workers)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        // Pipeline 顺序不能调换：TLS 先解密，FrameDecoder 再做拆包，ChannelHandler 最后做业务应答和派发。
                        if (sslContext != null) {
                            ch.pipeline().addLast("ssl", sslContext.newHandler(ch.alloc()));
                        }
                        if (idleReadSeconds > 0) {
                            // 只监听读空闲：readerIdle=N，writerIdle=0，allIdle=0。
                            // 触发后由 Gb32960ChannelHandler#userEventTriggered 仅打告警日志，不断链。
                            ch.pipeline().addLast("idle",
                                    new IdleStateHandler(idleReadSeconds, 0, 0));
                        }
                        ch.pipeline()
                                .addLast("frame", new Gb32960FrameDecoder())
                                .addLast("dispatch",
                                        new Gb32960ChannelHandler(messageDecoder, dispatcher,
                                                accessService, ackService, diagnostics,
                                                connectionDiagnostics));
                    }
                });
        this.serverChannel = b.bind(port).sync().channel();
        log.info("[gb32960] Netty server listening on :{} (tls={}, vinWhitelist={}, platformAuth={}, idleReadSeconds={})",
                getBoundPort(),
                sslContext != null,
                accessService.isVinWhitelistEnforcing() ? accessService.vinWhitelistSize() : "DISABLED",
                accessService.isPlatformAuthEnforcing() ? accessService.platformPolicySize() : "DISABLED",
                idleReadSeconds > 0 ? idleReadSeconds : "DISABLED");
    }

    /** 实际绑定端口（支持 port=0 随机分配后查询）。 */
    public int getBoundPort() {
        if (serverChannel != null && serverChannel.localAddress() instanceof InetSocketAddress a) {
            return a.getPort();
        }
        return port;
    }

    private static SslContext buildSslContext(Gb32960Properties.Tls cfg) throws Exception {
        if (cfg.getCertPath() == null || cfg.getKeyPath() == null) {
            throw new IllegalStateException("TLS enabled but cert-path/key-path not configured");
        }
        SslContextBuilder builder = SslContextBuilder.forServer(
                openPem(cfg.getCertPath()),
                openPem(cfg.getKeyPath()));
        if (cfg.getTrustCertPath() != null && !cfg.getTrustCertPath().isBlank()) {
            try (InputStream trust = openPem(cfg.getTrustCertPath())) {
                builder.trustManager(trust);
            }
        }
        builder.clientAuth(cfg.isRequireClientAuth() ? ClientAuth.REQUIRE : ClientAuth.OPTIONAL);
        return builder.build();
    }

    /** 读取 PEM 文件，支持 file:// 绝对路径或相对路径。 */
    private static InputStream openPem(String path) throws Exception {
        String stripped = path.startsWith("file://") ? path.substring("file://".length()) : path;
        File f = new File(stripped);
        if (!f.isAbsolute()) {
            f = Paths.get(System.getProperty("user.dir"), stripped).toFile();
        }
        if (!f.exists()) {
            throw new IllegalStateException("TLS PEM not found: " + f.getAbsolutePath());
        }
        return Files.newInputStream(f.toPath());
    }

    @Override
    public void destroy() {
        try {
            if (serverChannel != null) serverChannel.close().sync();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (boss != null) boss.shutdownGracefully();
            if (workers != null) workers.shutdownGracefully();
            log.info("[gb32960] Netty server stopped");
        }
    }
}
