package com.eactive.eai.custom.message;

import static org.junit.jupiter.api.Assertions.*;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationContext;

import com.eactive.eai.common.monitor.EAIServiceMonitor;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.manager.StandardMessageManager;
import com.eactive.eai.message.parser.JsonReader;
import com.eactive.eai.message.service.InterfaceMapper;
import com.eactive.eai.common.message.MessageType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import static org.mockito.ArgumentMatchers.anyString;

/**
 * DJBank 표준전문 JSON 처리 흐름 통합 테스트
 *
 * 검증 시나리오:
 *  1. JSON → StandardMessage 파싱 (표준 요청 수신)
 *  2. JSON → StandardMessage 파싱 (표준 응답 수신 — MSG 포함)
 *  3. 표준 메시지 요청 수신 처리 (coordinateAfterCreateMessageByRule)
 *  4. 표준 응답 생성 (coordinateBeforeResponse → JSON 직렬화)
 *  5. 비표준 수신 → 정상 응답 표준 생성 (coordinateAfterRecvNonStdSyncResponse → JSON)
 *  6. 오류 응답 표준 생성 (coordinateSetStandardMessageError → JSON)
 *  7. JSON 왕복(parse → serialize) 일관성
 */
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class StandardMessageJsonFlowTest {

    // ----------------------------------------------------------------
    // 테스트용 상수 (GUID 40자 고정)
    // ----------------------------------------------------------------
    private static final String TEST_GUID =
            "20260420" + "120000" + "000" + "EAI" + "01" + "123456789012" + "000001";
    // 8+6+3+3+2+12+6 = 40자

    private static final String TEST_IF_ID = "IF_DJBTEST_001";
    private static final String TEST_TX_ID = "TXTEST000001";

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

    /**
     * 표준 요청 JSON (dman_rspn_dvcd=S → MSG 블록 미포함)
     * BIZ_DATA는 JSON 오브젝트 — 파싱 후 JSON 문자열로 저장됨
     */
    private static final String REQ_JSON =
        "{"
        + "\"HEAD\":{"
        +   "\"nxgn_stnd_idfr\":\"JERA\","
        +   "\"guid\":\"" + TEST_GUID + "\","
        +   "\"guid_prgs_no\":\"1\","
        +   "\"stnd_mesg_ver\":\"R10\","
        +   "\"ortr_guid\":\"" + TEST_GUID + "\","
        +   "\"dman_rspn_dvcd\":\"S\","
        +   "\"if_id\":\"" + TEST_IF_ID + "\","
        +   "\"procs_rslt_dvcd\":\"S\","
        +   "\"tx_id\":\"" + TEST_TX_ID + "\","
        +   "\"chnl_tycd\":\"EAI\","
        +   "\"hmab_dvcd\":\"1\","
        +   "\"trnm_sys_dvcd\":\"OPA\","
        +   "\"frst_trnm_sys_dvcd\":\"OPA\","
        +   "\"sys_env_dvcd\":\"D\","
        +   "\"frst_mesg_dman_dt\":\"20260420\","
        +   "\"frst_mesg_dman_time\":\"120000000\""
        + "},"
        + "\"DATA\":{"
        +   "\"data_dvcd\":\"IO\","
        +   "\"data_scop_len\":\"37\","
        +   "\"BIZ_DATA\":{\"acct_no\":\"123456789\",\"amt\":\"100000\"}"
        + "}"
        + "}";

    /**
     * 표준 응답 JSON (dman_rspn_dvcd=R → MSG 블록 포함, 정상)
     */
    private static final String RSP_JSON_OK =
        "{"
        + "\"HEAD\":{"
        +   "\"nxgn_stnd_idfr\":\"JERA\","
        +   "\"guid\":\"" + TEST_GUID + "\","
        +   "\"guid_prgs_no\":\"2\","
        +   "\"stnd_mesg_ver\":\"R10\","
        +   "\"ortr_guid\":\"" + TEST_GUID + "\","
        +   "\"dman_rspn_dvcd\":\"R\","
        +   "\"if_id\":\"" + TEST_IF_ID + "\","
        +   "\"procs_rslt_dvcd\":\"S\","
        +   "\"tx_id\":\"" + TEST_TX_ID + "\","
        +   "\"chnl_tycd\":\"EAI\","
        +   "\"hmab_dvcd\":\"1\","
        +   "\"trnm_sys_dvcd\":\"EAI\","
        +   "\"sys_env_dvcd\":\"D\","
        +   "\"frst_mesg_dman_dt\":\"20260420\","
        +   "\"frst_mesg_dman_time\":\"120000000\","
        +   "\"mesg_rspn_dt\":\"20260420\","
        +   "\"mesg_rspn_time\":\"120100000\""
        + "},"
        + "\"MSG\":{"
        +   "\"msg_dvcd\":\"NM\","
        +   "\"msg_scop_len\":\"0\","
        +   "\"MAIN_MSG\":{"
        +     "\"outp_atrb_cd\":\"0\","
        +     "\"outp_msg_cd\":\"000000000000\","
        +     "\"outp_msg_ctnt\":\"정상처리\","
        +     "\"msg_list_rowcnt\":\"1\""
        +   "}"
        + "},"
        + "\"DATA\":{"
        +   "\"data_dvcd\":\"IO\","
        +   "\"data_scop_len\":\"37\","
        +   "\"BIZ_DATA\":{\"acct_no\":\"123456789\",\"bal\":\"900000\"}"
        + "}"
        + "}";

    private StandardMessageManager manager;
    private InterfaceMapper mapper;
    private JsonReader jsonReader;
    private StandardMessageCoordinatorDJB coordinator;
    private ObjectMapper jacksonMapper;

    @BeforeAll
    void setUpAll() throws Exception {
        PropManager mockPropManager = Mockito.mock(PropManager.class);
        Mockito.when(mockPropManager.getProperty(anyString(), anyString(), anyString()))
                .thenAnswer(new Answer<String>() {
                    public String answer(InvocationOnMock inv) { return (String) inv.getArguments()[2]; }
                });

        EAIServerManager mockServer = Mockito.mock(EAIServerManager.class);
        Mockito.when(mockServer.isPEAIServer()).thenReturn(false);
        Mockito.when(mockServer.isSEAIServer()).thenReturn(false);
        Mockito.when(mockServer.isMCI()).thenReturn(false);
        Mockito.when(mockServer.getLocalServerName()).thenReturn("TEST_SERVER");
        Mockito.when(mockServer.getGroupInstId()).thenReturn("TEST_INST");

        EAIServiceMonitor mockMonitor = Mockito.mock(EAIServiceMonitor.class);
        Mockito.when(mockMonitor.isTimeOutCodes(anyString())).thenReturn(false);
        Mockito.when(mockMonitor.isBizErrorCodes(anyString())).thenReturn(false);

        ApplicationContext mockCtx = Mockito.mock(ApplicationContext.class);
        Mockito.when(mockCtx.getBean(EAIServerManager.class)).thenReturn(mockServer);
        Mockito.when(mockCtx.getBean(PropManager.class)).thenReturn(mockPropManager);
        Mockito.when(mockCtx.getBean(EAIServiceMonitor.class)).thenReturn(mockMonitor);

        Field ctxField = ApplicationContextProvider.class.getDeclaredField("context");
        ctxField.setAccessible(true);
        ctxField.set(null, mockCtx);

        System.setProperty(StandardMessageManager.STANDARD_MESSAGE_CONFIG,
                "src/test/resources/standard-message-config-test.properties");
        manager = StandardMessageManager.getInstance();
        manager.init();
    }

    @BeforeEach
    void setUp() {
        mapper      = manager.getMapper();
        jsonReader  = (JsonReader) manager.getReader("JSON");
        coordinator = new StandardMessageCoordinatorDJB();
        jacksonMapper = new ObjectMapper();
    }

    // ================================================================
    // 1. 표준 요청 JSON 파싱
    // ================================================================

    @Test
    void 요청JSON_파싱_HEAD_guid_40자_정확() throws Exception {
        StandardMessage msg = parseReq();

        String guid = mapper.getGuid(msg);
        assertEquals(TEST_GUID, guid, "파싱된 guid가 입력 JSON과 다름");
        assertEquals(GUIDGeneratorDJB.GUID_LENGTH, guid.length(), "GUID 길이가 40자가 아님");
    }

    @Test
    void 요청JSON_파싱_HEAD_if_id_매핑() throws Exception {
        StandardMessage msg = parseReq();

        assertEquals(TEST_IF_ID, msg.findItemValue("HEAD.if_id").trim());
    }

    @Test
    void 요청JSON_파싱_HEAD_dman_rspn_dvcd_S() throws Exception {
        StandardMessage msg = parseReq();

        assertEquals("S", msg.findItemValue("HEAD.dman_rspn_dvcd"),
                "요청 방향은 dman_rspn_dvcd = S 여야 함");
    }

    @Test
    void 요청JSON_파싱_DATA_BIZ_DATA_JSON_오브젝트_저장() throws Exception {
        StandardMessage msg = parseReq();

        String bizData = msg.findItemValue("DATA.BIZ_DATA");
        assertNotNull(bizData, "BIZ_DATA가 null");
        assertFalse(bizData.trim().isEmpty(), "BIZ_DATA가 공백");
        // JSON 오브젝트로 파싱 가능한지 검증
        JsonNode node = jacksonMapper.readTree(bizData);
        assertEquals("123456789", node.path("acct_no").asText(), "BIZ_DATA.acct_no 불일치");
    }

    @Test
    void 요청JSON_파싱_MSG_블록_사이즈_0_숨김() throws Exception {
        StandardMessage msg = parseReq();

        // dman_rspn_dvcd=S → MSG 블록은 조건 미충족 → size=0(숨김)
        // JSON 직렬화 결과에 MSG 키가 없어야 함
        String json = msg.toJson();
        JsonNode root = jacksonMapper.readTree(json);
        assertFalse(root.has("MSG"), "요청 전문 JSON에 MSG 블록이 포함되면 안 됨");
    }

    // ================================================================
    // 2. 표준 응답 JSON 파싱 (MSG 포함)
    // ================================================================

    @Test
    void 응답JSON_파싱_MSG_msg_dvcd_NM() throws Exception {
        StandardMessage msg = parseRsp();

        assertEquals("NM", msg.findItemValue("MSG.msg_dvcd"),
                "정상 응답의 msg_dvcd는 NM이어야 함");
    }

    @Test
    void 응답JSON_파싱_MSG_MAIN_MSG_outp_msg_ctnt_정상처리() throws Exception {
        StandardMessage msg = parseRsp();

        assertEquals("정상처리", msg.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"),
                "정상 응답의 outp_msg_ctnt는 '정상처리'여야 함");
    }

    @Test
    void 응답JSON_파싱_HEAD_procs_rslt_dvcd_S() throws Exception {
        StandardMessage msg = parseRsp();

        assertEquals("S", msg.findItemValue("HEAD.procs_rslt_dvcd"),
                "정상 응답의 procs_rslt_dvcd는 S여야 함");
    }

    // ================================================================
    // 3. 표준 메시지 수신 처리 — coordinateAfterCreateMessageByRule
    // ================================================================

    @Test
    void 표준수신_coordinateAfterCreateMessageByRule_새GUID_40자_재설정() throws Exception {
        StandardMessage msg = parseReq();
        coordinator.coordinateAfterCreateMessageByRule(msg, null, null);

        String newGuid = mapper.getGuid(msg);
        assertNotNull(newGuid, "coordinateAfterCreateMessageByRule 후 GUID null");
        assertEquals(GUIDGeneratorDJB.GUID_LENGTH, newGuid.length(), "재설정된 GUID가 40자가 아님");
        // Coordinator는 새 GUID를 생성하므로 원본과 달라야 함
        assertNotEquals(TEST_GUID, newGuid, "Coordinator가 기존 GUID를 그대로 사용함");
    }

    @Test
    void 표준수신_coordinateAfterCreateMessageByRule_frst_mesg_dman_dt_오늘날짜() throws Exception {
        StandardMessage msg = parseReq();
        coordinator.coordinateAfterCreateMessageByRule(msg, null, null);

        String today = LocalDate.now().format(FMT_DATE);
        assertEquals(today, msg.findItemValue("HEAD.frst_mesg_dman_dt"),
                "frst_mesg_dman_dt는 오늘 날짜여야 함");
    }

    @Test
    void 표준수신_coordinateAfterCreateMessageByRule_trnm_sys_dvcd_EAI() throws Exception {
        StandardMessage msg = parseReq();
        coordinator.coordinateAfterCreateMessageByRule(msg, null, null);

        assertEquals("EAI", msg.findItemValue("HEAD.trnm_sys_dvcd"),
                "coordinateAfterCreateMessageByRule 후 trnm_sys_dvcd는 EAI여야 함");
    }

    // ================================================================
    // 4. 표준 응답 생성 — coordinateBeforeResponse + JSON 직렬화
    // ================================================================

    @Test
    void 표준응답생성_coordinateBeforeResponse_mesg_rspn_dt_오늘날짜() throws Exception {
        StandardMessage msg = parseReq();
        coordinator.coordinateBeforeResponse(msg, null, null, "utf-8");

        String today = LocalDate.now().format(FMT_DATE);
        assertEquals(today, msg.findItemValue("HEAD.mesg_rspn_dt"),
                "mesg_rspn_dt는 오늘 날짜여야 함");
    }

    @Test
    void 표준응답생성_coordinateBeforeResponse_mesg_rspn_time_9자리() throws Exception {
        StandardMessage msg = parseReq();
        coordinator.coordinateBeforeResponse(msg, null, null, "utf-8");

        String time = msg.findItemValue("HEAD.mesg_rspn_time");
        assertNotNull(time, "mesg_rspn_time이 null");
        assertEquals(9, time.length(), "mesg_rspn_time은 HHmmssSSS(9자)여야 함: " + time);
        assertTrue(time.matches("\\d{9}"), "mesg_rspn_time 형식 오류");
    }

    @Test
    void 표준응답생성_JSON_직렬화_HEAD_포함() throws Exception {
        StandardMessage msg = parseReq();
        coordinator.coordinateBeforeResponse(msg, null, null, "utf-8");

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        assertNotNull(json, "직렬화 JSON이 null");
        JsonNode root = jacksonMapper.readTree(json);
        assertTrue(root.has("HEAD"), "직렬화 JSON에 HEAD 블록 없음");
    }

    @Test
    void 표준응답생성_JSON_직렬화_guid_값_보존() throws Exception {
        StandardMessage msg = parseReq();
        // Coordinator 호출 없이 파싱된 상태 그대로 직렬화
        String json = msg.getDataString(MessageType.JSON, "utf-8");

        JsonNode root = jacksonMapper.readTree(json);
        assertEquals(TEST_GUID, root.path("HEAD").path("guid").asText(),
                "직렬화 JSON의 guid가 원본과 다름");
    }

    @Test
    void 표준응답생성_JSON_직렬화_BIZ_DATA_보존() throws Exception {
        StandardMessage msg = parseReq();
        coordinator.coordinateBeforeResponse(msg, null, null, "utf-8");

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        // BIZ_DATA는 JSON 문자열 또는 JSON 오브젝트로 들어갈 수 있음
        assertNotNull(root.path("DATA").get("BIZ_DATA"),
                "직렬화 JSON에 DATA.BIZ_DATA 없음");
    }

    // ================================================================
    // 5. 비표준 수신 → 표준 정상 응답 생성
    //    (외부 시스템에서 온 비표준 응답을 표준 성공 응답으로 변환)
    // ================================================================

    @Test
    void 비표준수신_coordinateAfterRecvNonStdSyncResponse_procs_rslt_dvcd_S() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        coordinator.coordinateAfterRecvNonStdSyncResponse(msg);

        assertEquals("S", msg.findItemValue("HEAD.procs_rslt_dvcd"),
                "비표준 정상수신 후 procs_rslt_dvcd는 S여야 함");
    }

    @Test
    void 비표준수신_coordinateAfterRecvNonStdSyncResponse_MSG_msg_dvcd_NM() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        coordinator.coordinateAfterRecvNonStdSyncResponse(msg);

        assertEquals("NM", msg.findItemValue("MSG.msg_dvcd"),
                "비표준 정상수신 후 msg_dvcd는 NM이어야 함");
    }

    @Test
    void 비표준수신_정상응답_JSON_직렬화_NM_포함() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        coordinator.coordinateAfterRecvNonStdSyncResponse(msg);

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        assertTrue(root.has("MSG"), "정상응답 JSON에 MSG 블록 없음");
        assertEquals("NM", root.path("MSG").path("msg_dvcd").asText(),
                "정상응답 JSON의 msg_dvcd가 NM이 아님");
    }

    @Test
    void 비표준수신_정상응답_JSON_직렬화_outp_msg_ctnt_설정됨() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        coordinator.coordinateAfterRecvNonStdSyncResponse(msg);

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        String ctnt = root.path("MSG").path("MAIN_MSG").path("outp_msg_ctnt").asText();
        assertFalse(ctnt.isEmpty(), "정상응답 JSON의 outp_msg_ctnt가 설정되어야 함");
    }

    // ================================================================
    // 6. 오류 응답 표준 생성 — coordinateSetStandardMessageError + JSON 직렬화
    // ================================================================

    @Test
    void 오류응답_coordinateSetStandardMessageError_procs_rslt_dvcd_F() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류");

        assertEquals("F", msg.findItemValue("HEAD.procs_rslt_dvcd"),
                "오류 응답의 procs_rslt_dvcd는 F여야 함");
    }

    @Test
    void 오류응답_JSON_직렬화_MSG_블록_포함() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류");

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        assertTrue(root.has("MSG"), "오류응답 JSON에 MSG 블록이 없음");
    }

    @Test
    void 오류응답_JSON_직렬화_msg_dvcd_EM() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류");

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        assertEquals("EM", root.path("MSG").path("msg_dvcd").asText(),
                "오류응답 JSON의 msg_dvcd가 EM이 아님");
    }

    @Test
    void 오류응답_JSON_직렬화_outp_atrb_cd_1_팝업() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류");

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        assertEquals("1",
                root.path("MSG").path("MAIN_MSG").path("outp_atrb_cd").asText(),
                "오류응답 JSON의 outp_atrb_cd가 1(팝업)이 아님");
    }

    @Test
    void 오류응답_JSON_직렬화_procs_rslt_dvcd_F_HEAD에_포함() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류");

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        assertEquals("F", root.path("HEAD").path("procs_rslt_dvcd").asText(),
                "오류응답 JSON의 HEAD.procs_rslt_dvcd가 F가 아님");
    }

    // ================================================================
    // 7. JSON 왕복 일관성 (parse → serialize)
    // ================================================================

    @Test
    void JSON_왕복_파싱후_직렬화_guid_일치() throws Exception {
        StandardMessage msg = parseReq();

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);

        assertEquals(TEST_GUID, root.path("HEAD").path("guid").asText(),
                "JSON 왕복 후 guid 불일치");
    }

    @Test
    void JSON_왕복_파싱후_직렬화_if_id_일치() throws Exception {
        StandardMessage msg = parseReq();

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);

        assertEquals(TEST_IF_ID, root.path("HEAD").path("if_id").asText().trim(),
                "JSON 왕복 후 if_id 불일치");
    }

    @Test
    void JSON_왕복_응답JSON_파싱후_직렬화_MSG_보존() throws Exception {
        StandardMessage msg = parseRsp();

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);

        assertTrue(root.has("MSG"), "응답 JSON 왕복 후 MSG 블록 소실");
        assertEquals("NM", root.path("MSG").path("msg_dvcd").asText(),
                "응답 JSON 왕복 후 msg_dvcd 불일치");
    }

    // ================================================================
    // helper
    // ================================================================

    private StandardMessage parseReq() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        jsonReader.parse(msg, REQ_JSON);
        return msg;
    }

    private StandardMessage parseRsp() throws Exception {
        StandardMessage msg = manager.getStandardMessage();
        jsonReader.parse(msg, RSP_JSON_OK);
        return msg;
    }
}
