package com.its.dsrc.xnettcp.dsrc.handler;

import com.beanit.asn1dsrc.util.DsrcAsn1Utils;
import com.its.app.utils.NettyUtils;
import com.its.app.utils.SysUtils;
import com.its.dsrc.config.RunningConfig;
import com.its.dsrc.entity.TbRseCtlr;
import com.its.dsrc.global.AppRepository;
import com.its.dsrc.vo.NET;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
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.slf4j.MDC;

import java.util.concurrent.TimeUnit;

/**
 * 소켓 채널이 연결된 후 최소 데이터 송수신 까지의 타임아웃을 체크한다.
 */
@Slf4j
@RequiredArgsConstructor
public class DsrcAsn1ServerIdleStateConnectionHandler extends ChannelDuplexHandler {

    private final RunningConfig config;

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;

            if (e.state() == IdleState.READER_IDLE) {
                // 최초 통신 연결후 어떠한 데이터의 수신이 없는 경우 해당 채널 Close.
                //String tcpAddress = NettyUtils.getTcpAddress(ctx.channel());
                String ipAddress  = NettyUtils.getRemoteIpAddress(ctx.channel());
                TbRseCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
                if (obj != null) {
                    MDC.put("id", obj.getLogKey());
                }
                log.error("DsrcAsn1ServerIdleStateConnectionHandler.userEventTriggered, ----READER_IDLE: {},  will be closed.", ipAddress);
                ctx.channel().disconnect();
                ctx.channel().close();
                if (obj != null) {
                    MDC.remove(obj.getLogKey());
                    MDC.clear();
                }
            }
            else if (e.state() == IdleState.WRITER_IDLE) {
            }
        }
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // 클라이언트 접속시 이벤트 발생(1)
        // 활성화된 채널의 디비 등록여부에 따라 열결을 활성화 하거나 종료시킨다.
        //logger.warn("DsrcAsn1ServerIdleStateConnectionHandler.-----handlerAdded: {}", NettyUtils.getTcpAddress(ctx.channel()));
        super.handlerAdded(ctx);    // channelActive 이벤트 발생시킴
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // channelInactive 이벤트 다음에 발생(2)
        // 이곳 핸들러에서 종료되는 경우는 채널연결이 인증되지 않는 경우이므로 그냥 종료시키면 된다.
        //logger.warn("DsrcAsn1ServerIdleStateConnectionHandler.---handlerRemoved: {}", NettyUtils.getTcpAddress(ctx.channel()));
        super.handlerRemoved(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // handlerAdded 다음 이벤트 발생(2)
        // 클라이언트 접속 요청 처리, 활성화된 채널의 디비 등록여부에 따라 열결을 활성화 하거나 종료시킨다.
        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
        TbRseCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
        if (obj == null) {
            log.error("DsrcAsn1ServerIdleStateConnectionHandler.----channelActive: {}, Unknown ip address. will be closed.", ipAddress);
            DsrcAsn1ServerIdleStateConnectionHandler.disconnectChannel(ctx.channel());
        }
        else {
            MDC.put("id", obj.getLogKey());
            log.info("DsrcAsn1ServerIdleStateConnectionHandler.----channelActive: {}, ID: {}, {}, {}", ipAddress, obj.getID(), obj.getRSE_ID(), ctx.channel().toString());

            if (obj.getChannel() != null) {
                try {
                    log.error("DsrcAsn1ServerIdleStateConnectionHandler.----channelActive: Old Connection Active: {}, ID: {}, {}, {}", ipAddress, obj.getID(), obj.getRSE_ID(), obj.getChannel().toString());
                    obj.getChannel().disconnect();
                    obj.getChannel().close();
                }
                catch(Exception e) {
                }
            }

            /**
             * 1. 데이터 송수신 타임아웃 핸들러 등록
             * 2. 현재 핸들러를 삭제한다.
             */
            int allIdleTimeSec = 120;   // 2분
            DsrcAsn1ServerIdleStatePacketHandler dsrcAsn1ServerIdleStatePacketHandler = new DsrcAsn1ServerIdleStatePacketHandler(
                    0,
                    0,
                    allIdleTimeSec,
                    TimeUnit.SECONDS
            );
            ctx.channel().pipeline().addAfter("dsrcAsn1ServerConnectionIdleStateHandler","dsrcAsn1ServerIdleStateHandler", dsrcAsn1ServerIdleStatePacketHandler);    // packet idle handler add
            ctx.channel().pipeline().remove("dsrcAsn1ServerConnectionIdleStateHandler");     // login idle handler remove
            ctx.channel().pipeline().remove(this);                              // First packet recv/send timeout handler(dsrcAsn1ServerConnectionHandler)

            obj.setNetState(NET.LOGIN_REQ);
            obj.setConnectTm(SysUtils.getSysTimeStr());
            obj.setChannel(ctx.channel());
            obj.setHeaderOptions(DsrcAsn1Utils.getDefaultOptions());
            obj.setLastRecvTime();

            MDC.remove(obj.getLogKey());
            MDC.clear();
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // 클라이언트 연결 종료시 이벤트 발생(1)
        // 이곳 핸들러에서 종료되는 경우는 채널연결이 인증되지 않는 경우이므로 그냥 종료시키면 된다.
        // 채널이 이미 연결 종료된 상태에서의 이벤트 수신
        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
        TbRseCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
        if (obj != null) {
            MDC.put("id", obj.getLogKey());
        }
        log.error("DsrcAsn1ServerIdleStateConnectionHandler.--channelInactive: {}, {}", ipAddress, ctx.channel().toString());

        super.channelInactive(ctx); // handlerRemoved 이벤트 발생 시킴

        try {
            ctx.channel().close();
        }
        catch(Exception e) {
        }

        if (obj != null) {
            MDC.remove(obj.getLogKey());
            MDC.clear();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 이곳 핸들러에서 종료되는 경우는 채널연결이 인증되지 않는 경우이므로 그냥 종료시키면 된다.
        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
        TbRseCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
        if (obj != null) {
            MDC.put("id", obj.getLogKey());
        }
        log.error("DsrcAsn1ServerIdleStateConnectionHandler.--exceptionCaught: {}, {}, Exception: {}", ipAddress, ctx.channel().toString(), cause);
        DsrcAsn1ServerIdleStateConnectionHandler.disconnectChannel(ctx.channel());
        //super.exceptionCaught(ctx, cause);
        if (obj != null) {
            MDC.remove(obj.getLogKey());
            MDC.clear();
        }
    }

    public static void disconnectChannel(Channel channel) {
        // 로그인 하지 않은 또는 비정상 접속 네트워크 세션 종료(로그인 처리를 수행하지 않은 세션에 대한 종료)
        String ipAddress = NettyUtils.getRemoteIpAddress(channel);
        TbRseCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
        if (obj != null) {
            MDC.put("id", obj.getLogKey());
        }
        try {
            log.error("DsrcAsn1ServerIdleStateConnectionHandler.disconnectChannel: {}, {}", ipAddress, channel.toString());
            if (!channel.isActive()) {
                log.error("DsrcAsn1ServerIdleStateConnectionHandler.disconnectChannel: {}, channel already closed.", ipAddress);
            }

            channel.flush();
            ChannelFuture f = channel.disconnect().awaitUninterruptibly();  // channelInactive event fire
            if (!f.isDone() || !f.isSuccess()) {
                log.error("DsrcAsn1ServerIdleStateConnectionHandler.disconnectChannel: {}, isDone: {}, isSuccess: {}", ipAddress, f.isDone(), f.isSuccess());
            }
        }
        catch(Exception e) {
        }

        if (obj != null) {
            MDC.remove(obj.getLogKey());
            MDC.clear();
        }
    }
}
