package com.cmvr.quic.gateway.server;

import com.cmvr.quic.gateway.config.GatewayConfig;
import com.cmvr.quic.gateway.core.ConnectionSession;
import com.cmvr.quic.gateway.core.NodeEventBroker;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
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.nio.NioDatagramChannel;
import io.netty.handler.codec.quic.Quic;
import io.netty.handler.codec.quic.QuicChannel;
import io.netty.handler.codec.quic.QuicServerCodecBuilder;
import io.netty.handler.codec.quic.QuicSslContext;
import io.netty.handler.codec.quic.QuicSslContextBuilder;
import io.netty.handler.codec.quic.QuicStreamChannel;
import io.netty.handler.codec.quic.QuicStreamType;
import io.netty.handler.ssl.ClientAuth;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

/** Netty 4.2 QUIC服务端，只负责cmvr-quic-edge/1自定义协议。 */
public final class QuicGatewayServer implements AutoCloseable {

    private static final Logger log = LoggerFactory.getLogger(QuicGatewayServer.class);
    private static final AttributeKey<ConnectionSession> SESSION_KEY =
            AttributeKey.valueOf("cmvr.quic.connection.session");

    private final GatewayConfig config;
    private final NodeEventBroker broker;
    private EventLoopGroup eventLoopGroup;
    private Channel datagramChannel;

    public QuicGatewayServer(GatewayConfig config, NodeEventBroker broker) {
        this.config = config;
        this.broker = broker;
    }

    public void start() throws InterruptedException {
        Quic.ensureAvailability();
        final QuicSslContext sslContext = buildSslContext();
        ChannelHandler codec = new QuicServerCodecBuilder()
                .sslContext(sslContext)
                .maxIdleTimeout(config.getIdleTimeoutMs(), TimeUnit.MILLISECONDS)
                .initialMaxData(16L * 1024L * 1024L)
                .initialMaxStreamDataBidirectionalRemote(
                        (long) config.getMaximumControlFrameBytes() + 4L)
                .initialMaxStreamDataBidirectionalLocal(
                        (long) config.getMaximumControlFrameBytes() + 4L)
                .initialMaxStreamsBidirectional(1)
                .initialMaxStreamsUnidirectional(0)
                .datagram(config.getDatagramQueueLength(), config.getDatagramQueueLength())
                .tokenHandler(new HmacQuicTokenHandler())
                .handler(new ChannelInitializer<QuicChannel>() {
                    @Override
                    protected void initChannel(QuicChannel channel) {
                        ConnectionSession session = new ConnectionSession(channel, config, broker);
                        channel.attr(SESSION_KEY).set(session);
                        channel.pipeline().addLast(new EdgeConnectionHandler(session));
                    }
                })
                .streamHandler(new ChannelInitializer<QuicStreamChannel>() {
                    @Override
                    protected void initChannel(QuicStreamChannel stream) {
                        ConnectionSession session = stream.parent().attr(SESSION_KEY).get();
                        if (session == null || stream.type() != QuicStreamType.BIDIRECTIONAL
                                || !session.attachControlStream(stream)) {
                            stream.close();
                            return;
                        }
                        stream.pipeline().addLast(new ControlStreamHandler(
                                session, config.getMaximumControlFrameBytes()));
                    }
                })
                .build();

        eventLoopGroup = new NioEventLoopGroup(config.getIoThreads());
        datagramChannel = new Bootstrap()
                .group(eventLoopGroup)
                .channel(NioDatagramChannel.class)
                .option(ChannelOption.SO_REUSEADDR, true)
                .handler(codec)
                .bind(new InetSocketAddress(config.getBindHost(), config.getQuicPort()))
                .sync()
                .channel();
        log.info("QUIC Gateway已启动，bind={}:{}，alpn={}，clientCertificateRequired={}",
                config.getBindHost(), config.getQuicPort(), config.getAlpn(),
                config.isRequireClientCertificate());
        if (config.getAllowedNodeIds().isEmpty()) {
            log.warn("QUIC Gateway未配置nodeId准入名单，当前允许任意nodeId注册；生产环境必须配置cmvr.quic.allowed-node-ids");
        }
    }

    private QuicSslContext buildSslContext() {
        QuicSslContextBuilder builder = QuicSslContextBuilder.forServer(
                        config.getPrivateKeyFile().toFile(), null,
                        config.getCertificateFile().toFile())
                .applicationProtocols(config.getAlpn());
        if (config.isRequireClientCertificate()) {
            builder.trustManager(config.getClientCaFile().toFile());
            builder.clientAuth(ClientAuth.REQUIRE);
        }
        return builder.build();
    }

    @Override
    public void close() {
        if (datagramChannel != null) {
            datagramChannel.close().awaitUninterruptibly(5, TimeUnit.SECONDS);
        }
        if (eventLoopGroup != null) {
            eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS).awaitUninterruptibly();
        }
    }
}
