package com.eactive.eai.custom.message;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;

import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.inbound.processor.ProcessVO;
import com.eactive.eai.message.StandardItem;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.StandardType;
import com.eactive.eai.message.manager.StandardMessageManager;
import com.eactive.eai.message.service.DefaultStandardMessageCoordinator;
import com.eactive.eai.message.service.InterfaceMapper;
import com.ext.eai.common.stdmessage.STDMessageKeys;

/**
 * DJBank(제주은행) 차세대 표준전문 Coordinator
 *
 * 표준전문 구조 변경에 따른 각 처리 단계 후처리 정의:
 *  - HEAD 평면 구조, GUID 40자, guid_prgs_no 별도 필드
 *  - MSG 영역: msg_dvcd(NM/EM) + MAIN_MSG + MSG_LIST
 *  - 처리결과구분코드: S(성공) / F(오류)
 *  - 전문종료부: @JJ
 */
public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordinator {

    private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);

    private static final DateTimeFormatter FMT_DATE        = DateTimeFormatter.ofPattern("yyyyMMdd");
    private static final DateTimeFormatter FMT_TIME_MILLIS = DateTimeFormatter.ofPattern("HHmmssSSS");

    // ${callProp.key} 또는 ${callProp.key:기본값} 패턴
    private static final Pattern CALL_PROP_PATTERN =
            Pattern.compile("\\$\\{callProp\\.([^}:]+)(?::([^}]*))?\\}");

    // HEAD 필드 경로
    public static final String HEAD_FRST_MESG_DMAN_DT   = "HEAD.frst_mesg_dman_dt";
    public static final String HEAD_FRST_MESG_DMAN_TIME  = "HEAD.frst_mesg_dman_time";
    public static final String HEAD_FRST_TRNM_IPAD       = "HEAD.frst_trnm_ipad";
    public static final String HEAD_FRST_TRNM_IPV4_ADDR  = "HEAD.frst_trnm_ipv4_addr";
    public static final String HEAD_FRST_TRNM_MAC        = "HEAD.frst_trnm_mac";
    public static final String HEAD_SYS_ENV_DVCD         = "HEAD.sys_env_dvcd";
    public static final String HEAD_TRNM_SYS_DVCD        = "HEAD.trnm_sys_dvcd";
    public static final String HEAD_MESG_RSPN_DT         = "HEAD.mesg_rspn_dt";
    public static final String HEAD_MESG_RSPN_TIME       = "HEAD.mesg_rspn_time";

    // EZDATA 영역 경로
    public static final String EZDATA_ELFN_BNKN_DVCD     = "EZDATA.elfn_bnkn_dvcd";
    public static final String EZDATA_SVR_TX_SEQNO        = "EZDATA.svr_tx_seqno";
    public static final String EZDATA_ELFN_MAC            = "EZDATA.elfn_mac";
    public static final String EZDATA_PC_OFCL_IPAD        = "EZDATA.pc_ofcl_ipad";
    public static final String EZDATA_PC_PRVAT_IPAD       = "EZDATA.pc_prvat_ipad";
    public static final String EZDATA_ELFN_SPR_FILD       = "EZDATA.elfn_spr_fild";

    // MSG 영역 경로
    public static final String MSG_DVCD                  = "MSG.msg_dvcd";
    public static final String MSG_SCOP_LEN              = "MSG.msg_scop_len";
    public static final String MSG_MAIN_MSG              = "MSG.MAIN_MSG";
    public static final String MSG_OUTP_ATRB_CD          = "MSG.MAIN_MSG.outp_atrb_cd";
    public static final String MSG_OUTP_MSG_CD           = "MSG.MAIN_MSG.outp_msg_cd";
    public static final String MSG_OUTP_MSG_CTNT         = "MSG.MAIN_MSG.outp_msg_ctnt";
    public static final String MSG_OUTP_MSG_DESC         = "MSG.MAIN_MSG.outp_msg_desc";
    public static final String MSG_LIST_ROWCNT           = "MSG.MAIN_MSG.msg_list_rowcnt";
    public static final String MSG_LIST                  = "MSG.MSG_LIST";

    // DATA 영역 경로
    public static final String DATA_SCOP_LEN             = "DATA.data_scop_len";
    
    private String[] netInfo;

    @Override
    public void coordinateAfterParsing(StandardMessage standardMessage, Object paramObject, Properties prop) {
    	// TODO 표준전문을 수신할 경우, 전문의 interface id로 가져온 eaiSvcCd가 다른 값으로 치환되어야 할 수도 있다.
    	// 수신한 if_id를 기준으로 api gw의 eaiSvcCd를 매핑하는 프러퍼티가 있어야 한다.
    	// PropGroup : DJB_IN_STDHEAD_IFID_MAPPING, DJB_IN_HTTHEAD_STDHEAD_MAPPING
    	// standardMessage.setEaiSvcCd(mappedIfId);
    }

    @Override
    public void coordinateAfterCreateMessageByRule(StandardMessage standardMessage, Object paramObject, Properties prop) {
        StandardMessageManager manager = StandardMessageManager.getInstance();
        InterfaceMapper mapper = manager.getMapper();

        // GUID 생성 (40자) 및 설정
        String guid = GUIDGeneratorDJB.getGUID("OPA");
        mapper.setGuid(standardMessage, guid);

        // GUID 진행번호 초기화 (최초=001)
        mapper.setGuidSeq(standardMessage, "001");

        // 원거래GUID = GUID 동일값 설정
        mapper.setOrgGuid(standardMessage, guid);

        // 최초전문요청일자/시각 설정
        LocalDateTime now = LocalDateTime.now();
        standardMessage.setData(HEAD_FRST_MESG_DMAN_DT,   now.format(FMT_DATE));
        standardMessage.setData(HEAD_FRST_MESG_DMAN_TIME,  now.format(FMT_TIME_MILLIS));

        // 전송시스템구분코드 설정
        standardMessage.setData(HEAD_TRNM_SYS_DVCD, "EAI");

        // 시스템환경구분코드 설정 (D/T/P)
        EAIServerManager eaiServer = EAIServerManager.getInstance();
        String sysEnvDvcd = getSysEnvDvcd(eaiServer);
        if(StringUtils.equals("P", sysEnvDvcd))
        	standardMessage.setData(HEAD_SYS_ENV_DVCD, "P");

        // 최초전송 IP / IPv4 / MAC 수집 및 설정
        String[] netInfo = collectNetworkInfo();
        String ipv4 = netInfo[0];
        String mac  = netInfo[1];
        standardMessage.setData(HEAD_FRST_TRNM_IPAD,      ipv4);
        standardMessage.setData(HEAD_FRST_TRNM_IPV4_ADDR, ipv4);
        standardMessage.setData(HEAD_FRST_TRNM_MAC,       mac);

        // ERP 채널 전자금융공통영역(EZDATA) 설정 — CSV refPath/refValue 조건 평가
        StandardItem ezdataItem = standardMessage.findItem("EZDATA");
        if (ezdataItem != null && evaluateBlockCondition(standardMessage, ezdataItem)) {
            setEzdataFields(standardMessage, ezdataItem, ipv4, mac);
        }

        // refValue="${callProp.키}" 필드에 Properties 값 주입
        resolveCallPropRefValues(standardMessage, prop);
    }

    @Override
    public void coordinateBeforeStandardMessageSend(StandardMessage standardMessage, String msgType, String charset) {
        makeupDataScopLen(standardMessage, charset);
    }

    @Override
    public void coordinateBeforeResponse(StandardMessage standardMessage, ProcessVO processVO, Properties prop, String charset) {
        StandardMessageManager manager = StandardMessageManager.getInstance();
        InterfaceMapper mapper = manager.getMapper();

        // GUID 진행번호 +1
        //mapper.nextGuidSeq(standardMessage);

        // 요청응답구분 = 응답(R)
        mapper.setSendRecvDivision(standardMessage, STDMessageKeys.SEND_RECV_CD_RECV);

        // 응답일자/시각 설정
        LocalDateTime now = LocalDateTime.now();
        standardMessage.setData(HEAD_MESG_RSPN_DT,   now.format(FMT_DATE));
        standardMessage.setData(HEAD_MESG_RSPN_TIME,  now.format(FMT_TIME_MILLIS));

        
        StandardItem msgListRowCnt = standardMessage.findItem(StandardMessageCoordinatorDJB.MSG_LIST_ROWCNT);
		if (msgListRowCnt == null || "0".equals(msgListRowCnt.getValue()) || "".equals(msgListRowCnt.getValue())) {
			StandardItem msgPart = standardMessage.findItem("MSG");
			msgPart.getChilds().remove("MSG_LIST");
		}
        
        makeupDataScopLen(standardMessage, charset);
    }

    @Override
    public boolean coordinateSetStandardMessageError(StandardMessage standardMessage, InterfaceMapper mapper,
            String errCode, String errMsg) {

        // 처리결과 = 오류(F)
        mapper.setResponseType(standardMessage, STDMessageKeys.RESPONSE_TYPE_CODE_E);
        // 요청응답구분 = 응답(R)
        mapper.setSendRecvDivision(standardMessage, STDMessageKeys.SEND_RECV_CD_RECV);

        // 에러코드/메시지 변환
        String siteErrCode = StringUtils.defaultString(MessageUtil.getTobeCode(errCode));
        String siteErrMsg  = StringUtils.defaultString(MessageUtil.getTobeMessage(errCode));
        String siteErrDesc  = StringUtils.defaultString(MessageUtil.getTobeDesc(errCode));

        // MSG 영역 활성화
        StandardItem msgItem = standardMessage.findItem("MSG");
        if (msgItem != null) {
            msgItem.setHidden(false);
            msgItem.setSize(1);
        }
        StandardItem mainMsgItem = standardMessage.findItem(MSG_MAIN_MSG);
        if (mainMsgItem != null) {
            mainMsgItem.setHidden(false);
            mainMsgItem.setSize(1);
        }

        // msg_dvcd = EM (에러메시지)
        standardMessage.setData(MSG_DVCD, "EM");

        // 출력속성코드 = 1(팝업)
        standardMessage.setData(MSG_OUTP_ATRB_CD, "1");

        // 대표 에러코드/메시지
        standardMessage.setData(MSG_OUTP_MSG_CD,   siteErrCode);
        standardMessage.setData(MSG_OUTP_MSG_CTNT, siteErrMsg);

        // MSG_LIST 1건 구성
        standardMessage.setData(MSG_LIST_ROWCNT, "1");
        StandardItem msgList = standardMessage.findItem(MSG_LIST);
        if (msgList != null) {
            msgList.setSize(1);
            LinkedHashMap<String, StandardItem> row = msgList.getArrayChilds(0, true);
            if (row != null) {
                if (row.containsKey("outp_msg_cd"))   row.get("outp_msg_cd").setValue(siteErrCode);
                if (row.containsKey("outp_msg_ctnt")) row.get("outp_msg_ctnt").setValue(siteErrMsg);
                if (row.containsKey("outp_msg_desc")) row.get("outp_msg_desc").setValue(siteErrDesc);
                if (row.containsKey("err_occu_loct")) row.get("err_occu_loct").setValue(errCode);
            }
        }

        // mapper 에러코드 설정
        mapper.setErrorCode(standardMessage, siteErrCode);
        mapper.setErrorMsg(standardMessage, siteErrMsg);

        // msg_scop_len 계산
        makeMsgScopLen(standardMessage);

        // BIZ_DATA는 null 처리 (false 반환)
        return false;
    }

    @Override
    public void coordinateAfterRecvNonStdSyncResponse(StandardMessage responseMessage) {
        // 처리결과 = 성공(S)
        StandardMessageManager manager = StandardMessageManager.getInstance();
        InterfaceMapper mapper = manager.getMapper();
        mapper.setResponseType(responseMessage, STDMessageKeys.RESPONSE_TYPE_CODE_N);

        // 요청응답구분 = 응답(R) — dman_rspn_dvcd 기본값이 S(요청)이므로 반드시 설정
        mapper.setSendRecvDivision(responseMessage, STDMessageKeys.SEND_RECV_CD_RECV);

        // MSG 영역 활성화
        StandardItem msgItem = responseMessage.findItem("MSG");
        if (msgItem != null) {
            msgItem.setHidden(false);
            msgItem.setSize(1);
        }
        StandardItem mainMsgItem = responseMessage.findItem(MSG_MAIN_MSG);
        if (mainMsgItem != null) {
            mainMsgItem.setHidden(false);
            mainMsgItem.setSize(1);
        }

        // msg_dvcd=NM, outp_atrb_cd=0, outp_msg_cd, outp_msg_ctnt, msg_list_rowcnt=0
        // → 모두 CSV 기본값 사용

        makeMsgScopLen(responseMessage);
    }

    // ----------------------------------------------------------------
    // private helpers
    // ----------------------------------------------------------------

    private void resolveCallPropRefValues(StandardMessage standardMessage, Properties prop) {
        if (prop == null) return;
        resolveCallPropInChilds(standardMessage.getChilds(), prop);
    }

    private void resolveCallPropInChilds(LinkedHashMap<String, StandardItem> childs, Properties prop) {
        if (childs == null) return;
        for (StandardItem item : childs.values()) {
            int type = item.getType();
            if (type == StandardType.FIELD) {
                String refValue = item.getRefValue();
                if (refValue == null) continue;
                Matcher m = CALL_PROP_PATTERN.matcher(refValue);
                if (m.matches()) {
                    String key        = m.group(1);
                    String defaultVal = m.group(2) != null ? m.group(2) : "";
                    String resolved   = resolveCallPropValue(prop, key);
                    item.setValue(resolved != null ? resolved : defaultVal);
                }
            } else if (type == StandardType.GROUP) {
                resolveCallPropInChilds(item.getChilds(), prop);
            }
            // GRID, FARRAY, BIZDATA: 배열/업무데이터 영역은 건너뜀
        }
    }

    /**
     * Properties/Map 중첩 구조에서 점(.) 구분 keyPath로 값을 탐색한다.
     * Properties는 Map을 구현하므로 Map<?,?> 단일 메서드로 통일한다.
     * - "aaa.bbb" : map.get("aaa")가 Map(Properties 포함)이면 그 안에서 "bbb" 재귀 조회
     * - 중간 값이 Map이 아니면 전체 keyPath로 폴백 조회
     * - 값을 찾지 못하면 null 반환
     */
    private String resolveCallPropValue(Properties props, String keyPath) {
        return resolveNestedValue(props, keyPath);
    }

    @SuppressWarnings("unchecked")
    private String resolveNestedValue(Map<?, ?> map, String keyPath) {
        int dotIdx = keyPath.indexOf('.');
        if (dotIdx < 0) {
            Object val = map.get(keyPath);
            if (val == null)           return null;
            if (val instanceof String) return (String) val;
            return val.toString();
        }

        String first = keyPath.substring(0, dotIdx);
        String rest  = keyPath.substring(dotIdx + 1);
        Object val = map.get(first);
        if (val instanceof Map) {
            return resolveNestedValue((Map<?, ?>) val, rest);
        }

        // 폴백: 전체 keyPath를 단일 키로 조회
        Object direct = map.get(keyPath);
        if (direct == null)           return null;
        if (direct instanceof String) return (String) direct;
        return direct.toString();
    }

    private String getSysEnvDvcd(EAIServerManager server) {
        if (server.isPEAIServer()) return "P"; // 운영
        if (server.isSEAIServer()) return "T"; // 검증/테스트
        return "D";                            // 개발
    }

    private String[] collectNetworkInfo() {
    	if(this.netInfo != null)
    		return this.netInfo;
    	
        String ipv4 = "";
        String mac  = "";
        try {
            InetAddress localAddr = InetAddress.getLocalHost();
            ipv4 = localAddr.getHostAddress();
            NetworkInterface ni = NetworkInterface.getByInetAddress(localAddr);
            if (ni != null) {
                byte[] macBytes = ni.getHardwareAddress();
                if (macBytes != null) {
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < macBytes.length; i++) {
                        if (i > 0) sb.append(":");
                        sb.append(String.format("%02X", macBytes[i]));
                    }
                    mac = sb.toString();
                }
            }
        } catch (Exception e) {
            if (logger.isWarn()) logger.warn("Failed to collect network info", e);
        }
        this.netInfo = new String[]{ipv4, mac};
        return this.netInfo;
    }

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("ddHHmmss");
    private static final Random RANDOM = new Random();

    public static String generate() {
        String datePart = LocalDateTime.now().format(FORMATTER);
        String randomPart = String.format("%02d", RANDOM.nextInt(100));
        return datePart + randomPart;
    }
    
    private void setEzdataFields(StandardMessage standardMessage, StandardItem ezdataItem, String ipv4, String mac) {
        ezdataItem.setHidden(false);
        ezdataItem.setSize(1);
//        standardMessage.setData(EZDATA_ELFN_BNKN_DVCD, "10");
        standardMessage.setData(EZDATA_SVR_TX_SEQNO,    generate());
        standardMessage.setData(EZDATA_ELFN_MAC,        mac);
        standardMessage.setData(EZDATA_PC_OFCL_IPAD,   ipv4);
        standardMessage.setData(EZDATA_PC_PRVAT_IPAD,  ipv4);
        standardMessage.setData(EZDATA_ELFN_SPR_FILD,  ezdataItem.getRefValue());
    }

    /**
     * StandardItem의 refPath/refValue를 현재 메시지 필드값과 비교해 조건 충족 여부를 반환한다.
     * refValue 앞에 '!'를 붙이면 NOT 조건, '|'로 구분하면 OR 조건이다.
     * refPath 또는 refValue가 없으면 조건 없음(true)으로 처리한다.
     */
    private boolean evaluateBlockCondition(StandardMessage standardMessage, StandardItem item) {
        String refPath  = item.getRefPath();
        String refValue = item.getRefValue();
        if (StringUtils.isBlank(refPath) || StringUtils.isBlank(refValue)) {
            return true;
        }
        String actualValue = StringUtils.trimToEmpty(standardMessage.findItemValue(refPath));
        boolean negate = refValue.startsWith("!");
        String compareValue = negate ? refValue.substring(1) : refValue;
        boolean matched = matchesAny(compareValue, actualValue);
        return negate ? !matched : matched;
    }

    private boolean matchesAny(String refValue, String actualValue) {
        for (String v : refValue.split("\\|")) {
            if (v.equals(actualValue)) return true;
        }
        return false;
    }

    private void makeupDataScopLen(StandardMessage standardMessage, String charset) {
        try {
            int dataLen = standardMessage.getBizDataBytes(charset).length;
            String lenStr = StringUtils.leftPad(String.valueOf(dataLen), 8, '0');
            standardMessage.setData(DATA_SCOP_LEN, lenStr);
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate data_scop_len", e);
        }
    }

    private void makeMsgScopLen(StandardMessage standardMessage) {
        try {
            StandardItem mainMsg = standardMessage.findItem(MSG_MAIN_MSG);
            if (mainMsg == null) return;
            int len = standardMessage.getBytesDataLengthForPath(MSG_MAIN_MSG);
            standardMessage.setData(MSG_SCOP_LEN, StringUtils.leftPad(String.valueOf(len), 8, '0'));
        } catch (Exception e) {
            if (logger.isWarn()) logger.warn("Failed to calculate msg_scop_len", e);
        }
    }
}
