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

import cn.hutool.core.util.StrUtil;
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.SocketMessageHandler;
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;

/**
 * Shared client handler skeleton for the retained Netty communication layer.
 * Stage 4-A centralizes common session registration, message delegation and
 * idle cleanup so concrete handlers stay transport-oriented.
 *
 * @author hongawen
 * @date 2026/04/07
 */
@Slf4j
public abstract class AbstractNettyClientHandler extends SimpleChannelInboundHandler<String> {

    private final String handlerName;

    protected final ConnectionContext connectionContext;

    private final SocketMessageHandler socketMessageHandler;

    private final ConnectionLifecycleHandler lifecycleHandler;

    protected AbstractNettyClientHandler(String handlerName, ConnectionContext connectionContext,
                                         SocketMessageHandler socketMessageHandler,
                                         ConnectionLifecycleHandler lifecycleHandler) {
        this.handlerName = handlerName;
        this.connectionContext = connectionContext;
        this.socketMessageHandler = socketMessageHandler;
        this.lifecycleHandler = lifecycleHandler == null ? ConnectionLifecycleHandler.NO_OP : lifecycleHandler;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        String sessionKey = resolveSessionKey();
        log.info("{} channel active: channelId={}, sessionKey={}", handlerName, ctx.channel().id(), sessionKey);
        if (StrUtil.isNotBlank(sessionKey)) {
            SocketManager.addUser(sessionKey, ctx.channel());
        } else {
            log.warn("{} channel active without sessionKey, skip registration", handlerName);
        }
        lifecycleHandler.onConnected(connectionContext);
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        String sessionKey = resolveSessionKey();
        log.warn("{} channel inactive: channelId={}, sessionKey={}", handlerName, ctx.channel().id(), sessionKey);
        if (StrUtil.isNotBlank(sessionKey)) {
            SocketManager.removeUser(sessionKey);
        }
        lifecycleHandler.onDisconnected(connectionContext);
        super.channelInactive(ctx);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        if (socketMessageHandler == null) {
            log.warn("{} receive message but handler is null: sessionKey={}, message={}",
                    handlerName, resolveSessionKey(), msg);
            return;
        }
        try {
            socketMessageHandler.handle(connectionContext, msg);
        } catch (Exception e) {
            lifecycleHandler.onMessageHandlingError(connectionContext, msg, e);
            throw e;
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
            log.warn("{} trigger reader idle timeout: sessionKey={}", handlerName, resolveSessionKey());
            lifecycleHandler.onIdleTimeout(connectionContext);
            ctx.close();
            return;
        }
        super.userEventTriggered(ctx, evt);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("{} catch transport exception: sessionKey={}, message={}",
                handlerName, resolveSessionKey(), cause.getMessage(), cause);
        lifecycleHandler.onException(connectionContext, cause);
        ctx.close();
    }

    protected String resolveSessionKey() {
        return connectionContext == null ? null : connectionContext.getSessionKey();
    }
}
