package com.eactive.eai.inbound.processor;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

import org.junit.jupiter.api.BeforeAll;
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.adapter.Keys;
import com.eactive.eai.common.monitor.EAIServiceMonitor;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.restrict.RestrictInfoVO;
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.service.InterfaceMapper;
import com.ext.eai.common.stdmessage.STDMessageKeys;

/**
 * RequestProcessor 단위테스트 — StandardMessageManager/Coordinator 연동 검증
 *
 * 전략:
 *  - DB/Spring 없는 환경에서 Mockito + 리플렉션으로 ApplicationContextProvider 주입
 *  - RequestProcessor 클래스 로드 전(static 블록 실행 전)에 mock ApplicationContext를 주입
 *  - private 메서드(getInboundErrorResponseForStandard, getInboundErrorResponse)는
 *    리플렉션으로 직접 호출하여 Coordinator 연동 결과를 검증
 */
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class RequestProcessorStdMsgTest {

    private StandardMessageManager stdMsgManager;

    @BeforeAll
    void setUpAll() throws Exception {
        // ① PropManager mock — STDMessageErrorKeys 정적 초기화용 (def 반환)
        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 mock — static 블록의 getLocalServerName/getGroupInstId 대응
        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 mock — MessageUtil.getTobeCode() 사용
        EAIServiceMonitor mockMonitor = Mockito.mock(EAIServiceMonitor.class);
        Mockito.when(mockMonitor.isTimeOutCodes(anyString())).thenReturn(false);
        Mockito.when(mockMonitor.isBizErrorCodes(anyString())).thenReturn(false);

        // ④ ApplicationContext mock
        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);

        // ⑤ ApplicationContextProvider.context(static 필드)에 mock 주입
        Field ctxField = ApplicationContextProvider.class.getDeclaredField("context");
        ctxField.setAccessible(true);
        ctxField.set(null, mockCtx);

        // ⑥ StandardMessageManager 초기화
        System.setProperty(StandardMessageManager.STANDARD_MESSAGE_CONFIG,
                "src/test/resources/standard-message-config-test.properties");
        stdMsgManager = StandardMessageManager.getInstance();
        stdMsgManager.init();

        // ⑦ RequestProcessor 클래스 강제 로드 — mock 주입 완료 후 static 블록 안전 실행
        Class.forName("com.eactive.eai.inbound.processor.RequestProcessor");
    }

    // ================================================================
    // getInboundErrorResponseForStandard (private) — Coordinator 연동 검증
    //
    // 이 메서드 내부 흐름:
    //   mapper.nextGuidSeq(msg)
    //   mapper.setSendRecvDivision(msg, R)
    //   standardManager.getMessageCoordinator().coordinateSetStandardMessageError(...)
    //   standardMessage.getDataString(messageType, charset)
    // ================================================================

    @Test
    void getInboundErrorResponseForStandard_procs_rslt_dvcd_F_설정() throws Exception {
        StandardMessage msg = stdMsgManager.getStandardMessage();
        InterfaceMapper mapper = stdMsgManager.getMapper();

        callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT");

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

    @Test
    void getInboundErrorResponseForStandard_dman_rspn_dvcd_R_설정() throws Exception {
        StandardMessage msg = stdMsgManager.getStandardMessage();
        InterfaceMapper mapper = stdMsgManager.getMapper();

        callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT");

        assertEquals(STDMessageKeys.SEND_RECV_CD_RECV, msg.findItemValue("HEAD.dman_rspn_dvcd"),
                "오류 응답 생성 시 dman_rspn_dvcd는 R이어야 함");
    }

    @Test
    void getInboundErrorResponseForStandard_MSG_msg_dvcd_EM_설정() throws Exception {
        StandardMessage msg = stdMsgManager.getStandardMessage();
        InterfaceMapper mapper = stdMsgManager.getMapper();

        callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT");

        assertEquals("EM", msg.findItemValue("MSG.msg_dvcd"),
                "오류 응답 생성 시 msg_dvcd는 EM이어야 함");
    }

    @Test
    void getInboundErrorResponseForStandard_outp_atrb_cd_1_설정() throws Exception {
        StandardMessage msg = stdMsgManager.getStandardMessage();
        InterfaceMapper mapper = stdMsgManager.getMapper();

        callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT");

        assertEquals("1", msg.findItemValue("MSG.MAIN_MSG.outp_atrb_cd"),
                "오류 시 outp_atrb_cd는 1(팝업)이어야 함");
    }

    @Test
    void getInboundErrorResponseForStandard_outp_msg_cd_설정됨() throws Exception {
        StandardMessage msg = stdMsgManager.getStandardMessage();
        InterfaceMapper mapper = stdMsgManager.getMapper();

        callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT");

        String errCode = msg.findItemValue("MSG.MAIN_MSG.outp_msg_cd");
        assertNotNull(errCode, "outp_msg_cd가 null");
        assertFalse(errCode.trim().isEmpty(), "outp_msg_cd가 공백");
    }

    @Test
    void getInboundErrorResponseForStandard_예외없이_완료() {
        assertDoesNotThrow(() -> {
            StandardMessage msg = stdMsgManager.getStandardMessage();
            InterfaceMapper mapper = stdMsgManager.getMapper();
            callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT");
        }, "getInboundErrorResponseForStandard 실행 중 예외 발생");
    }

    // ================================================================
    // getInboundErrorResponse (private) — IF_STANDARD 분기 검증
    //
    // IF_STANDARD 분기 흐름:
    //   coordinateBeforeInboundErrorResponse(standardMessage)  ← StandardMessageCoordinatorDJB 호출
    //   getInboundErrorResponseForStandard(...)                ← 위 검증된 메서드
    // ================================================================

    @Test
    void getInboundErrorResponse_IF_STANDARD_procs_rslt_dvcd_F_설정() throws Exception {
        StandardMessage msg = stdMsgManager.getStandardMessage();
        InterfaceMapper mapper = stdMsgManager.getMapper();

        Properties prop = new Properties();
        prop.setProperty(Processor.STD_MESSAGE, Keys.IF_STANDARD);
        prop.setProperty(Processor.MESSAGE_TYPE, "FLAT");

        callGetInboundErrorResponse(prop, msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr");

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

    @Test
    void getInboundErrorResponse_IF_STANDARD_msg_dvcd_EM_설정() throws Exception {
        StandardMessage msg = stdMsgManager.getStandardMessage();
        InterfaceMapper mapper = stdMsgManager.getMapper();

        Properties prop = new Properties();
        prop.setProperty(Processor.STD_MESSAGE, Keys.IF_STANDARD);
        prop.setProperty(Processor.MESSAGE_TYPE, "FLAT");

        callGetInboundErrorResponse(prop, msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr");

        assertEquals("EM", msg.findItemValue("MSG.msg_dvcd"),
                "IF_STANDARD 오류응답 후 msg_dvcd는 EM이어야 함");
    }

    @Test
    void getInboundErrorResponse_standardMessage_null_비표준_예외없음() {
        Properties prop = new Properties();
        prop.setProperty(Processor.STD_MESSAGE, "X");  // 비표준 → ExceptionHandler.handle(errMsg)
        prop.setProperty(Processor.MESSAGE_TYPE, "FLAT");

        assertDoesNotThrow(() ->
            callGetInboundErrorResponse(prop, null, null, "RECEAICMM999", "테스트 오류", "euc-kr"),
            "standardMessage null 비표준 케이스에서 예외 발생"
        );
    }

    // ================================================================
    // checkRestrictTime (public)
    // ================================================================

    @Test
    void checkRestrictTime_start_end_미설정_항상_true() throws Exception {
        RequestProcessor rp = new RequestProcessor();
        RestrictInfoVO rVo = new RestrictInfoVO();
        // params 길이 < 6 → start/end 모두 null → true 반환
        rVo.setEAICtrlDsticCtnt("a|b|c|d|e");

        assertTrue(rp.checkRestrictTime(rVo), "start/end 미설정 시 true 기대");
    }

    @Test
    void checkRestrictTime_start_0000_end_0000_항상_true() throws Exception {
        RequestProcessor rp = new RequestProcessor();
        RestrictInfoVO rVo = new RestrictInfoVO();
        // end="0000" 이면 코드상 종료시간 체크 블록 건너뜀 → start 0000 이후면 true
        rVo.setEAICtrlDsticCtnt("a|b|c|d|e|0000|0000");

        assertTrue(rp.checkRestrictTime(rVo), "start=0000/end=0000 설정 시 true 기대");
    }

    @Test
    void checkRestrictTime_end_0001_현재시간_이후_false() throws Exception {
        RequestProcessor rp = new RequestProcessor();
        RestrictInfoVO rVo = new RestrictInfoVO();
        // 종료시간이 00:01이면 00:01 이후(= 거의 항상)는 false 반환
        // 단, 자정(00:00) 실행 시에는 통과 — 허용 오차
        rVo.setEAICtrlDsticCtnt("a|b|c|d|e|0000|0001");

        java.util.Calendar cal = java.util.Calendar.getInstance();
        int hour   = cal.get(java.util.Calendar.HOUR_OF_DAY);
        int minute = cal.get(java.util.Calendar.MINUTE);

        boolean result = rp.checkRestrictTime(rVo);
        // 00:01 이후(99% 이상의 시간)에는 false여야 함
        if (hour > 0 || minute >= 1) {
            assertFalse(result, "종료시간(00:01) 이후에는 false여야 함");
        }
        // 정확히 00:00에 실행되면 검증 생략 (허용)
    }

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

    private Object callGetInboundErrorResponseForStandard(
            StandardMessage msg, InterfaceMapper mapper,
            String errCode, String errMsg, String charset, String messageType) throws Exception {
        Method method = RequestProcessor.class.getDeclaredMethod(
                "getInboundErrorResponseForStandard",
                StandardMessage.class, InterfaceMapper.class,
                String.class, String.class, String.class, String.class);
        method.setAccessible(true);
        try {
            return method.invoke(new RequestProcessor(), msg, mapper, errCode, errMsg, charset, messageType);
        } catch (InvocationTargetException e) {
            throw (Exception) e.getCause();
        }
    }

    private Object callGetInboundErrorResponse(
            Properties prop, StandardMessage msg, InterfaceMapper mapper,
            String errCode, String errMsg, String charset) throws Exception {
        Method method = RequestProcessor.class.getDeclaredMethod(
                "getInboundErrorResponse",
                Properties.class, StandardMessage.class, InterfaceMapper.class,
                String.class, String.class, String.class);
        method.setAccessible(true);
        try {
            return method.invoke(new RequestProcessor(), prop, msg, mapper, errCode, errMsg, charset);
        } catch (InvocationTargetException e) {
            throw (Exception) e.getCause();
        }
    }
}
