package com.njcn.gather.detection.util.socket.cilent;

import cn.hutool.core.util.ObjectUtil;
import com.njcn.gather.detection.util.socket.SocketManager;
import com.njcn.gather.detection.util.socket.communication.handler.ConnectionLifecycleHandler;
import com.njcn.gather.detection.util.socket.communication.handler.HeartbeatMessageStrategy;
import com.njcn.gather.detection.util.socket.communication.handler.SocketMessageHandler;
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
import com.njcn.gather.detection.util.socket.communication.model.ConnectionType;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * Generic Netty client entry.
 * Stage 4-A removes direct dependencies on detection handlers and services.
 * Message parsing, heartbeat framing and lifecycle side effects now come from
 * callbacks attached to {@link ConnectionContext}.
 *
 * @author wr
 * @author hongawen
 * @date 2026/04/07
 */
@Slf4j
@Component
public class NettyClient {

    public void connect(String ip, Integer port, ConnectionContext context, String msg) {
        if (ObjectUtil.isNull(context) || ObjectUtil.isNull(context.getConnectionType())) {
            log.warn("Skip socket connect because connection context is null");
            return;
        }
        SocketMessageHandler messageHandler = context.getMessageHandler();
        ConnectionLifecycleHandler lifecycleHandler = resolveLifecycleHandler(context);
        HeartbeatMessageStrategy heartbeatMessageStrategy = context.getHeartbeatStrategy();
        SimpleChannelInboundHandler<String> handler = createHandler(context, messageHandler, lifecycleHandler);
        executeSocketConnection(ip, port, context, msg, handler, lifecycleHandler, heartbeatMessageStrategy);
    }

    private SimpleChannelInboundHandler<String> createHandler(ConnectionContext context,
                                                              SocketMessageHandler messageHandler,
                                                              ConnectionLifecycleHandler lifecycleHandler) {
        if (context.getConnectionType() == ConnectionType.SOURCE) {
            return new NettySourceClientHandler(context, messageHandler, lifecycleHandler);
        }
        if (context.getConnectionType() == ConnectionType.DEVICE) {
            return new NettyDevClientHandler(context, messageHandler, lifecycleHandler);
        }
        return new NettyContrastClientHandler(context, messageHandler, lifecycleHandler);
    }

    private ConnectionLifecycleHandler resolveLifecycleHandler(ConnectionContext context) {
        ConnectionLifecycleHandler lifecycleHandler = context.getLifecycleHandler();
        return lifecycleHandler == null ? ConnectionLifecycleHandler.NO_OP : lifecycleHandler;
    }

    private void executeSocketConnection(String ip, Integer port, ConnectionContext context, String msg,
                                         SimpleChannelInboundHandler<String> handler,
                                         ConnectionLifecycleHandler lifecycleHandler,
                                         HeartbeatMessageStrategy heartbeatMessageStrategy) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                    .channel(NioSocketChannel.class)
                    .handler(createChannelInitializer(context, handler, lifecycleHandler, heartbeatMessageStrategy));
            ChannelFuture channelFuture = bootstrap.connect(ip, port).sync();
            handleConnectionResult(channelFuture, context, group, msg, lifecycleHandler);
        } catch (Exception e) {
            log.warn("Connect socket server error: type={}, sessionKey={}",
                    context.getConnectionType(), context.getSessionKey(), e);
            group.shutdownGracefully();
            lifecycleHandler.onConnectFailed(context, e);
        }
    }

    private ChannelInitializer<NioSocketChannel> createChannelInitializer(ConnectionContext context,
                                                                          SimpleChannelInboundHandler<String> handler,
                                                                          ConnectionLifecycleHandler lifecycleHandler,
                                                                          HeartbeatMessageStrategy heartbeatMessageStrategy) {
        return new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) {
                setupPipeline(ch.pipeline(), context, handler, lifecycleHandler, heartbeatMessageStrategy);
            }
        };
    }

    /**
     * Key refactor point: pipeline extension now comes from generic strategy
     * and lifecycle callbacks instead of fixed detection business classes.
     */
    private void setupPipeline(ChannelPipeline pipeline, ConnectionContext context,
                               SimpleChannelInboundHandler<String> handler,
                               ConnectionLifecycleHandler lifecycleHandler,
                               HeartbeatMessageStrategy heartbeatMessageStrategy) {
        pipeline.addLast(new LineBasedFrameDecoder(10240 * 2))
                .addLast(new StringDecoder(CharsetUtil.UTF_8))
                .addLast(new StringEncoder(CharsetUtil.UTF_8))
                .addLast(new HeartbeatHandler(context, heartbeatMessageStrategy, lifecycleHandler));
        if (context.getConnectionType().isEnableIdleMonitor()) {
            pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
        }
        pipeline.addLast(handler);
    }

    private void handleConnectionResult(ChannelFuture channelFuture, ConnectionContext context,
                                        NioEventLoopGroup group, String msg,
                                        ConnectionLifecycleHandler lifecycleHandler) {
        channelFuture.addListener((ChannelFutureListener) future -> {
            if (!future.isSuccess()) {
                log.warn("Connect socket server failed: type={}, sessionKey={}",
                        context.getConnectionType(), context.getSessionKey(), future.cause());
                group.shutdownGracefully();
                lifecycleHandler.onConnectFailed(context, future.cause());
                return;
            }
            log.info("Connect socket server success: type={}, sessionKey={}",
                    context.getConnectionType(), context.getSessionKey());
            manageSocketConnection(context, group);
            SocketManager.addUser(context.getSessionKey(), future.channel());
            SocketManager.sendMsg(context.getSessionKey(), msg);
        });
    }

    private void manageSocketConnection(ConnectionContext context, NioEventLoopGroup group) {
        String sessionKey = context.getSessionKey();
        NioEventLoopGroup existingGroup = SocketManager.getGroupByUserId(sessionKey);
        if (ObjectUtil.isNotNull(existingGroup)) {
            try {
                existingGroup.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        SocketManager.addGroup(sessionKey, group);
    }
}
