package com.its.pis.service;

import com.its.app.AppUtils;
import com.its.app.utils.SysUtils;
import com.its.pis.dao.mapper.PisInfrMapper;
import com.its.pis.dao.mapper.PrkPlceMapper;
import com.its.pis.domain.NET;
import com.its.pis.entity.*;
import com.its.pis.global.AppRepository;
import com.its.pis.process.DbmsData;
import com.its.pis.process.DbmsDataProcess;
import com.its.pis.websocket.PisConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;

@Slf4j
@Service
@Transactional(rollbackFor = {Exception.class})
//@Order(1)
public class PisInfrService {

    private PisInfrMapper pisInfrMapper;
    private PrkPlceMapper prkPlceMapper;
    private DbmsDataProcess dbmsDataProcess;

    private int historyMin;

    @PostConstruct
    private void init() {
        log.info("PisInfrService.init: Start.");
        this.pisInfrMapper = (PisInfrMapper) AppUtils.getBean(PisInfrMapper.class);
        this.prkPlceMapper = (PrkPlceMapper) AppUtils.getBean(PrkPlceMapper.class);
        this.dbmsDataProcess = (DbmsDataProcess) AppUtils.getBean(DbmsDataProcess.class);
        this.historyMin = -1;
        loadDb();
        log.info("PisInfrService.init: ..End.");
    }

    @PreDestroy
    public void destroyService() {
        log.error("UnitSystService.destroy. system terminated.......");
        updateCtlrStts(false);
    }

    /**
     * 기초정보 메모리 로딩
     */
    public void loadDb() {
        loadPisInfr();
        loadPisInfrStts();
        loadPisRltnPrkPlce();
    }

    /**
     * 주차정보시스템 정보조회
     */
    public void loadPisInfr() {
        List<TbPisInfr> objList = this.pisInfrMapper.selectAll();
        for (TbPisInfr obj : objList) {
            obj.setPIS_IP(obj.getPIS_IP().trim());
            obj.setPIS_CYCLE(Math.max(30, obj.getPIS_CYCLE()));
            obj.getStts().setPIS_NMBR(obj.getPIS_NMBR());
            obj.getStts().initStts(false);

            AppRepository.getInstance().getPisInfrMap().put(obj.getPIS_NMBR(), obj);
            AppRepository.getInstance().getPisIpAddrMap().put(obj.getPIS_IP(), obj);
            AppRepository.getInstance().getPisTokenMap().put(obj.getPIS_TOKEN(), obj);
            AppRepository.getInstance().getPisIdMap().put(obj.getPIS_ID(), obj);
        }

//        log.info("loadPisInfr.PIS NMBR List............... {} EA.", AppRepository.getInstance().getPisInfrMap().size());
//        for (Map.Entry<String, TbPisInfr> e : AppRepository.getInstance().getPisInfrMap().entrySet()) {
//            log.info("  {}", e.toString());
//        }
//        log.info("loadPisInfr.PIS IP List............ {} EA.", AppRepository.getInstance().getPisIpAddrMap().size());
//        for (Map.Entry<String, TbPisInfr> e : AppRepository.getInstance().getPisIpAddrMap().entrySet()) {
//            log.info("  {}", e.toString());
//        }
//        log.info("loadPisInfr.PIS TOKEN List............ {} EA.", AppRepository.getInstance().getPisTokenMap().size());
//        for (Map.Entry<String, TbPisInfr> e : AppRepository.getInstance().getPisTokenMap().entrySet()) {
//            log.info("  {}", e.toString());
//        }
//        log.info("loadPisInfr.PIS ID List............ {} EA.", AppRepository.getInstance().getPisIdMap().size());
//        for (Map.Entry<String, TbPisInfr> e : AppRepository.getInstance().getPisIdMap().entrySet()) {
//            log.info("  {}", e.toString());
//        }
    }

    /**
     * 주차정보시스템 통신상태 조회
     */
    public void loadPisInfrStts() {
        List<TbPisInfrStts> sttsList = this.pisInfrMapper.selectPisInfrStts();
        for (TbPisInfrStts stts : sttsList) {
            TbPisInfr obj = AppRepository.getInstance().getPisInfrMap().get(stts.getPIS_NMBR());
            if (obj == null) {
                log.warn("loadPisInfrStts.PIS Stts Not Found, {}", stts.getPIS_NMBR());
            } else {
                obj.getStts().setCONN_DT(stts.getCONN_DT());
                obj.getStts().setCONN_DT(stts.getCONN_DT());
                obj.getStts().setDIS_CONN_DT(stts.getDIS_CONN_DT());
            }
        }
    }

    /**
     * 주차정보시스템에 속한 주차장 정보 조회
     */
    public void loadPisRltnPrkPlce() {

        loadPrkPlce();
        loadPrkPlceInfo();

        List<TbPisRltnPrlt> rltnList = this.pisInfrMapper.selectPisRltnPrkPlce();
        for (TbPisRltnPrlt rltn : rltnList) {
            TbPisInfr obj = AppRepository.getInstance().getPisInfrMap().get(rltn.getPIS_NMBR());
            if (obj == null) {
                log.error("loadPisRltnPrkPlce.PIS Rltn PIS Not Found, {}", rltn.getPIS_NMBR());
                continue;
            }
            TbPrkPlce prlt =  AppRepository.getInstance().getPrltNmbrMap().get(rltn.getPRK_PLCE_NMBR());
            if (prlt == null) {
                log.error("loadPisRltnPrkPlce.PIS Rltn PRK Not Found, {}", rltn.getPRK_PLCE_NMBR());
                continue;
            }

            obj.getRltnPrkPlce().put(rltn.getPRK_PLCE_NMBR(), prlt);
        }

//        log.info("loadPisRltnPrlt.PIS Rltn PRLT List.................");
//        for (Map.Entry<String, TbPisInfr> e : AppRepository.getInstance().getPisInfrMap().entrySet()) {
//            log.info("loadPisRltnPrlt.PIS PRLT Prlt: {}", e.getValue().getPIS_NMBR());
//            for (Map.Entry<String, TbPrkPlce> l : e.getValue().getRltnPrkPlce().entrySet()) {
//                log.info("  {}", l.toString());
//            }
//        }
    }

    /**
     * 주차장 정보 조회
     */
    public void loadPrkPlce() {

        List<TbPrkPlce> objList = this.prkPlceMapper.selectAll();
        for (TbPrkPlce obj : objList) {
            obj.getStts().setPRK_PLCE_NMBR(obj.getPRK_PLCE_NMBR());
            obj.getStts().initStts(false);

            AppRepository.getInstance().getPrltNmbrMap().put(obj.getPRK_PLCE_NMBR(), obj);
            AppRepository.getInstance().getPrltIdMap().put(obj.getPRK_PLCE_MANAGE_NO(), obj);
        }

//        log.info("loadPrkPlce.PRK NMBR List............... {} EA.", AppRepository.getInstance().getPrltNmbrMap().size());
//        for (Map.Entry<String, TbPrkPlce> e : AppRepository.getInstance().getPrltNmbrMap().entrySet()) {
//            log.info("  {}", e.toString());
//        }
//
//        log.info("loadPrkPlce.PRK ID List............... {} EA.", AppRepository.getInstance().getPrltIdMap().size());
//        for (Map.Entry<String, TbPrkPlce> e : AppRepository.getInstance().getPrltIdMap().entrySet()) {
//            log.info("  {}", e.toString());
//        }
    }

    /**
     * 주차장 시설정보 및 운영정보를 메모리에 로딩해 놓는다.
     */
    public void loadPrkPlceInfo() {

    }

    public void updateCtlrStts(boolean isRun) {
        String keyData = "unit";
        boolean insHs = false;
        Calendar cal = Calendar.getInstance();
        int min = cal.get(Calendar.MINUTE);
        if ((min % 5) == 0 && this.historyMin != min) {
            insHs = true;
            this.historyMin = min;
        }

        if (insHs) {
            // 5분 마다 실시간 주차장 이력 정보를 저장하자.
            insertPrkPlceRlTimeHs();
        }

        // 주차정보시스템 상태정보 업데이트
        List<TbPisInfrStts> pisSttsList = Collections.synchronizedList(new ArrayList<>());
        // 주차장 상태정보 업데이트
        List<TbPrkPlceStts> prkPlceSttsList = Collections.synchronizedList(new ArrayList<>());

        String UPDT_DT = SysUtils.getSysTime();
        int normal = 0;
        int error  = 0;
        for (Map.Entry<String, TbPisInfr> e : AppRepository.getInstance().getPisInfrMap().entrySet()) {
            TbPisInfr pis = e.getValue();
            pis.getStts().setUPDT_DT(UPDT_DT);
            if (isRun && pis.getNetState() >= NET.LOGIN_REQ && pis.getSession() != null) {
                // 주차정보시스템 통신정상
                normal++;
                pis.getStts().setCMNC_STTS_CD("CMS0");

                // 주차정보시스템에 연결된 주차장의 상태정보를 업데이트 한다.
                if ("Y".equals(pis.getPIS_CLCT_YN())) {
                    // 주차정보시스템이 데이터를 수집하지 않으면 주차장 상태정보를 업데이트 할 필요 없다.
                    for (Map.Entry<String, TbPrkPlce> el : pis.getRltnPrkPlce().entrySet()) {
                        TbPrkPlce obj = el.getValue();
                        obj.getStts().setUPDT_DT(UPDT_DT);
                        if (PisConstants.C2F.equals(pis.getPIS_TP())) {
                            // 자체 수집하는 경우(주차정보시스템 당 주차장은 1개가 존재한다.
                            obj.getStts().setCMNC_STTS_CD("CMS0");
                        } else {
                            // 외부 기관으로 부터 정보를 연계하는 경우 주차장은 여러개 일 수 있다.
                            // 주차장의 상태정보는 기관으로 부터 수신하기 때문에 여기서는 확인할수 없고
                            // 통신상에서 업데이트 되는것을 그대로 업데이트 해야 한다.
                        }
                        prkPlceSttsList.add(obj.getStts());
                    }
                }
            }
            else {
                // 주차정보시스템 통신이상
                error++;
                pis.getStts().initStts(false);
                pis.getStts().setUPDT_DT(UPDT_DT);

                // 주차정보시스템에 연결된 주차장의 상태정보를 업데이트 한다.
                if ("Y".equals(pis.getPIS_CLCT_YN())) {
                    // 주차정보시스템이 데이터를 수집하지 않으면 주차장 상태정보를 업데이트 할 필요 없다.
                    for (Map.Entry<String, TbPrkPlce> el : pis.getRltnPrkPlce().entrySet()) {
                        TbPrkPlce obj = el.getValue();
                        // 주차정보시스템의 통신상태가 비정상이기 때문에 주차장의 상태정보도 비정상이다.
                        obj.getStts().initStts(false);
                        obj.getStts().setUPDT_DT(UPDT_DT);
                        prkPlceSttsList.add(obj.getStts());
                    }
                }
            }

            pisSttsList.add(pis.getStts());
        }

        // 주차정보시스템 상태정보 DB 업데이트
        if (pisSttsList.size() > 0) {
            this.dbmsDataProcess.add(new DbmsData(DbmsData.DBMS_DATA_PIS_STTS, insHs, pisSttsList, null));
        }
        // 주차장 상태정보 DB 업데이트
        if (prkPlceSttsList.size() > 0) {
            this.dbmsDataProcess.add(new DbmsData(DbmsData.DBMS_DATA_PRK_PLCE_STTS, insHs, prkPlceSttsList, null));
        }
        if (insHs) {
            log.info("DATA_TYPE_PRK_PLCE_STTS: {}, {} EA.", UPDT_DT, prkPlceSttsList.size());
        }
        log.info("PisInfrService.updateCtlrStts: PIS, total {}, normal {}, error {}", normal + error, normal, error);
    }

    public void insertPrkPlceRlTimeHs() {
        TbPrkPlceRtHs rtHs = TbPrkPlceRtHs.builder()
                .crtnDt(SysUtils.getSysTimeHm())
                .build();
        log.info("insertPrkPlceRlTimeHs: {}", rtHs.getCrtnDt());
        this.dbmsDataProcess.add(new DbmsData(DbmsData.DBMS_DATA_RL_TIME_HS, true, rtHs, null));
    }

    /**
     * C2C-S 로 설정되어 있는 주차정보 시스템을 찾아 해당 주차정보 시스템으로 연결하여 통신하는 C2C_Client 핸들러를 실행시킨다.
     */
    public void startC2C_Client() {
        for (Map.Entry<String, TbPisInfr> e : AppRepository.getInstance().getPisInfrMap().entrySet()) {
            TbPisInfr obj = e.getValue();
            if (PisConstants.C2CServer.equals(obj.getPIS_TP())) {
                log.info("Connect to C2C Server: {}", obj);
                obj.createWebsocketClient();
            }
        }
    }

    /**
     * 세션 연결후 주차정보 업로드 주기동안 데이터가 올라오지 않으면 실시간 정보를 요청한다.
     * 마지막 수신시각에서 60초 이상 지나면 세션연결을 종료한다.
     */
    public void monitoringC2FSession() {
        long currMilliSeconds = System.currentTimeMillis();
        for (Map.Entry<String, TbPisInfr> e : AppRepository.getInstance().getPisInfrMap().entrySet()) {
            TbPisInfr pis = e.getValue();
            if (pis.getNetState() >= NET.LOGIN_REQ && pis.getSession() != null) {
                // 주차정보시스템 통신정상
                long recvTimout = currMilliSeconds - pis.getLastRecvTime();
                if (recvTimout > ((pis.getPIS_CYCLE() * 1000L) * 3)) {
                    log.error("Idle timeout: {}, ipAddr={}, Idle={} sec.", pis.getPIS_ID(), pis.getConnectIpAddr(), (int)(recvTimout/1000L));
                    pis.channelClose();
                    continue;
                }

                if (recvTimout > (pis.getPIS_CYCLE() * 1000L)) {
                    boolean isSendOk = pis.requestRlTimeInfo();
                    log.info("Idle request: {}, ipAddr={}, sendOk={}, Idle={} sec.", pis.getPIS_ID(), pis.getConnectIpAddr(), isSendOk, (int)(recvTimout/1000L));
                }
            }
        }
    }
}
