package com.icomsys.main_vm.biz.rcp.util;

import com.icomsys.main_vm.biz.advice.excep.CustomBadRequestException;
import com.icomsys.main_vm.biz.advice.excep.CustomNoSuchFieldException;
import com.icomsys.main_vm.common.code.enumresource.ScenarioNodeType;
import com.icomsys.main_vm.common.util.HashUtil;
import com.jayway.jsonpath.DocumentContext;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import com.jayway.jsonpath.JsonPath;

import java.security.NoSuchAlgorithmException;
import java.util.*;

import org.json.JSONArray;
import org.json.JSONObject;

@Slf4j
@Getter
@Setter
@NoArgsConstructor
public class ScenarioConverter {
    private String diagramString;
    private String diagramChecksum;
    private String callScenarioString;
    private String callScenarioChecksum;
    private DocumentContext diagramContext;

    private Map<Integer, String> diagramShapeKey;
    private Map<String, String> diagramConnector;

    public ScenarioConverter(String diagramString) {
        this.diagramString = diagramString;
    }

    public void createDiagramChecksum() throws NoSuchAlgorithmException {
        diagramChecksum = HashUtil.getStringToChecksum(diagramString);
    }

    public void createScenarioChecksum() throws NoSuchAlgorithmException {
        callScenarioChecksum = HashUtil.getStringToChecksum(callScenarioString);
    }

    public void createChecksum() throws NoSuchAlgorithmException {
        diagramChecksum = HashUtil.getStringToChecksum(diagramString);
        callScenarioChecksum = HashUtil.getStringToChecksum(callScenarioString);
    }



    public void convertDiagramToCallScenario() throws CustomBadRequestException {

        JSONObject ojbCallScenario = new JSONObject();
        // JSONObject objCallInfo = new JSONObject(); // 기본변수 삭제됨

        if (diagramContext == null) {
            diagramContext = JsonPath.parse(diagramString);
        }
        // log.debug(diagramString);

        // 다이어그램 shape, connector 값 초기화
        createDiagramShapeKey();
        createDiagramConnector();

        // 시나리오 변수 세팅
        ojbCallScenario.put("scenarioParam", convertScenarioParams());

        JSONArray arrayNode = new JSONArray();
        List<Map<String,Object>> shapes = diagramContext.read("$.shapes.[*].customData");

        for (Map<String,Object> shape : shapes) {

            JSONObject objNode = new JSONObject();
            Integer nodeCode = (Integer) shape.get("nodeCode");
            objNode.put("nodeType", shape.get("nodeType"));
            objNode.put("nodeCode", Integer.toString(nodeCode));
            objNode.put("nodeName", shape.get("nodeName"));

            String nextNode = findNextNodeCode(nodeCode);
            objNode.put("nextNode", nextNode);

            String nodeType = (String)shape.get("nodeType");
            switch (ScenarioNodeType.findByName(nodeType)) {
                case Intent:
                    convertIntent(objNode, shape);
                    break;
                case Speak:
                    convertSpeak(objNode, shape);
                    break;
                case Slot:
                    convertSlot(objNode, shape);
                    break;
                case Split:
                    convertSplit(objNode, shape);
                    break;
                case API:
                    convertAPI(objNode, shape);
                    break;
                case Common:
                    convertCommon(objNode, shape);
                    break;
                case Function:
                    convertFunction(objNode, shape);
                    break;
                case Call:
                    convertCall(objNode, shape);
                    break;
                case IN:
                case OUT:
                case HORIZONTALCONTAINER:
                case VERTICALCONTAINER:
                    // 기본 세팅 없음
                    break;
                default:
                    throw new CustomBadRequestException("Undefined nodeType: " + nodeType);
            }

            arrayNode.put(objNode);
        }

        ojbCallScenario.put("scenario", arrayNode);
        callScenarioString  = ojbCallScenario.toString();
        // log.debug(callScenarioString);
    }


    private JSONObject convertScenarioParams(){
        JSONObject params = new JSONObject();
        LinkedHashMap<String,Object> scParams = diagramContext.read("$.scenarioParam");
        for (String paramKey: scParams.keySet()) {
            params.put(paramKey, "");
        }

        return params;
    }

    private void convertIntent(JSONObject objNode, Map<String,Object> node) {
        JSONObject objIntent = new JSONObject();
        String[] keyNames = {"nodeMent", "intentSeq" };
        for (String keyName: keyNames) {
            objIntent.put(keyName, node.get(keyName));
        }

        objNode.put("nodeOption", objIntent);
    }

    private void convertSpeak(JSONObject objNode, Map<String,Object> node) {
        JSONObject objSpeak = new JSONObject();
        String[] keyNames = {"nodeMent", "bargeIn", "endFlag" };
        for (String keyName: keyNames) {
            objSpeak.put(keyName, node.get(keyName));
        }

        objNode.put("nodeOption", objSpeak);
    }

    private void convertSlot(JSONObject objNode, Map<String,Object> node) {
        JSONObject objSlot = new JSONObject();

        String[] keyNames = {"paramName", "nodeMent", "bargeIn", "reNodeMent", "reBargeIn", "nodeQstCnt", "requiredYn", "actionType", "maxDigit", "entityType" };
        for (String keyName: keyNames) {
            objSlot.put(keyName, node.get(keyName));
        }

        objNode.put("nodeOption", objSlot);
    }

    private void convertSplit(JSONObject objNode, Map<String,Object> node) {
        JSONObject objSplit = new JSONObject();

        JSONArray arrayConditionList = new JSONArray();
        LinkedHashMap<String, Object> conditionList = (LinkedHashMap<String,Object>)node.get("conditionList");
        if (conditionList != null) {

            Set<String> conditionKeySet = conditionList.keySet();

            for (String key : conditionKeySet) {
                LinkedHashMap<String,Object> condition = (LinkedHashMap<String,Object>)conditionList.get(key);
                JSONObject objCondition = new JSONObject();
                objCondition.put("nextNode", String.valueOf(condition.get("nextNode")));
                objCondition.put("condition", (condition.get("condition") == null)? "" : condition.get("condition"));
                objCondition.put("nodeMent", (condition.get("nodeMent") == null)? "" : condition.get("nodeMent"));

                arrayConditionList.put(objCondition);
            }
        }

        objSplit.put("conditionList",arrayConditionList);
        objNode.put("nodeOption", objSplit);
    }

    private void convertAPI(JSONObject objNode, Map<String,Object> node) {
        JSONObject objAPI = new JSONObject();
        objAPI.put("apiNodeSeq", node.get("apiNodeSeq"));

        // 요청 파라메터 변환
        JSONArray arrayReqParamList = new JSONArray();
        LinkedHashMap<String,Object> reqParamList = (LinkedHashMap<String,Object>)node.get("reqParamList");
        if (reqParamList != null) {
            for (int i=0; i < reqParamList.size(); i++) {
                LinkedHashMap<String,Object> reqParam = (LinkedHashMap<String,Object>)reqParamList.get(Integer.toString(i));
                JSONObject objReqParam = new JSONObject();
                objReqParam.put("reqParamName", reqParam.get("reqParamName"));
                objReqParam.put("reqParamMap", reqParam.get("reqParamMap"));

                arrayReqParamList.put(objReqParam);
            }
        }

        // 응답 파라메터 변환
        JSONArray arrayResParamList = new JSONArray();
        LinkedHashMap<String,Object> resParamList = (LinkedHashMap<String,Object>)node.get("resParamList");
        if (resParamList != null) {
            for (int i=0; i < resParamList.size(); i++) {
                LinkedHashMap<String, Object> resParam = (LinkedHashMap<String, Object>) resParamList.get(Integer.toString(i));
                JSONObject objResParam = new JSONObject();
                objResParam.put("resParamName", resParam.get("resParamName"));
                objResParam.put("resParamMap", resParam.get("resParamMap"));

                arrayResParamList.put(objResParam);
            }
        }
        objAPI.put("reqParamList", arrayReqParamList);
        objAPI.put("resParamList", arrayResParamList);
        objNode.put("nodeOption", objAPI);
    }

    private void convertFunction(JSONObject objNode, Map<String,Object> node) {
        JSONObject objFunction = new JSONObject();

        String[] keyNames = {"javaScript"};
        for (String keyName: keyNames) {
            objFunction.put(keyName, node.get(keyName));
        }

        JSONArray arrayFuncParam = new JSONArray();
        LinkedHashMap<String,Object> funcParam = (LinkedHashMap<String,Object>)node.get("funcParam");
        if (funcParam != null) {
            Collection<Object> paramList = funcParam.values();
            for (Object paramName : paramList) {
                arrayFuncParam.put((String)paramName);
            }
        }
        objFunction.put("funcParam", arrayFuncParam);

        objNode.put("nodeOption", objFunction);
    }

    private void convertCommon(JSONObject objNode, Map<String,Object> node) {
        JSONObject objCommon = new JSONObject();

        String[] keyNames = {"commonNodeSeq"};
        for (String keyName: keyNames) {
            objCommon.put(keyName, node.get(keyName));
        }

        objNode.put("nodeOption", objCommon);
    }

    private void convertCall(JSONObject objNode, Map<String,Object> node) {
        JSONObject objCall = new JSONObject();

        String[] keyNames = {"nodeMent", "bargeIn", "callProcess", "shopPhoneNumber"};
        for (String keyName: keyNames) {
            objCall.put(keyName, node.get(keyName));
        }

        objNode.put("nodeOption", objCall);
    }

    private void createDiagramShapeKey() {
        if (diagramShapeKey == null) {
            diagramShapeKey = new HashMap<Integer,String>();
        } else {
            diagramShapeKey.clear();
        }

        if (diagramContext == null) {
            diagramContext = JsonPath.parse(diagramString);
        }

        List<Map<String,Object>> shapes = diagramContext.read("$.shapes.[*]");
        for (Map<String,Object> shape : shapes) {
            String key = (String)shape.get("key");
            LinkedHashMap<String,Object> customData = (LinkedHashMap<String,Object>)shape.get("customData");
            Integer id =  (Integer)customData.get("ID");

            diagramShapeKey.put(id,key);
        }
    }

    private void createDiagramConnector() {
        if (diagramConnector == null) {
            diagramConnector = new HashMap<String,String>();
        } else {
            diagramConnector.clear();
        }

        if (diagramContext == null) {
            diagramContext = JsonPath.parse(diagramString);
        }

        List<Map<String,Object>> connectors = diagramContext.read("$.connectors.[*]");
        for (Map<String,Object> connector : connectors) {
            String begin = (String)connector.get("beginItemKey");
            String end = (String)connector.get("endItemKey");

            diagramConnector.put(begin, end);
        }
    }

    private String findNextNodeCode(Integer nodeCode) {
        String endKey = diagramConnector.get(diagramShapeKey.get(nodeCode));
        if (endKey == null) {
            return "";
        }

        for(Map.Entry<Integer, String> entry: diagramShapeKey.entrySet()) {
            if (endKey.equals(entry.getValue())) {
                return String.valueOf(entry.getKey());
            }
        }

        return "";
    }

    public Long getIntentSeq() {
        if (diagramContext == null) {
            diagramContext = JsonPath.parse(diagramString);
        }
        List<Object> intentSeq = diagramContext.read("$.shapes.[?(@.type==\"Intent\")].customData.intentSeq");
        // List<Object> intentSeq = diagramContext.read("$.shapes[?(@.nodeType=='INTENT')].customData.intentSeq");  // javascript jsonpath에서는 동작 하지만 java에서 검색이 안됨

        Long result = -1L;
        if (intentSeq.size() != 0) {
            result = Long.valueOf((Integer)intentSeq.get(0));
        }
        return result;
    }

    public void validationCheckScenario(String scenarioJsonString) throws CustomBadRequestException {
        Boolean isSuccess = true;
        String resultMessage = "";

        DocumentContext scenarioContext = JsonPath.parse(scenarioJsonString);
        List<Map<String,Object>> nodes = scenarioContext.read("$.scenario.[*]");

        for (Map<String,Object> node : nodes) {
            String nodeType = (String)node.get("nodeType");
            String nodeCode = (String)node.get("nodeCode");
            Map<String,Object> nodeOption = (LinkedHashMap<String,Object>)node.get("nodeOption");

            switch (ScenarioNodeType.findByName(nodeType)) {
                case Intent:
                    if (isNotSettingInteger(nodeOption.get("intentSeq"))) {
                        resultMessage += String.format("[%s: %s] 인텐트가 설정되지 않았습니다.\n", nodeCode, nodeType);
                        isSuccess = false;
                    }

                    break;
                case Speak:
                    if (isEmptyString(nodeOption.get("nodeMent"))) {
                        isSuccess = false;
                        resultMessage += String.format("[%s: %s] 멘트가 설정되지 않았습니다.\n", nodeCode, nodeType);
                    }

                    break;
                case Slot:
                    if (isEmptyString(nodeOption.get("paramName"))) {
                        isSuccess = false;
                        resultMessage += String.format("[%s: %s] 파라메터ID가 설정되지 않았습니다.\n", nodeCode, nodeType);
                    }

                    if (isEmptyString(nodeOption.get("nodeMent"))) {
                        isSuccess = false;
                        resultMessage += String.format("[%s: %s] 멘트가 설정되지 않았습니다.\n", nodeCode, nodeType);
                    }

                    switch ((String)nodeOption.get("actionType")) {
                        case "STT":
                        case "MEMO":
                            String entityType = (String)nodeOption.get("entityType");
                            if ( entityType.equals("") ){
                                isSuccess = false;
                                resultMessage += String.format("[%s: %s] 엔티티타입이 설정되지 않았습니다.\n", nodeCode, nodeType);
                            }
                            break;
                        default:
                    }

                    break;
                case Split:
                    Object objConditions = nodeOption.get("conditionList");
                    JSONArray conditions = new JSONArray(objConditions.toString());

                    if ( conditions.length() == 0) {
                        isSuccess = false;
                        resultMessage += String.format("[%s: %s] 연결된 다음 노드가 없습니다.\n", nodeCode, nodeType);
                    }

                    for (int i=0; i < conditions.length() ; i++) {
                        JSONObject nextNodeInfo = (JSONObject) conditions.get(i);
                        if (isEmptyString(nextNodeInfo.get("condition"))) {
                            isSuccess = false;
                            resultMessage += String.format("[%s: %s] 다음 노드의 조건이 설정되지 않았습니다.\n", nodeCode, nodeType);
                        }
                    }

                    break;
                case API:
                    if (isNotSettingInteger(nodeOption.get("apiNodeSeq"))) {
                        resultMessage += String.format("[%s: %s] API노드가 설정되지 않았습니다.\n", nodeCode, nodeType);
                        isSuccess = false;
                    }
                    break;
                case Common:
                    if (isNotSettingInteger(nodeOption.get("commonNodeSeq"))) {
                        resultMessage += String.format("[%s: %s] Common노드가 설정되지 않았습니다.\n", nodeCode, nodeType);
                        isSuccess = false;
                    }
                    break;
                case Call:
                    String callProcess = String.valueOf(nodeOption.get("callProcess"));
                    if (callProcess.equals("none")) {
                        resultMessage += String.format("[%s: %s] 콜처리가 설정되지 않았습니다.\n", nodeCode, nodeType);
                        isSuccess = false;
                    }

                    if (isEmptyString(nodeOption.get("nodeMent"))) {
                        isSuccess = false;
                        resultMessage += String.format("[%s: %s] 멘트가 설정되지 않았습니다.\n", nodeCode, nodeType);
                    }
                    break;
                case Function:
                case IN:
                case OUT:
                case HORIZONTALCONTAINER:
                case VERTICALCONTAINER:
                    // 기본 세팅 없음
                    break;
                default:
                    throw new CustomBadRequestException("Undefined nodeType: " + nodeType);
            }

        }

        if (!isSuccess) {
            throw new CustomBadRequestException(resultMessage);
        }

    }



    private boolean isEmptyString(Object checkObject) {
        String str = (String) checkObject;
        if (str == null || str.isEmpty()) {
            return  true;
        }
        return false;
    }

    private boolean isNotSettingInteger(Object checkObject){
        Integer i = (Integer) checkObject;
        if (i == null || i == -1) {
            return  true;
        }
        return false;
    }

}

