package com.tsi.comm.server.tcp.service;

import com.tsi.comm.server.config.TsiCvimServerConfig;
import com.tsi.comm.server.process.dbms.TsiCvimDbmsProcess;
import com.tsi.comm.server.process.packet.TsiChannelSession;
import com.tsi.comm.server.process.packet.TsiCvimPacketProcess;
import com.tsi.comm.server.protocol.TsiCpuDisconnected;
import com.tsi.comm.server.repository.TsiAlarmManager;
import com.tsi.comm.server.repository.TsiNodeManager;
import com.tsi.comm.server.repository.TsiSessionManager;
import com.tsi.comm.server.service.TsiQueueDistributorService;
import com.tsi.comm.server.vo.TsiAlarmConfigVo;
import com.tsi.comm.server.vo.TsiNodeVo;
import com.tsi.comm.server.vo.mariadb.AbstractDbmsVo;
import com.tsi.comm.server.vo.mariadb.AlarmOccrVo;
import com.tsi.comm.server.vo.mariadb.NodeStatusVo;
import com.tsi.comm.server.xnet.NettyUtils;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class ConnectionLifecycleService {

    private final TsiCvimServerConfig config;
    private final TsiNodeManager nodeManager;
    private final TsiSessionManager sessionManager;
    private final TsiAlarmManager alarmManager;
    private final TsiCvimPacketProcess packetProcess;
    private final TsiCvimDbmsProcess dbmsProcess;
    private final TsiQueueDistributorService queueDistributorService;

    /**
     * 채널 활성화 이벤트를 처리.
     */
    public void handleChannelActive(ChannelHandlerContext ctx) {
        int sessions = this.sessionManager.add();
        final String remoteIpAddr = NettyUtils.getRemoteIpAddress(ctx.channel());
        log.info("--channelActive: {}, {} Sessions.", remoteIpAddr, sessions);

        TsiNodeVo ipNodeVo = this.nodeManager.getIpAddr(remoteIpAddr);
        if (ipNodeVo == null) {
            this.alarmManager.reportUnknownIp(remoteIpAddr);
        } else {
            TsiChannelSession.sessionActive(ipNodeVo, remoteIpAddr);
        }
    }

    /**
     * 채널 비활성화 이벤트를 처리.
     */
    public void handleChannelInactive(ChannelHandlerContext ctx) {
        final String remoteIpAddr = NettyUtils.getRemoteIpAddress(ctx.channel());
        int sessions = this.sessionManager.remove();
        TsiNodeVo nodeVo = this.sessionManager.getNodeVo(ctx.channel());

        Integer pktQIdx = ctx.channel().attr(TsiSessionManager.PKT_Q_IDX_KEY).get();
        Integer dbmsQIdx = ctx.channel().attr(TsiSessionManager.DBMS_Q_IDX_KEY).get();
        Integer logQIdx = ctx.channel().attr(TsiSessionManager.LOG_Q_IDX_KEY).get();
        if (pktQIdx != null && dbmsQIdx != null && logQIdx != null) {
            this.queueDistributorService.releaseQueue(pktQIdx, dbmsQIdx, logQIdx);
        }

        if (nodeVo != null) {
            // DB 상태 업데이트 큐잉
            NodeStatusVo status = new NodeStatusVo(AbstractDbmsVo.DBMS_NODE_STATUS);
            status.setServerId(this.config.getServerId());
            status.setNodeId(nodeVo.getNodeId());
            status.setStatus(0);
            status.setIpAddr(remoteIpAddr);
            this.dbmsProcess.add(status, nodeVo.getDbmsQIdx());

            // Disconnected 패킷 큐잉
            TsiCpuDisconnected packet = new TsiCpuDisconnected(nodeVo.getNodeId(), ctx.channel());
            packet.setBuf(null);
            packet.setObj(nodeVo);
            if (pktQIdx != null) {
                this.packetProcess.add(packet, pktQIdx);    // 패킷인덱스큐로 데이터 전송
            }
            else {
                this.packetProcess.add(packet, nodeVo.getPktQIdx());    // 패킷인덱스큐로 데이터 전송
            }

            TsiChannelSession.sessionInactive(nodeVo, remoteIpAddr);

            // 리소스 정리
            this.sessionManager.removeChannel(ctx.channel());
            ctx.channel().attr(TsiSessionManager.TSI_NODE_ATTRIBUTE_KEY).set(null);

//            this.queueDistributorService.releaseQueue(nodeVo);  // 작업큐를 할당을 해제

            // --- 기존의 synchronized 블록 전체를 아래 코드로 대체 ---
            // TsiNodeVo 객체가 스스로 상태를 확인하고 안전하게 변경하도록 책임을 위임합니다.
            if (nodeVo.disconnectChannel(ctx.channel())) {
                // 현재 비활성화되는 채널이 nodeVo에 등록된 활성 채널과 동일한 경우 (정상적인 연결 종료)
                log.info("channelInactive: {}, {}, sessions: {}", nodeVo.getNodeId(), remoteIpAddr, sessions);
            } else {
                // 새로운 연결에 의해 강제로 종료됨(즉, 이전 연결된 채널이 종료되고 신규채널로 대체되는 상태)
                log.warn("channelInactive: {}, {}, sessions: {}, Duplicate Connected.", nodeVo.getNodeId(), remoteIpAddr, sessions);
            }
        } else {
            log.info("channelInactive: {}, sessions: {}, Unknown node.", remoteIpAddr, sessions);
        }
    }

    /**
     * 유휴 상태 이벤트를 처리.
     */
    public void handleIdleStateEvent(ChannelHandlerContext ctx, IdleStateEvent evt) {
        if (evt.state() == IdleState.READER_IDLE) {
            TsiNodeVo nodeVo = this.sessionManager.getNodeVo(ctx.channel());
            String remoteIpAddr = NettyUtils.getRemoteIpAddress(ctx.channel());
            if (nodeVo == null) {
                log.warn("userEventTriggered: READER_IDLE: {}", remoteIpAddr);
                // 통신 접속 후 수신 데이터가 없이 READ 타임아웃이 발생한 경우임
                if (this.alarmManager.checkAlarm(TsiAlarmConfigVo.COMM_02)) {
                    AlarmOccrVo alarm = new AlarmOccrVo(AbstractDbmsVo.DBMS_ALARM_OCCR_HS);
                    alarm.setAlarmCode(TsiAlarmConfigVo.COMM_02);
                    alarm.setAlarmTarget(remoteIpAddr);
                    alarm.setAlarmValue(remoteIpAddr);
                    this.dbmsProcess.add(alarm, 0);
                }
            } else {
                log.warn("userEventTriggered: READER_IDLE: {}, {}", remoteIpAddr, nodeVo.getNodeId());
                // 통신 접속 후 데이터를 한번이라도 수신한 경우에는 스케쥴러에서 처리한다.
                if (this.alarmManager.checkAlarm(TsiAlarmConfigVo.COMM_02)) {
                    AlarmOccrVo alarm = new AlarmOccrVo(AbstractDbmsVo.DBMS_ALARM_OCCR_HS);
                    alarm.setAlarmCode(TsiAlarmConfigVo.COMM_02);
                    alarm.setAlarmTarget(nodeVo.getKey());
                    alarm.setAlarmValue(remoteIpAddr);
                    this.dbmsProcess.add(alarm, nodeVo.getDbmsQIdx());
                }
                TsiChannelSession.sessionTimeout(nodeVo, remoteIpAddr);
            }
            ctx.channel().close();
        } else if (evt.state() == IdleState.WRITER_IDLE) {
            log.error("userEventTriggered: WRITER_IDLE: {}", NettyUtils.getAddress(ctx.channel()));
        } else if (evt.state() == IdleState.ALL_IDLE) {
            log.error("userEventTriggered: ALL_IDLE: {}", NettyUtils.getAddress(ctx.channel()));
        }
    }

    public void handleExceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        TsiNodeVo nodeVo = this.sessionManager.getNodeVo(ctx.channel());
        if (nodeVo != null) {
            log.error("exceptionCaught: {}, {}", nodeVo.getNodeId(), ctx.channel());
        }
        log.error("exceptionCaught: {}", NettyUtils.getAddress(ctx.channel()));
    }
}
