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

import com.tsi.app.common.xnet.NettyUtils;
import com.tsi.comm.server.config.TsiCvimServerConfig;
import com.tsi.comm.server.mybatis.vo.AbstractDbmsVo;
import com.tsi.comm.server.mybatis.vo.AlarmOccrVo;
import com.tsi.comm.server.mybatis.vo.NodeStatusVo;
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.AbstractTsiPacket;
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.vo.TsiAlarmConfigVo;
import com.tsi.comm.server.vo.TsiNodeVo;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@RequiredArgsConstructor
@Component
@ChannelHandler.Sharable
public class CvimServerInboundMessageHandler extends ChannelInboundHandlerAdapter {

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

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //log.error("Handler: channelRead: {}", NettyUtils.getRemoteIpAddress(ctx.channel()));
        if (msg instanceof AbstractTsiPacket) {
            //TsiCpuPacket packet = (TsiCpuPacket)msg;
            //packet.setPop(System.nanoTime());
        }
        else {
            log.error("Handler: channelRead: Unknown Instance Type: {}", msg.getClass().getSimpleName());
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // session count increment
        int sessions = this.sessionManager.add();
        // IP Address 로 제어기를 판달할 수 있으면 여기서 제어기 등록 여부를 판단해서
        // 접속 여부를 처리할 수 있다.
        final String remoteIpAddr = NettyUtils.getRemoteIpAddress(ctx.channel());
        log.info("--channelActive: {}, {} Sessions.", remoteIpAddr, sessions);
        TsiNodeVo ipNodeVo = this.nodeManager.getIpAddr(remoteIpAddr);
        if (ipNodeVo != null) {
            TsiChannelSession.sessionActive(ipNodeVo, remoteIpAddr);
        }
        ctx.fireChannelActive();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        final String remoteIpAddr = NettyUtils.getRemoteIpAddress(ctx.channel());
        int sessions = this.sessionManager.remove();
        TsiNodeVo nodeVo = this.sessionManager.getNodeVo(ctx.channel());
        if (nodeVo != null) {
            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, (int)Thread.currentThread().getId());

            if (this.sessionManager.isServerRun()) {
                TsiCpuDisconnected packet = new TsiCpuDisconnected(nodeVo.getNodeId(), ctx.channel());
                packet.setBuf(null);
                packet.setObj(nodeVo);
                this.packetProcess.add(packet, nodeVo.getIdx());
            }
            log.info("channelInactive: Node {}, Channel close: {}", nodeVo.getNodeId(), remoteIpAddr);
            TsiChannelSession.sessionInactive(nodeVo, remoteIpAddr);

            nodeVo.setConnect(null);
            this.sessionManager.removeChannel(ctx.channel());
            ctx.channel().attr(TsiSessionManager.TSI_NODE_ATTRIBUTE_KEY).set(null);
        }
        else {
            log.info("channelInactive: {}, {} Sessions.", remoteIpAddr, sessions);
        }

        ctx.fireChannelInactive();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object e) throws Exception {
        if (e instanceof IdleStateEvent) {
            IdleStateEvent evt = (IdleStateEvent) e;
            TsiNodeVo nodeVo = this.sessionManager.getNodeVo(ctx.channel());
//            if (nodeVo != null) {
//                log.error("{}.userEventTriggered: {}, {}, {}", this.getClass().getSimpleName(), nodeVo.getNodeId(), ctx.channel(), evt.state().toString());
//            }

            // 연결이 완료된 후 송수신 데이터가 일정시간 동안 없을 경우 이곳에서 처리
            if (evt.state() == IdleState.READER_IDLE) {
                if (nodeVo == null) {
                    // 통신 접속 후 수신 데이터가 없이 READ 타임아웃이 발생한 경우임
                    if (this.alarmManager.checkAlarm(TsiAlarmConfigVo.COMM_02)) {
                        AlarmOccrVo alarm = new AlarmOccrVo(AbstractDbmsVo.DBMS_ALARM_OCCR_HS);
                        alarm.setAlarmCode(TsiAlarmConfigVo.COMM_02);
                        alarm.setAlarmTarget(NettyUtils.getRemoteIpAddress(ctx.channel()));
                        alarm.setAlarmValue(NettyUtils.getRemoteIpAddress(ctx.channel()));
                        this.dbmsProcess.add(alarm, (int) Thread.currentThread().getId());
                        log.warn("{}.userEventTriggered: Recv Timeout: {}", this.getClass().getSimpleName(), NettyUtils.getRemoteIpAddress(ctx.channel()));
                    }
                    ctx.channel().disconnect();
                    ctx.channel().close();
                }
                else {
                    // 통신 접속 후 데이터를 한번이라도 수신한 경우에는 스케쥴러에서 처리한다.
                }
            } else if (evt.state() == IdleState.WRITER_IDLE) {
                log.error("{}.userEventTriggered: WRITER_IDLE: {}", this.getClass().getSimpleName(), NettyUtils.getAddress(ctx.channel()));
            } else if (evt.state() == IdleState.ALL_IDLE) {
                log.error("{}.userEventTriggered: ALL_IDLE: {}", this.getClass().getSimpleName(), NettyUtils.getAddress(ctx.channel()));
            }
        }
        ctx.fireUserEventTriggered(e);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        TsiNodeVo nodeVo = this.sessionManager.getNodeVo(ctx.channel());
        if (nodeVo != null) {
            log.error("{}.exceptionCaught: {}, {}", this.getClass().getSimpleName(), nodeVo.getNodeId(), ctx.channel());
        }
        log.error("{}.exceptionCaught: {}", this.getClass().getSimpleName(), NettyUtils.getAddress(ctx.channel()));
        ctx.fireExceptionCaught(cause);
        ctx.channel().close();
    }
}
