package com.tsi.comm.server.config;

import com.tsi.comm.server.TsiCommServerApplication;
import com.tsi.comm.server.repository.TsiNodeManager;
import com.tsi.comm.server.repository.TsiSessionManager;
import com.tsi.comm.server.tcp.handler.MdcLoggingHandler;
import com.tsi.comm.server.vo.TsiNodeVo;
import com.tsi.common.utils.StringUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.logging.LogLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@Component
public class TraceConfig {

    private final TsiNodeManager nodeManager;

    private Set<Long> currentDumpNodeIds = new HashSet<>();
    private Set<String> currentTcpDumpIps = new HashSet<>();

    private boolean queueReport = false;
    private boolean sessionReport = false;
    private boolean nodeLogging = false;

    @PostConstruct
    void init() {
        loadTraceInfo();
    }

    private Properties getProperties() {
        String traceFileName = System.getProperty("user.dir") + File.separator + "conf" + File.separator + TsiCommServerApplication.APPLICATION_NAME + "-trace.cfg";
        Properties props = new Properties();
        try {
            FileInputStream in = new FileInputStream(traceFileName);
            props.load(in);
            in.close();
        }
        catch (FileNotFoundException ef) {
            // no logging
        }
        catch(Exception e) {
//            log.error("getProperties: Exception1: {}", e.getMessage());
        }
        return props;
    }

    private long getNodeId(String nodeId) {
        try {
            return Long.parseLong(nodeId.trim());
        }
        catch (NumberFormatException e) {
            return -1;
        }
    }

    /**
     * 설정 파일을 다시 로드하여, '변경된' 노드의 덤프 상태만 선택적으로 업데이트합니다.
     */
    public void loadTraceInfo() {
        try {
            Properties props = getProperties();

            // --- DUMP
            Set<Long> newDumpNodeIds = StringUtils.splitToStream(props.getProperty("DUMP", ""), ",")
                    .map(this::getNodeId)
                    .filter(id -> id > 0)
                    .collect(Collectors.toSet());

            // 비활성화할 노드 찾기 (이전 Set에서 새로운 Set에는 없는 노드를 제거)
            Set<Long> nodesToDisableDump = new HashSet<>(this.currentDumpNodeIds);
            nodesToDisableDump.removeAll(newDumpNodeIds);
            nodesToDisableDump.forEach(nodeId -> updateDumpStatus(nodeId, false));

            // 활성화할 노드 찾기 (새로운 Set에는 있지만, 이전 Set에는 없었던 노드) ==> 이전과 신규 모두 설정된것은 변경작업을 하지 않음
            Set<Long> nodesToEnableDump = new HashSet<>(newDumpNodeIds);
            nodesToEnableDump.removeAll(this.currentDumpNodeIds);
            nodesToEnableDump.forEach(nodeId -> updateDumpStatus(nodeId, true));

            // 다음 비교를 위해 현재 상태를 업데이트
            this.currentDumpNodeIds = newDumpNodeIds;


            // --- TCP-DUMP
            Set<String> newTcpDumpIps = StringUtils.splitToStream(props.getProperty("TCP-DUMP", ""), ",")
                    .collect(Collectors.toSet());

            Set<String> ipsToDisableTcpDump = new HashSet<>(this.currentTcpDumpIps);
            ipsToDisableTcpDump.removeAll(newTcpDumpIps);
            ipsToDisableTcpDump.forEach(ip -> updateTcpDumpStatus(ip, false));

            Set<String> ipsToEnableTcpDump = new HashSet<>(newTcpDumpIps);
            ipsToEnableTcpDump.removeAll(this.currentTcpDumpIps);
            ipsToEnableTcpDump.forEach(ip -> updateTcpDumpStatus(ip, true));

            this.currentTcpDumpIps = newTcpDumpIps;

            // --- 기타 설정 로드 ---
            this.sessionReport = props.getProperty("session-report", "false").trim().equalsIgnoreCase("true");
            this.queueReport = props.getProperty("queue-report", "false").trim().equalsIgnoreCase("true");
            this.nodeLogging = props.getProperty("node-logging", "false").trim().equalsIgnoreCase("true");

        } catch (Exception e) {
            log.error("loadTraceInfo: Exception: {}", e.getMessage());
        }
    }

    private void updateTcpDumpStatus(String ipAddr, boolean shouldTcpDump) {
        TsiNodeVo nodeVo = this.nodeManager.getIpAddr(ipAddr);
        if (nodeVo != null && nodeVo.isTcpDump() != shouldTcpDump) {
            log.info("TCP dump status changed for node {}: {} -> {}", nodeVo.getNodeId(), nodeVo.isTcpDump(), shouldTcpDump);
            updateTcpDumpForChannel(nodeVo, shouldTcpDump);
        }
    }

    private void updateDumpStatus(long nodeId, boolean shouldDump) {
        TsiNodeVo nodeVo = this.nodeManager.get(nodeId);
        if (nodeVo != null && nodeVo.isDump() != shouldDump) {
            log.info("Dump status changed for node {}: {} -> {}", nodeId, nodeVo.isDump(), shouldDump);
            nodeVo.setDump(shouldDump);
        }
    }

    /**
     * '특정 노드 하나'의 TCP 덤프 상태를 변경하고, 채널 파이프라인을 수정합니다.
     */
    private void updateTcpDumpForChannel(TsiNodeVo nodeVo, boolean addHandler) {

        Channel channel = nodeVo.getChannel();
        if (channel == null || !channel.isActive()) {
            return;
        }

        final String handlerName = TsiSessionManager.MDC_LOGGING_HANDLER_NAME;
        try {
            channel.eventLoop().execute(() -> {
                ChannelPipeline pipeline = channel.pipeline();
                boolean hasHandler = pipeline.get(handlerName) != null;
                if (addHandler && !hasHandler) {
                    log.info("Adding TCP dump handler to channel for center: {}, {}", nodeVo.getNodeId(), nodeVo.getIpAddr());
                    pipeline.addFirst(handlerName, new MdcLoggingHandler(LogLevel.INFO));
                }
                else if (!addHandler && hasHandler) {
                    log.info("Removing TCP dump handler from channel for center: {}, {}", nodeVo.getNodeId(), nodeVo.getIpAddr());
                    pipeline.remove(handlerName);
                }
            });
        }
        catch(Exception e) {
            log.error("updateTcpDumpForChannel Exception: {}", e.getMessage());
        }
    }

    public void loadTraceInfo2() {
        try {
            Properties props = getProperties();

//            this.nodeManager.initDump();

            // 1. DUMP 설정
            Set<Long> dumpNodeIds = StringUtils.splitToStream(props.getProperty("DUMP", ""), ",")
                    .map(this::getNodeId)
                    .filter(id -> id > 0)
                    .collect(Collectors.toSet());

            // 2. TCP-DUMP 설정
            Set<String> tcpDumpIps = StringUtils.splitToStream(props.getProperty("TCP-DUMP", ""), ",")
                    .collect(Collectors.toSet());

            // 3. 한 번의 순회로 모든 노드의 상태를 확인하고 업데이트
            for (TsiNodeVo nodeVo : this.nodeManager.getAllNodes()) {
                // 3-1. DUMP 상태 업데이트
                boolean shouldDump = dumpNodeIds.contains(nodeVo.getNodeId());
                if (nodeVo.isDump() != shouldDump) {
                    log.info("Dump status changed for node {}: {} -> {}", nodeVo.getNodeId(), nodeVo.isDump(), shouldDump);
                    nodeVo.setDump(shouldDump);
                }

                // 3-2. TCP-DUMP 상태 업데이트
                boolean shouldTcpDump = tcpDumpIps.contains(nodeVo.getIpAddr());
                if (nodeVo.isTcpDump() != shouldTcpDump) {
                    log.info("TCP dump status changed for node {}: {} -> {}", nodeVo.getNodeId(), nodeVo.isTcpDump(), shouldTcpDump);
                    nodeVo.setTcpDump(shouldTcpDump);
                    updateTcpDumpForChannel(nodeVo, shouldTcpDump);
                }
            }

            // 4. 기타 설정 로드
            this.sessionReport = props.getProperty("session-report",  "false").trim().equalsIgnoreCase("true");
            this.queueReport = props.getProperty("queue-report",  "false").trim().equalsIgnoreCase("true");
            this.nodeLogging = props.getProperty("node-logging",  "false").trim().equalsIgnoreCase("true");
        }
        catch(Exception e) {
            log.error("loadTraceInfo: Exception: {}", e.getMessage());
        }

    }

}
