package com.utic.center.utic.traf.server.service;

import com.utic.center.common.annotation.ProcessingElapsed;
import com.utic.center.common.service.AbstractProcessService;
import com.utic.center.common.utils.LogUtils;
import com.utic.center.utic.traf.server.config.TraceConfig;
import com.utic.center.utic.traf.server.dao.mapper.dwdb.DwdbTrafficMapper;
import com.utic.center.utic.traf.server.dto.DbmsBatchJobResult;
import com.utic.center.utic.traf.server.dto.LinkDto;
import com.utic.center.utic.traf.server.dto.LinkTrafCenterDto;
import com.utic.center.utic.traf.server.repository.ApplicationRepository;
import com.utic.center.utic.traf.server.service.worker.WorkerUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class LinkTrafSaveDwdbService implements AbstractProcessService {

    private final TraceConfig trace;
    private final LinkService linkService;
    private final SqlSessionFactory sqlSessionFactory;

    private String histMonth;

    public LinkTrafSaveDwdbService(
            TraceConfig trace,
            LinkService linkService,
            @Qualifier("dwdbSqlSessionFactory") SqlSessionFactory sqlSessionFactory
    ) {
        this.trace = trace;
        this.linkService = linkService;
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @ProcessingElapsed(type="DWDB", name="교통정보 DWDB 저장", starting = false, endLine = true)
    @Override
    public boolean processing() {
        this.histMonth = ApplicationRepository.trafPrcsTime.getPrcsTime().substring(4, 6);

        insertTrafficCenterHist();

        return true;
    }

    private void batchInsertTrafficCenterHist(DbmsBatchJobResult dbmsJobResult, String jobTable) {
        final long start = System.currentTimeMillis();
        final String mapperName = "insertTrafficCenterHist";
        final int maxBatchSize = ApplicationRepository.DBMS_BATCH_SIZE;
        int total = 0, target = 0, jobCnt = 0;
        int[] linkCounts = new int[4];  // 0으로 초기화

        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
            DwdbTrafficMapper mapper = sqlSession.getMapper(DwdbTrafficMapper.class);

            for (String linkId : dbmsJobResult.getLinkIds()) {
                LinkDto link = this.linkService.getLinkMap().get(linkId);
                if (link == null) {
                    log.error("[ERR] batchInsertTrafficCenterHist: {} link not found.", linkId);
                    continue;
                }

                total++;
                if (!link.getTrafFsn().getState().isState() || link.getTrafFsn().getSpeed() == 0) {
                    // 소통정보 생성을 하지 못하였거나, 운영자 입력 및 속도 정보가 0인 것은 제외
                    continue;
                }

                target++;
                LinkTrafCenterDto trafDto = link.getTrafCenter();

                try {
                    mapper.insertTrafficCenterHist(histMonth, trafDto);
                    linkCounts[trafDto.getLinkLevel()-1]++;
                    jobCnt++;

                    if (jobCnt % maxBatchSize == 0) {
                        sqlSession.flushStatements();
                    }
                } catch (Exception e) {
                    log.error("[ERR] batchInsertTrafficCenterHist({}): jobCnt({}), Exception, [{}], {}.",
                            dbmsJobResult.getJobIndex(), jobCnt, trafDto, e.getMessage());
                    sqlSession.rollback(); // 즉시 롤백
                    break; // 작업 중단
                }
            }
            try {
                sqlSession.flushStatements();
                sqlSession.commit();
            }
            catch (Exception e) {
                log.error("[ERR] batchInsertTrafficCenterHist({}): commit Exception, [{}], {}.", dbmsJobResult.getJobIndex(), jobTable, e.getMessage());
                sqlSession.rollback();
            }
        } catch (Exception e) {
            log.error("[ERR] batchInsertTrafficCenterHist({}): Exception, [{}], {}.", dbmsJobResult.getJobIndex(), jobTable, e.getMessage());
        }

        dbmsJobResult.setTotal(total);
        dbmsJobResult.setTarget(target);
        dbmsJobResult.setLink1(linkCounts[0]);
        dbmsJobResult.setLink2(linkCounts[1]);
        dbmsJobResult.setLink3(linkCounts[2]);
        dbmsJobResult.setLink4(linkCounts[3]);
        dbmsJobResult.setEffects(jobCnt);
        dbmsJobResult.setElapsedTime(System.currentTimeMillis() - start);

        if (this.trace.isDebug()) {
            log.info("[INS] {} | {}", LogUtils.elapsedLog(mapperName + dbmsJobResult.getJobIndex(), jobCnt, dbmsJobResult.getElapsedTime()), Thread.currentThread().getName());
        }
    }

    private void insertTrafficCenterHist() {
        final long start = System.currentTimeMillis();
        final String jobTable = ApplicationRepository.TABLE_TRAFFIC_CENTER_HIST + this.histMonth + "@DWDB";

        final ConcurrentHashMap<Integer, DbmsBatchJobResult> mapData = new ConcurrentHashMap<>();
        final int threadPoolSize = WorkerUtils.allocateWorkerLinkJobs(mapData, ApplicationRepository.PRCS_THREAD_COUNT, this.linkService.getValidTrafLinkList());
        if (threadPoolSize < 0) {
            log.warn("[WAN] insertTrafficCenterHist: getValidTrafLinkList no data, job return: [{}].", jobTable);
            return;
        }

        log.info("[INF] {}: Links({}), PoolSize({})", LogUtils.elapsedLog("DWDB BATCH SAVE"), this.linkService.getValidTrafLinkList().size(), threadPoolSize);

        final ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
        mapData.forEach((key, dbmsJobResult) -> {
//            log.info("[INF] {}: Job({}), Link({})", LogUtils.elapsedLog("BatchThread"), dbmsJobResult.getJobIndex(), dbmsJobResult.getLinkIds().size());
            executorService.execute(() -> batchInsertTrafficCenterHist(dbmsJobResult, jobTable));
        });

        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(120, TimeUnit.SECONDS)) {
                log.warn("[WAN] insertTrafficCenterHist: Timeout while waiting for tasks to finish, [{}].", jobTable);
                List<Runnable> droppedTasks = executorService.shutdownNow();
                log.warn("[WAN] insertTrafficCenterHist: {} tasks were dropped during shutdownNow, [{}].", droppedTasks.size(), jobTable);
            }
        } catch (InterruptedException e) {
            log.error("[ERR] insertTrafficCenterHist: InterruptedException, [{}], {}.", jobTable, e.getMessage());
            List<Runnable> droppedTasks = executorService.shutdownNow();
            log.error("[ERR] insertTrafficCenterHist: Forced shutdown due to interruption, [{}] tasks dropped.", droppedTasks.size());
            Thread.currentThread().interrupt(); // 현재 스레드의 인터럽트 상태 복원
        }

        long elapsedTime = 0;
        int link1 = 0, link2 = 0, link3 = 0, link4 = 0;

        for (DbmsBatchJobResult result : mapData.values()) {
            elapsedTime = Math.max(elapsedTime, result.getElapsedTime());
            link1 += result.getLink1();
            link2 += result.getLink2();
            link3 += result.getLink3();
            link4 += result.getLink4();
        }

        final int resultCount = link1 + link2 + link3 + link4;

        log.info("[INF] {}", LogUtils.elapsedLog("레벨1 " + jobTable, link1, elapsedTime));
        log.info("[INF] {}", LogUtils.elapsedLog("레벨2 " + jobTable, link2, elapsedTime));
        log.info("[INF] {}", LogUtils.elapsedLog("레벨3 " + jobTable, link3, elapsedTime));
        log.info("[INF] {}", LogUtils.elapsedLog("레벨4 " + jobTable, link4, elapsedTime));
        log.info("[INF] {}", LogUtils.elapsedLog(jobTable, resultCount, System.currentTimeMillis() - start));
    }

}
