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.dao.repository.dwdb.DwdbParamRepository;
import com.utic.center.utic.traf.server.dao.repository.utic.LinkRepository;
import com.utic.center.utic.traf.server.dto.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Getter
@RequiredArgsConstructor
@Service
public class LinkService implements AbstractProcessService {

    private final DwdbParamRepository dwdbParamRepo;
    private final LinkRepository linkRepo;

    private final ConcurrentHashMap<String, LinkDto> linkMap = new ConcurrentHashMap<>();

    private volatile List<String> validTrafLinkList = Collections.emptyList();
    private final LinkTrafFusionInfo fusionInfo = new LinkTrafFusionInfo();

    private List<String> linkLists = null;
    private List<String> link1Lists = null;

    private int link1Count = 0;
    private int link2Count = 0;
    private int link3Count = 0;
    private int link4Count = 0;

    public void makeValidTrafLink() {
        this.validTrafLinkList = this.linkMap.values().parallelStream()
                .filter(dto -> dto.getTrafFsn().getState().isState())
                .filter(dto -> dto.getTrafFsn().getSpeed() > 0)
                .map(LinkDto::getLinkId)
                .collect(Collectors.toList());
    }

    @ProcessingElapsed(type="LINK", name="링크 기본정보 로딩", starting = false)
    @Override
    public boolean processing() {
        if (!loadLinkCount()) {          // 링크 레벨별 갯수 조회
            return false;
        }
        loadLink();               // 링크 레벨별 정보 조회
        loadLogicalLink();        // 로지컬링크 정보 조회
        loadPrcsPtrnMissing();    // 패턴결측 링크 정보 조회
        return false;
    }

    public LinkDto getLink(String linkId) {
        return this.linkMap.get(linkId);
    }

    private boolean loadLinkCount() {
        LinkCountDto linkCount = this.linkRepo.findLinkCount();
        if (linkCount == null) {
            return false;
        }
        if (linkCount.getLevel1() < 1 || linkCount.getLevel2() < 1 || linkCount.getLevel3() < 1 || linkCount.getLevel4() < 1) {
            log.error("[ERR] {}: Link Level count error. LV1({}), LV2({}), LV3({}), LV4({}).",
                    LogUtils.elapsedLog("LinkLevelCount"),
                    linkCount.getLevel1(), linkCount.getLevel2(), linkCount.getLevel3(), linkCount.getLevel4());
            return false;
        }

        this.link1Count = linkCount.getLevel1();
        this.link2Count = linkCount.getLevel2();
        this.link3Count = linkCount.getLevel3();
        this.link4Count = linkCount.getLevel4();

        int totalLinkCount = this.link1Count + this.link2Count + this.link3Count + this.link4Count;
        this.link1Lists = new ArrayList<>(this.link1Count);
        this.linkLists = new ArrayList<>(totalLinkCount);
        return true;
    }

    private int loadLinkLevel1() {
        List<LinkDto> links = this.linkRepo.findLinkAll("1");
        if (links == null || links.isEmpty()) {
            return -1;
        }
        for (LinkDto link : links) {
            link.init();

            this.link1Lists.add(link.getLinkId());
            this.linkLists.add(link.getLinkId());
            this.linkMap.put(link.getLinkId(), link);
        }
        return links.size();
    }
    private int loadLinkGroup(String linkLevel) {
        List<LinkDto> links = this.linkRepo.findLinkAll(linkLevel);
        if (links == null || links.isEmpty()) {
            return -1;
        }
        for (LinkDto link : links) {
            link.init();

            this.linkLists.add(link.getLinkId());
            this.linkMap.put(link.getLinkId(), link);
        }
        return links.size();
    }
    private boolean loadLink() {
        int link1 = loadLinkLevel1();
        if (link1 < 0) {
            return false;
        }
        int link2 = loadLinkGroup("2");
        if (link2 < 0) {
            return false;
        }
        int link3 = loadLinkGroup("3");
        if (link3 < 0) {
            return false;
        }
        int link4 = loadLinkGroup("4");
        if (link4 < 0) {
            return false;
        }

        int linkCnt = link1 + link2 + link3 + link4;
        log.info("[INF] {}: LV1({}), LV2({}), LV3({}), LV4({}), Total: {} EA.",
                LogUtils.elapsedLog("Link Memory Allocation"),
                link1, link2, link3, link4, linkCnt);
        return true;
    }

    private boolean loadLinkX() {
        List<LinkDto> links = this.linkRepo.findLinkAll("ALL");
        if (links == null || links.isEmpty()) {
            return false;
        }

        int link1, link2, link3, link4;
        int linkCnt = 0;
        link1 = link2 = link3 = link4 = 0;
        for (LinkDto link : links) {
            link.init();

            if (link.getLinkLevel() == 1) {
                this.link1Lists.add(link.getLinkId());
                link1++;
            } else if (link.getLinkLevel() == 2) {
                link2++;
            } else if (link.getLinkLevel() == 3) {
                link3++;
            } else if (link.getLinkLevel() == 4) {
                link4++;
            }
            linkCnt++;
            this.linkLists.add(link.getLinkId());
            this.linkMap.put(link.getLinkId(), link);

//            LinkDto tmpLink = this.linkMap.get(link.getLinkId());
//            if (tmpLink == null) {
//                // 신규링크(링크정보 업데이트됨==>매년 1회)
//                this.linkMap.put(link.getLinkId(), link);
//            }
//            else {
//                // 이미 링크가 존재하는 경우
//            }
        }
        log.info("[INF] {}: LV1({}), LV2({}), LV3({}), LV4({}), Total: {} EA.",
                LogUtils.elapsedLog("Link Memory Allocation"),
                link1, link2, link3, link4, linkCnt);
        return true;
    }

    private boolean loadLogicalLink() {
        for (int linkLevel = 2; linkLevel <= 4; linkLevel++) {
            List<LogicalLinkDto> links = this.linkRepo.findLogicalLinkAll(Integer.toString(linkLevel));
            if (links == null || links.isEmpty()) {
                continue;
            }

            int calCnt = 0;
            for (LogicalLinkDto logicalLinkDto : links) {
                String logicalLinkId = logicalLinkDto.getLogicalLinkId();
                LinkDto logicalLink = this.linkMap.get(logicalLinkId);
                if (logicalLink == null) {
                    continue;
                }

                logicalLink.setLinkIds(logicalLinkDto.getLinkIds());

                if (linkLevel != 4) {
                    continue;
                }

                int linkCount = logicalLinkDto.getLinkIds().size();
                if (linkCount <= 1) {
                    continue;   // 1 개의 구간으로 구성된 로지컬링크.
                }

                // 레벨 4일경우에만 해도 됌.
                for (int ii = 0; ii < logicalLinkDto.getLinkIds().size(); ii++) {
                    String linkId = logicalLinkDto.getLinkIds().get(ii);
                    LinkDto link = this.linkMap.get(linkId);
                    if (link == null) {
                        continue;
                    }

                    link.setLink4Id(logicalLinkId);
                    if (ii == 0) {
                        // 첫번째
                        link.setUpLinkId(logicalLinkDto.getLinkIds().get(ii + 1));
                        link.setDnLinkId("");
                    } else if (ii == (linkCount - 1)) {
                        // 마지막
                        link.setUpLinkId("");
                        link.setDnLinkId(logicalLinkDto.getLinkIds().get(ii - 1));
                    } else {
                        // 중간
                        link.setUpLinkId(logicalLinkDto.getLinkIds().get(ii + 1));
                        link.setDnLinkId(logicalLinkDto.getLinkIds().get(ii - 1));
                    }
                    calCnt++;
                }
            }
            if (linkLevel == 4) {
                log.info("[INF] {}: Level({}) SUB LINK: {} EA.",
                        LogUtils.elapsedLog("findLogicalLinkAll"), linkLevel, calCnt);
            }
        }
        return true;
    }

    private boolean loadPrcsPtrnMissing() {
        List<String> missLinks = this.dwdbParamRepo.findPtrnMissingLink();
        if (missLinks == null || missLinks.isEmpty()) {
            return true;
        }

        int missCount = 0;
        for (String linkId : missLinks) {
            LinkDto link = this.linkMap.get(linkId);
            if (link != null) {
                link.setPrcsPtrnMissing(true);
                missCount++;
            }
            else {
                // 패턴결측 링크가 존재하지 않는 경우
            }
        }
        log.info("[INF] {}: set pattern missing link, {}/{} EA.", LogUtils.elapsedLog("findPtrnMissingLink"),
                missLinks.size(), missCount);
        return true;
    }
    public void initLinkTraf() {
        this.linkMap.values().parallelStream()
                .forEach(LinkDto::initTraf);
    }

    public void initLinkTrafParallel() {
//        this.linkMap.values().parallelStream()
//                .forEach(LinkDto::initTraf);
// 병렬처리로 링크 트래픽 초기화
//        CompletableFuture<Void> fsnFuture = CompletableFuture.runAsync(() -> {
//            this.linkMap.values().parallelStream().forEach(link -> {
//                link.getTrafFsn().init();
//            });
//        });
//        CompletableFuture<Void> psdFuture = CompletableFuture.runAsync(() -> {
//            this.linkMap.values().parallelStream().forEach(link -> {
//                link.getMissPsd().init();
//            });
//        });
//        CompletableFuture<Void> centerFuture = CompletableFuture.runAsync(() -> {
//            this.linkMap.values().parallelStream().forEach(link -> {
//                link.getTrafCenter().init();
//            });
//        });
//        CompletableFuture.allOf(fsnFuture, psdFuture, centerFuture).join();

        // 중복 병렬화, 스레드 풀이 과도하게 사용되지 않도록 수정
        CompletableFuture<Void> fsnFuture = CompletableFuture.runAsync(() -> {
            this.linkMap.values().forEach(link -> link.getTrafFsn().init());
        });
        CompletableFuture<Void> psdFuture = CompletableFuture.runAsync(() -> {
            this.linkMap.values().forEach(link -> link.getMissPsd().init());
        });
        CompletableFuture<Void> centerFuture = CompletableFuture.runAsync(() -> {
            this.linkMap.values().forEach(link -> link.getTrafCenter().init());
        });
        CompletableFuture.allOf(fsnFuture, psdFuture, centerFuture).join();

//        ExecutorService executor = Executors.newFixedThreadPool(100); // 병렬 스레드 수 조절
//        List<CompletableFuture<Void>> fsnFutures = linkMap.values().stream()
//                .map(link -> CompletableFuture.runAsync(() -> link.getTrafFsn().init(), executor))
//                .collect(Collectors.toList());
//        List<CompletableFuture<Void>> psdFutures = linkMap.values().stream()
//                .map(link -> CompletableFuture.runAsync(() -> link.getMissPsd().init(), executor))
//                .collect(Collectors.toList());
//        List<CompletableFuture<Void>> centerFutures = linkMap.values().stream()
//                .map(link -> CompletableFuture.runAsync(() -> link.getTrafCenter().init(), executor))
//                .collect(Collectors.toList());
//        CompletableFuture.allOf(
//                Stream.of(fsnFutures, psdFutures, centerFutures)
//                        .flatMap(List::stream)
//                        .toArray(CompletableFuture[]::new)
//        ).join();
//        executor.shutdown();
    }

    public void initLinkPtrn() {
        this.linkMap.values().parallelStream()
                .forEach(LinkDto::initPtrn);
    }

    public void makeTrafficCenterDto(String prcsDate) {
        this.linkMap.values().parallelStream()
                .filter(link -> link.getTrafFsn().getState().isState())
                .forEach(link -> link.makeTrafficCenterDto(prcsDate));
    }

    public LinkMissingValueHistDto getMissingValueHist(LinkDto link, String exeDate) {
        if (!link.isPrcsPtrnMissing()) {
            return null;
        }
        if (link.getTrafFsn().getState().getCrtDataType() == LinkTrafState.TRAFFIC_CRT_DATA_TYPE.CRT_DATA_NORMAL) {
            return null;
        }
        if ("O".equals(link.getTrafFsn().getState().getMissValueYn())) {
            // 운영자가 입력한 소통정보이면 결측자료로 처리하지 않는다.(20131113)
            return null;
        }

        LinkMissingValueHistDto valueHist = new LinkMissingValueHistDto(link, exeDate);
        if (link.getTrafFsn().getState().isState()) {
            // 결측값
            valueHist.setMissingType(link.getTrafFsn().getState().getMissValueYn());
            valueHist.setRevisionSpd(link.getTrafFsn().getSpeed());
        }
        if (link.getTrafPtrn().getState().isState()) {
            // 패턴값
            valueHist.setLinkPatnSpd(link.getTrafPtrn().getSpeed());
        }

        LinkDto upLink = this.linkMap.get(link.getUpLinkId());
        if (upLink != null) {
            // 상류부 링크
            if (upLink.getTrafFsn().getState().isState() && upLink.getTrafFsn().getState().getCrtDataType() == LinkTrafState.TRAFFIC_CRT_DATA_TYPE.CRT_DATA_NORMAL) {
                valueHist.setUpLinkSpd(upLink.getTrafFsn().getSpeed());
            }
            if (upLink.getTrafPtrn().getState().isState()) {
                valueHist.setUpPatnSpd(upLink.getTrafPtrn().getSpeed());
            }
        }
        LinkDto dnLink = this.linkMap.get(link.getDnLinkId());
        if (dnLink != null) {
            // 상류부 링크
            if (dnLink.getTrafFsn().getState().isState() && dnLink.getTrafFsn().getState().getCrtDataType() == LinkTrafState.TRAFFIC_CRT_DATA_TYPE.CRT_DATA_NORMAL) {
                valueHist.setDnLinkSpd(dnLink.getTrafFsn().getSpeed());
            }
            if (dnLink.getTrafPtrn().getState().isState()) {
                valueHist.setDnPatnSpd(dnLink.getTrafPtrn().getSpeed());
            }
        }
        LinkDto grpLink = this.linkMap.get(link.getLink4Id());
        if (grpLink != null) {
            // 레벨4 링크
            if (grpLink.getTrafFsn().getState().isState() && grpLink.getTrafFsn().getState().getCrtDataType() == LinkTrafState.TRAFFIC_CRT_DATA_TYPE.CRT_DATA_NORMAL) {
                valueHist.setL4LinkSpd(grpLink.getTrafFsn().getSpeed());
            }
            if (grpLink.getTrafPtrn().getState().isState()) {
                valueHist.setL4PatnSpd(grpLink.getTrafPtrn().getSpeed());
            }
        }
        valueHist.setPsdSpeed(link.getMissPsd().getSpeed());
        valueHist.setKnnSpeed(0);
        valueHist.setKnnCnt(0);
        return valueHist;
    }

}
