package com.eactive.eai.custom.message;

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

import java.lang.reflect.Field;

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.StandardItem;
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 표준전문 EZDATA(전자금융공통영역) JSON 처리 흐름 통합 테스트
 *
 * 검증 시나리오:
 *  1. EZDATA 포함 요청 JSON 파싱 — corp_tlwn_virt_brcd=ERP 조건 활성
 *  2. EZDATA 핵심 필드 값 검증
 *  3. EZDATA 조건 미충족(비ERP) 시 JSON 직렬화 제외 검증
 *  4. EZDATA 포함 JSON 왕복(parse → serialize) 일관성
 *  5. coordinateAfterCreateMessageByRule 후 EZDATA 보존
 *  6. coordinateBeforeResponse 후 EZDATA 포함 직렬화
 *  7. 오류 응답 생성 시 EZDATA 보존
 */
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class StandardMessageEzdataJsonFlowTest {

    private static final String TEST_GUID =
            "20260420" + "120000" + "000" + "EAI" + "01" + "123456789012" + "000001";

    private static final String TEST_IF_ID = "IF_DJBTEST_EZ001";
    private static final String TEST_TX_ID = "TXTEST000002";

    /**
     * ERP 채널 요청 JSON — corp_tlwn_virt_brcd=ERP → EZDATA 블록 활성화
     */
    private static final String ERP_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\","
        +   "\"corp_tlwn_virt_brcd\":\"ERP\","
        +   "\"trnm_sys_dvcd\":\"OPA\","
        +   "\"frst_trnm_sys_dvcd\":\"OPA\","
        +   "\"sys_env_dvcd\":\"D\","
        +   "\"frst_mesg_dman_dt\":\"20260420\","
        +   "\"frst_mesg_dman_time\":\"120000000\""
        + "},"
        + "\"EZDATA\":{"
        +   "\"login_mhod_dvcd\":\"1\","
        +   "\"elfn_bnkn_dvcd\":\"01\","
        +   "\"usr_no\":\"USR0000000000001\","
        +   "\"rgsr_id\":\"REG_ID_000000001\","
        +   "\"secu_media_dvcd\":\"00\","
        +   "\"secu_media_sta_dvcd\":\"00\","
        +   "\"brll_srcd_yn\":\"N\","
        +   "\"next_data_exis_yn\":\"N\","
        +   "\"page_no\":\"00001\","
        +   "\"page_size\":\"00020\","
        +   "\"dup_tx_yn\":\"N\""
        + "},"
        + "\"DATA\":{"
        +   "\"data_dvcd\":\"IO\","
        +   "\"data_scop_len\":\"37\","
        +   "\"BIZ_DATA\":{\"acct_no\":\"987654321\",\"amt\":\"500000\"}"
        + "}"
        + "}";

    /**
     * 비ERP 요청 JSON — corp_tlwn_virt_brcd 미설정 → EZDATA 블록 비활성
     */
    private static final String NON_ERP_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\":\"20\","
        +   "\"BIZ_DATA\":{\"acct_no\":\"111222333\"}"
        + "}"
        + "}";

    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. EZDATA 포함 요청 JSON 파싱
    // ================================================================

    @Test
    void ERP요청_파싱_EZDATA_블록_활성() throws Exception {
        StandardMessage msg = parseErpReq();

        assertNotNull(msg.findItem("EZDATA"), "EZDATA 블록이 null");
        assertFalse(msg.findItem("EZDATA").isHidden(), "EZDATA 블록이 hidden 상태");
    }

    @Test
    void ERP요청_파싱_HEAD_corp_tlwn_virt_brcd_ERP() throws Exception {
        StandardMessage msg = parseErpReq();

        assertEquals("ERP", msg.findItemValue("HEAD.corp_tlwn_virt_brcd"),
                "corp_tlwn_virt_brcd가 ERP가 아님");
    }

    // ================================================================
    // 2. EZDATA 핵심 필드 값 검증
    // ================================================================

    @Test
    void EZDATA_login_mhod_dvcd_파싱() throws Exception {
        StandardMessage msg = parseErpReq();

        assertEquals("1", msg.findItemValue("EZDATA.login_mhod_dvcd"),
                "EZDATA.login_mhod_dvcd 불일치");
    }

    @Test
    void EZDATA_elfn_bnkn_dvcd_파싱() throws Exception {
        StandardMessage msg = parseErpReq();

        assertEquals("01", msg.findItemValue("EZDATA.elfn_bnkn_dvcd"),
                "EZDATA.elfn_bnkn_dvcd 불일치");
    }

    @Test
    void EZDATA_usr_no_파싱() throws Exception {
        StandardMessage msg = parseErpReq();

        assertEquals("USR0000000000001", msg.findItemValue("EZDATA.usr_no").trim(),
                "EZDATA.usr_no 불일치");
    }

    @Test
    void EZDATA_rgsr_id_파싱() throws Exception {
        StandardMessage msg = parseErpReq();

        assertEquals("REG_ID_000000001", msg.findItemValue("EZDATA.rgsr_id").trim(),
                "EZDATA.rgsr_id 불일치");
    }

    @Test
    void EZDATA_secu_media_dvcd_파싱() throws Exception {
        StandardMessage msg = parseErpReq();

        assertEquals("00", msg.findItemValue("EZDATA.secu_media_dvcd"),
                "EZDATA.secu_media_dvcd 불일치");
    }

    @Test
    void EZDATA_dup_tx_yn_파싱() throws Exception {
        StandardMessage msg = parseErpReq();

        assertEquals("N", msg.findItemValue("EZDATA.dup_tx_yn"),
                "EZDATA.dup_tx_yn 불일치");
    }

    // ================================================================
    // 3. EZDATA 조건 미충족(비ERP) — EZDATA 블록 직렬화 제외 검증
    //    size=0 + refPath/refValue 조건 미충족 → isHidden=true → JSON 제외
    // ================================================================

    @Test
    void 비ERP요청_파싱_EZDATA_size_0() throws Exception {
        StandardMessage msg = parseNonErpReq();

        StandardItem ezdataItem = msg.findItem("EZDATA");
        assertTrue(ezdataItem == null || ezdataItem.getSize() == 0,
                "비ERP 요청에서 EZDATA 블록 size는 0이어야 함");
    }

    @Test
    void 비ERP요청_파싱_JSON_직렬화_EZDATA_제외() throws Exception {
        StandardMessage msg = parseNonErpReq();

        String json = msg.toJson(false, false);
        JsonNode root = jacksonMapper.readTree(json);
        assertFalse(root.has("EZDATA"), "비ERP 요청 직렬화 JSON에 EZDATA 블록이 없어야 함");
    }

    @Test
    void 비ERP요청_파싱_BIZ_DATA_정상파싱() throws Exception {
        StandardMessage msg = parseNonErpReq();

        String bizData = msg.findItemValue("DATA.BIZ_DATA");
        assertNotNull(bizData, "비ERP 요청의 BIZ_DATA가 null");
        JsonNode node = jacksonMapper.readTree(bizData);
        assertEquals("111222333", node.path("acct_no").asText());
    }

    // ================================================================
    // 4. EZDATA 포함 JSON 왕복(parse → serialize) 일관성
    // ================================================================

    @Test
    void JSON_왕복_EZDATA_포함_직렬화() throws Exception {
        StandardMessage msg = parseErpReq();

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        assertTrue(root.has("EZDATA"), "JSON 왕복 후 EZDATA 블록 소실");
    }

    @Test
    void JSON_왕복_EZDATA_usr_no_보존() throws Exception {
        StandardMessage msg = parseErpReq();

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        assertEquals("USR0000000000001",
                root.path("EZDATA").path("usr_no").asText().trim(),
                "JSON 왕복 후 EZDATA.usr_no 불일치");
    }

    @Test
    void JSON_왕복_HEAD_guid_보존() throws Exception {
        StandardMessage msg = parseErpReq();

        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 = parseErpReq();

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        JsonNode bizData = root.path("DATA").get("BIZ_DATA");
        assertNotNull(bizData, "JSON 왕복 후 BIZ_DATA 소실");
        assertEquals("987654321", bizData.path("acct_no").asText(),
                "JSON 왕복 후 BIZ_DATA.acct_no 불일치");
    }

    // ================================================================
    // 5. coordinateAfterCreateMessageByRule 후 EZDATA 보존
    // ================================================================

    @Test
    void 표준수신_coordinateAfterCreateMessageByRule_EZDATA_보존() throws Exception {
        StandardMessage msg = parseErpReq();
        coordinator.coordinateAfterCreateMessageByRule(msg, null, null);

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        assertTrue(root.has("EZDATA"), "coordinateAfterCreateMessageByRule 후 EZDATA 소실");
    }

    @Test
    void 표준수신_coordinateAfterCreateMessageByRule_EZDATA_usr_no_보존() throws Exception {
        StandardMessage msg = parseErpReq();
        coordinator.coordinateAfterCreateMessageByRule(msg, null, null);

        assertEquals("USR0000000000001", msg.findItemValue("EZDATA.usr_no").trim(),
                "coordinateAfterCreateMessageByRule 후 EZDATA.usr_no 변경됨");
    }

    // ================================================================
    // 6. coordinateBeforeResponse 후 EZDATA 포함 직렬화
    // ================================================================

    @Test
    void 표준응답생성_coordinateBeforeResponse_EZDATA_포함() throws Exception {
        StandardMessage msg = parseErpReq();
        coordinator.coordinateBeforeResponse(msg, null, null, "utf-8");

        String json = msg.getDataString(MessageType.JSON, "utf-8");
        JsonNode root = jacksonMapper.readTree(json);
        assertTrue(root.has("EZDATA"), "coordinateBeforeResponse 후 EZDATA 소실");
    }

    @Test
    void 표준응답생성_coordinateBeforeResponse_HEAD_mesg_rspn_dt_설정() throws Exception {
        StandardMessage msg = parseErpReq();
        coordinator.coordinateBeforeResponse(msg, null, null, "utf-8");

        String dt = msg.findItemValue("HEAD.mesg_rspn_dt");
        assertNotNull(dt, "mesg_rspn_dt가 null");
        assertTrue(dt.matches("\\d{8}"), "mesg_rspn_dt 형식 오류: " + dt);
    }

    // ================================================================
    // 7. 오류 응답 생성 시 EZDATA 보존
    // ================================================================

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

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

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

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

    @Test
    void 오류응답_EZDATA_MSG_블록_포함() throws Exception {
        StandardMessage msg = parseErpReq();
        coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류");

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

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

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

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