package com.its.vds.xnettcp.vds.handler;

import com.its.app.AppUtils;
import com.its.app.utils.NettyUtils;
import com.its.vds.config.CommunicationConfig;
import com.its.vds.dto.TbVdsCtlrDto;
import com.its.vds.entity.TbVdsCtlrStts;
import com.its.vds.process.DbmsData;
import com.its.vds.process.DbmsDataProcess;
import com.its.vds.process.DbmsDataType;
import com.its.vds.service.AppRepositoryService;
import com.its.vds.xnettcp.vds.process.TcpServerSendData;
import com.its.vds.xnettcp.vds.protocol.enums.eVdsOpCode;
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;

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

    private final TbVdsCtlrDto vdsObj;
    private final DbmsDataProcess dbmsDataProcess;
    private final CommunicationConfig 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) {
                MDC.put("id", this.vdsObj.getLogKey());
                String ipAddress  = NettyUtils.getRemoteIpAddress(ctx.channel());
                log.error("VdsServerIdleStateHandler.userEventTriggered, ----READER_IDLE: {}, R: {}, W: {}, A: {} will be closed.",
                        ipAddress, this.config.getReaderIdleTimeSeconds(), this.config.getWriterIdleTimeSeconds(), this.config.getAllIdleTimeSeconds());
                ctx.channel().disconnect();
                ctx.channel().close();
                MDC.remove(this.vdsObj.getLogKey());
                MDC.clear();
            }
            else if (e.state() == IdleState.WRITER_IDLE) {
            }
        }
    }

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

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

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // handlerAdded 다음 이벤트 발생(2)
        // 클라이언트 접속 요청 처리, 활성화된 채널의 디비 등록여부에 따라 열결을 활성화 하거나 종료시킨다.
        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
        MDC.put("id", this.vdsObj.getLogKey());
        log.info("VdsServerIdleStateHandler.----channelActive: {}, ID: {}, {}, {}", ipAddress, this.vdsObj.getVdsCtlrNmbr(), this.vdsObj.getVdsCtlrId(), ctx.channel().toString());
        if (this.vdsObj.getChannel() != null) {
            try {
                log.error("VdsServerIdleStateHandler.----channelActive: Old Connection Active: {}, ID: {}, {}, {}", ipAddress, this.vdsObj.getVdsCtlrNmbr(), this.vdsObj.getVdsCtlrId(), this.vdsObj.getChannel().toString());
                this.vdsObj.getChannel().disconnect();
                this.vdsObj.getChannel().close();
            }
            catch(Exception e) {
            }
        }

        this.vdsObj.connected(ctx);
        TbVdsCtlrStts stts = this.vdsObj.getStts().clone();
        this.dbmsDataProcess.add(new DbmsData(DbmsDataType.DBMS_DATA_CTLR_STTS, false, stts)); // 제어기 접속 상태 업데이트
        this.vdsObj.addRequestData(new TcpServerSendData(eVdsOpCode.OP_VDS_DATA_VOLUME, null));    // 최초 접속시 제어기 상태정보 요청

        MDC.remove(this.vdsObj.getLogKey());
        MDC.clear();
    }

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

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

        try {
            VdsServerIdleStateHandler.disconnectChannel(this.vdsObj, ctx.channel());
            ctx.channel().close();
        }
        catch(Exception e) {
        }

        MDC.remove(this.vdsObj.getLogKey());
        MDC.clear();
    }

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

    public static void disconnectChannel(TbVdsCtlrDto AObj, Channel channel) {
        // 로그인 하지 않은 또는 비정상 접속 네트워크 세션 종료(로그인 처리를 수행하지 않은 세션에 대한 종료)
        String ipAddress = NettyUtils.getRemoteIpAddress(channel);
        if (AObj == null) {
            AppRepositoryService repoService = (AppRepositoryService) AppUtils.getBean(AppRepositoryService.class);
            AObj = repoService.getIpAddrMap(ipAddress);
        }
        if (AObj != null) {
            MDC.put("id", AObj.getLogKey());
        }
        try {
            log.error("VdsServerIdleStateHandler.disconnectChannel: {}, {}", ipAddress, channel.toString());

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

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