package com.its.pis.websocket;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.its.app.AppUtils;
import com.its.app.utils.SysUtils;
import com.its.pis.entity.TbPisInfr;
import com.its.pis.entity.TbPisInfrStts;
import com.its.pis.entity.TbPrkPlce;
import com.its.pis.entity.TbPrkPlceStts;
import com.its.pis.global.AppRepository;
import com.its.pis.process.DbmsData;
import com.its.pis.process.DbmsDataProcess;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@Slf4j
public class PisWebSocketHandler extends TextWebSocketHandler {

    protected String type;
    protected ObjectMapper mapper;
    protected DbmsDataProcess dbmsDataProcess;

    public PisWebSocketHandler(String type) {

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            for (Map.Entry<WebSocketSession, PisWebSocketSession> obj : PisWebSocketSessionManager.getInstance().getMap().entrySet()) {
                if (obj.getValue() != null) {
                }
            }
        }));

        this.type = type;
        this.mapper = new ObjectMapper();
        this.dbmsDataProcess = (DbmsDataProcess) AppUtils.getBean(DbmsDataProcess.class);

        log.info("PisWebSocketHandler() START, type={}", this.type);
    }

    public void updatePisStts(TbPisInfr pis, boolean online) {

        String CMNC_STTS_CD = online ? "CMS0" : "CMS1";
        String UPDT_DT = SysUtils.getSysTime();

        List<TbPisInfrStts> pisOnSttsList = Collections.synchronizedList(new ArrayList<>());

        pis.getStts().setUPDT_DT(UPDT_DT);
        pis.getStts().setCMNC_STTS_CD(CMNC_STTS_CD);
        pisOnSttsList.add(pis.getStts());
        this.dbmsDataProcess.add(new DbmsData(DbmsData.DBMS_DATA_PIS_STTS, false, pisOnSttsList, pis));

        if (PisConstants.C2F.equals(pis.getPIS_TP())) {
            // 자체수집하는 경우 주차정보시스템의 상태가 주차장의 상태정보이다.
            List<TbPrkPlceStts> prkPlceSttsList = Collections.synchronizedList(new ArrayList<>());
            for (Map.Entry<String, TbPrkPlce> el : pis.getRltnPrkPlce().entrySet()) {
                TbPrkPlce obj = el.getValue();
                pis.getStts().setUPDT_DT(UPDT_DT);
                obj.getStts().setCMNC_STTS_CD(CMNC_STTS_CD);
                prkPlceSttsList.add(obj.getStts());
            }
            if (prkPlceSttsList.size() > 0) {
                this.dbmsDataProcess.add(new DbmsData(DbmsData.DBMS_DATA_PRK_PLCE_STTS, false, prkPlceSttsList, null));
            }
        }

    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 클라이언트가 연결되었을때 실행
        String ipAddr = session.getRemoteAddress().getHostString();
        String token = null;
        for (Map.Entry<String, List<String>> l : session.getHandshakeHeaders().entrySet()) {
            if ("sec-websocket-protocol".equals(l.getKey())) {
                token = l.getValue().get(0);
                break;
            }
        }
        if (token == null) {
            log.error("{}: afterConnectionEstablished: {}, token not found, session will be closed", this.type, ipAddr);
            session.close();
            return;
        }

        log.info("{}: afterConnectionEstablished: ipAddr={}, token={}", this.type, ipAddr, token);

        TbPisInfr pis = AppRepository.getInstance().getPisTokenMap().get(token);
        if (pis == null) {
            log.error("{}: afterConnectionEstablished: ipAddr={}, token={}, not found token, session will be closed", this.type, ipAddr, token);
            session.close();
            return;
        }

        pis.setLastRecvTime(System.currentTimeMillis());

        if (pis.getSession() != null) {
            WebSocketSession pisSession = pis.getSession().getSession();
            if (pisSession != null && pisSession.isOpen()) {
                log.error("{}: afterConnectionEstablished: ipAddr={}, token={}, session already opened, old session will be closed", this.type, ipAddr, token);
                try {
                    log.error("{}: afterConnectionEstablished: ipAddr={}, token={}, pis={}, old session", this.type, pis.getConnectIpAddr(), pis.getPIS_TOKEN(), pis.getPIS_ID());
                    pisSession.close();
                } catch(IOException e) {}
            }
        }

        PisWebSocketSession vo = new PisWebSocketSession(pis, session);
        pis.channelOpen(vo);
        pis.setConnectIpAddr(ipAddr);
        PisWebSocketSessionManager.getInstance().addSession(session, vo);

        session.setBinaryMessageSizeLimit(1*1024*1024);
        session.setTextMessageSizeLimit(1*1024*1024);
        super.afterConnectionEstablished(session);

        updatePisStts(pis, true);
        log.info("{}: afterConnectionEstablished: connection accept, {}, ipAddr={}, token={}, {}", this.type, pis.getPIS_ID(), ipAddr, token, pis.getPIS_NM());
    }

    //클라이언트 연결을 끊었을 때 실행
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.warn("{}: afterConnectionClosed: session={}, URI={}, UUID={}", this.type, session.getRemoteAddress(), session.getUri(), session.getId());
        PisWebSocketSession sessionClient = PisWebSocketSessionManager.getInstance().getSession(session);
        if (sessionClient != null) {
            TbPisInfr pis = sessionClient.getObj();
            if (pis != null) {
                pis.channelClosed();

                updatePisStts(pis, false);

//                List<TbPisInfrStts> pisOffSttsList = Collections.synchronizedList(new ArrayList<>());
//                pis.getStts().initStts(false);
//                pisOffSttsList.add(pis.getStts());
//                this.dbmsJobProcess.add(new DbmsJobData(DbmsJobType.DATA_TYPE_PIS_STTS, false, pisOffSttsList, pis));
//                if ("Y".equals(pis.getPIS_CLCT_YN())) {
//                    // 주차정보를 수집하는 주차정보시스템인 경우 모든 주차장의 통신을 비정상으로 설정
//                    List<TbPrkPlceStts> prkPlceSttsList = Collections.synchronizedList(new ArrayList<>());
//                    for (Map.Entry<String, TbPrkPlce> el : pis.getRltnPrkPlce().entrySet()) {
//                        TbPrkPlce obj = el.getValue();
//                        obj.getStts().initStts(false);
//                        prkPlceSttsList.add(obj.getStts());
//                    }
//                    if (prkPlceSttsList.size() > 0) {
//                        this.dbmsJobProcess.add(new DbmsJobData(DbmsJobType.DATA_TYPE_PRK_PLCE_STTS, false, prkPlceSttsList, null));
//                    }
//                }
            }
        }
        else {
            log.warn("{}: afterConnectionClosed: Not Found session={}, URI={}, UUID={}", this.type, session.getRemoteAddress(), session.getUri(), session.getId());
        }

        PisWebSocketSessionManager.getInstance().removeSession(session);
        super.afterConnectionClosed(session, status);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        String ipAddr = "1";
        if (session.getRemoteAddress() != null) {
            ipAddr = session.getRemoteAddress().getHostString();
        }
        log.error("{}: handleTransportError: ipAddr={}, {}", this.type, ipAddr, exception.getMessage());
        if (session.isOpen()) {
            session.close();
        }
        super.handleTransportError(session, exception);
    }
}
