package com.eactive.eai.transformer;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.Sql;

import com.eactive.eai.transformer.engine.TransformEngine;
import com.eactive.eai.transformer.function.jpa.TransformJPATestConfiguration;
import com.eactive.eai.transformer.message.Message;
import com.eactive.eai.transformer.message.MessageFactory;
import com.eactive.eai.transformer.message.MessageSetDataException;

/**
 * JSONMessage 필드 validation 단위테스트
 *
 * 테스트 대상 레이아웃: EAI_UJSONTYPE_REQ1
 *   - INT_VALUE    : INT,        length=5,  decimal=0
 *   - LONG_VALUE   : LONG,       length=10, decimal=0
 *   - BIG_VALUE    : BIGDECIMAL, length=10, decimal=5  → 정수부 최대 4자리
 *   - BIG15_VALUE  : BIGDECIMAL, length=15, decimal=0  → 정수부 최대 15자리
 *   - STR_VALUE    : STRING,     length=20
 *
 * 검증 시나리오
 *   1-a) 타입 불일치(오류) — 숫자형 필드에 비숫자 문자열 입력 시 setData() 에서 즉시 오류
 *   1-b) 타입 불일치(정상) — 숫자형 필드에 숫자 문자열 입력 시 파싱 가능하면 정상 처리
 *   2-a) Java 타입 범위 초과 — INT/LONG 최대값 초과 시 NumberFormatException (validation 설정 무관)
 *   2-b) BigDecimal 자릿수 초과 (숫자 리터럴) — 정수부/소수부 자릿수 초과 시 즉시 오류
 *   2-c) BigDecimal 자릿수 초과 (문자열 형태) — JSON 문자열로 전달된 경우도 동일하게 검증
 *                           (2-b, 2-c 모두 SystemKeys.TRANSFROMER_VALIDATION=true 필요)
 *   3)   역방향 타입       — 문자열 필드에 JSON 숫자 입력 시 asText()로 자동 변환 → 오류 없음
 */
@DataJpaTest
@ContextConfiguration(classes = { TransformJPATestConfiguration.class })
@Sql({
    "/com/eactive/eai/transformer/init_tseaitr06.sql",
    "/com/eactive/eai/transformer/init_json_validation.sql"
})
@DisplayName("JSONMessage Validation 테스트")
class JSONMessageValidationTest {

    @Autowired
    TransformEngine engine;

    @BeforeEach
    void setUp() {
        try {
            if (!engine.isStarted()) {
                engine.start();
            }
        } catch (Exception e) {
            assumeTrue(false, "TransformEngine 시작 실패: " + e.getMessage());
        }
    }

    @AfterEach
    void tearDown() {
        System.setProperty(SystemKeys.TRANSFROMER_VALIDATION, "false");
        System.setProperty(SystemKeys.TRANSFROMER_SCIENTIFIC_NORMALIZE, "false");
        SystemKeys.reload();
    }

    // ─────────────────────────────────────────────────────────────────
    // 1. 타입 불일치 테스트 — setData() 시점에 오류 발생
    // ─────────────────────────────────────────────────────────────────

    @Test
    @DisplayName("INT 타입 필드에 비숫자 문자열 입력 → NumberFormatException")
    void testIntFieldTypeError() {
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"INT_VALUE\":\"abc\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsCause(ex, NumberFormatException.class),
                "원인이 NumberFormatException이어야 합니다");
    }

    @Test
    @DisplayName("LONG 타입 필드에 비숫자 문자열 입력 → NumberFormatException")
    void testLongFieldTypeError() {
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"LONG_VALUE\":\"not_a_number\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsCause(ex, NumberFormatException.class),
                "원인이 NumberFormatException이어야 합니다");
    }

    @Test
    @DisplayName("BIGDECIMAL 타입 필드에 비숫자 문자열 입력 → NumberFormatException")
    void testBigDecimalFieldTypeError() {
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":\"invalid_decimal\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsCause(ex, NumberFormatException.class),
                "원인이 NumberFormatException이어야 합니다");
    }

    // ─────────────────────────────────────────────────────────────────
    // 1-b. 숫자형 필드에 숫자 문자열 입력 → 정상 처리
    //      JSON 문자열 노드라도 파싱 가능한 숫자면 오류 없이 처리됨
    // ─────────────────────────────────────────────────────────────────

    @Test
    @DisplayName("INT 타입 필드에 숫자 문자열 입력 → 오류 없이 정상 처리")
    void testIntFieldWithNumericString() throws Exception {
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"INT_VALUE\":\"42\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
        msg.setData(data);

        String result = (String) msg.getData();
        assertNotNull(result, "결과값이 null이 아니어야 합니다");
        assertTrue(result.contains("\"INT_VALUE\":42"), "INT_VALUE가 숫자 42로 출력되어야 합니다");
    }

    @Test
    @DisplayName("LONG 타입 필드에 숫자 문자열 입력 → 오류 없이 정상 처리")
    void testLongFieldWithNumericString() throws Exception {
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"LONG_VALUE\":\"9876543210\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
        msg.setData(data);

        String result = (String) msg.getData();
        assertNotNull(result, "결과값이 null이 아니어야 합니다");
        assertTrue(result.contains("\"LONG_VALUE\":9876543210"), "LONG_VALUE가 숫자 9876543210으로 출력되어야 합니다");
    }

    @Test
    @DisplayName("BIGDECIMAL 타입 필드에 숫자 문자열 입력 → 오류 없이 정상 처리")
    void testBigDecimalFieldWithNumericString() throws Exception {
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":\"1234.12345\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
        msg.setData(data);

        String result = (String) msg.getData();
        assertNotNull(result, "결과값이 null이 아니어야 합니다");
        assertTrue(result.contains("\"BIG_VALUE\":1234.12345"), "BIG_VALUE가 숫자 1234.12345로 출력되어야 합니다");
    }

    // ─────────────────────────────────────────────────────────────────
    // 2-a. INT/LONG Java 타입 범위 초과 — validation 설정 무관하게 NumberFormatException
    // ─────────────────────────────────────────────────────────────────

    @Test
    @DisplayName("INT 타입 필드에 Integer 최대값 초과 입력 → NumberFormatException")
    void testIntFieldJavaTypeOverflow() {
        // Integer.MAX_VALUE = 2147483647, 초과값 → new Integer() 파싱 실패
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"INT_VALUE\":\"2147483648\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsCause(ex, NumberFormatException.class),
                "원인이 NumberFormatException이어야 합니다");
    }

    @Test
    @DisplayName("LONG 타입 필드에 Long 최대값 초과 입력 → NumberFormatException")
    void testLongFieldJavaTypeOverflow() {
        // Long.MAX_VALUE = 9223372036854775807, 초과값 → new Long() 파싱 실패
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"LONG_VALUE\":\"9223372036854775808\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsCause(ex, NumberFormatException.class),
                "원인이 NumberFormatException이어야 합니다");
    }

    // ─────────────────────────────────────────────────────────────────
    // 2-b. BigDecimal 자릿수 초과 (JSON 숫자 리터럴) — transformer.validation=true 필요
    // ─────────────────────────────────────────────────────────────────

    @Test
    @DisplayName("BIG_VALUE 정수부 초과(5자리 > 4자리 제한) → MessageSetDataException")
    void testBigDecimalIntegerPartOverflow() {
        enableValidation();

        // 정수부 5자리(12345) → 제한(4자리) 초과
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":12345.12345}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsMessage(ex, "Invalid decimal point format"),
                "'Invalid decimal point format' 메시지가 포함되어야 합니다");
    }

    @Test
    @DisplayName("BIG_VALUE 소수부 초과(6자리 > 5자리 제한) → MessageSetDataException")
    void testBigDecimalFractionalPartOverflow() {
        enableValidation();

        // 소수부 6자리(123456) → 제한(5자리) 초과
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":1234.123456}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsMessage(ex, "Invalid decimal point format"),
                "'Invalid decimal point format' 메시지가 포함되어야 합니다");
    }

    @Test
    @DisplayName("BIG15_VALUE 정수부 초과(16자리 > 15자리 제한) → MessageSetDataException")
    void testBigDecimalIntegerOnlyOverflow() {
        enableValidation();

        // 정수부 16자리 → 제한(15자리) 초과
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG15_VALUE\":1234567890123456}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsMessage(ex, "Invalid decimal point format"),
                "'Invalid decimal point format' 메시지가 포함되어야 합니다");
    }

    @Test
    @DisplayName("BIG_VALUE 정상 범위(정수부 4자리 + 소수부 5자리) → 정상 처리")
    void testBigDecimalWithinBounds() throws Exception {
        enableValidation();

        // 정수부 4자리 + 소수부 5자리 → 정상
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":1234.12345}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
        msg.setData(data);

        String result = (String) msg.getData();
        assertNotNull(result, "결과값이 null이 아니어야 합니다");
        assertTrue(result.contains("BIG_VALUE"), "BIG_VALUE가 결과에 포함되어야 합니다");
    }

    // ─────────────────────────────────────────────────────────────────
    // 2-c. BigDecimal 자릿수 초과 (JSON 문자열 형태) — transformer.validation=true 필요
    // ─────────────────────────────────────────────────────────────────

    @Test
    @DisplayName("BIG_VALUE 정수부 초과 문자열 입력(5자리 > 4자리 제한) → MessageSetDataException")
    void testBigDecimalIntegerPartOverflowFromString() {
        enableValidation();

        // JSON 문자열 "12345.12345" → 정수부 5자리 초과
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":\"12345.12345\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsMessage(ex, "Invalid decimal point format"),
                "'Invalid decimal point format' 메시지가 포함되어야 합니다");
    }

    @Test
    @DisplayName("BIG_VALUE 소수부 초과 문자열 입력(6자리 > 5자리 제한) → MessageSetDataException")
    void testBigDecimalFractionalPartOverflowFromString() {
        enableValidation();

        // JSON 문자열 "1234.123456" → 소수부 6자리 초과
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":\"1234.123456\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsMessage(ex, "Invalid decimal point format"),
                "'Invalid decimal point format' 메시지가 포함되어야 합니다");
    }

    @Test
    @DisplayName("BIG15_VALUE 정수부 초과 문자열 입력(16자리 > 15자리 제한) → MessageSetDataException")
    void testBigDecimalIntegerOnlyOverflowFromString() {
        enableValidation();

        // JSON 문자열 "1234567890123456" → 정수부 16자리 초과
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG15_VALUE\":\"1234567890123456\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsMessage(ex, "Invalid decimal point format"),
                "'Invalid decimal point format' 메시지가 포함되어야 합니다");
    }

    // ─────────────────────────────────────────────────────────────────
    // 3. 역방향 타입 테스트 — 문자열 필드에 숫자 입력 → 정상 처리
    //    JSON 숫자 노드는 node.asText()로 문자열 변환되어 오류 없이 처리됨
    // ─────────────────────────────────────────────────────────────────

    @Test
    @DisplayName("STRING 타입 필드에 JSON 정수 입력 → 오류 없이 정상 처리")
    void testStringFieldWithIntegerValue() throws Exception {
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"STR_VALUE\":123}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
        msg.setData(data);

        String result = (String) msg.getData();
        assertNotNull(result, "결과값이 null이 아니어야 합니다");
        assertTrue(result.contains("STR_VALUE"), "STR_VALUE가 결과에 포함되어야 합니다");
    }

    @Test
    @DisplayName("STRING 타입 필드에 JSON 실수 입력 → 오류 없이 정상 처리")
    void testStringFieldWithDecimalValue() throws Exception {
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"STR_VALUE\":12.34}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
        msg.setData(data);

        String result = (String) msg.getData();
        assertNotNull(result, "결과값이 null이 아니어야 합니다");
        assertTrue(result.contains("STR_VALUE"), "STR_VALUE가 결과에 포함되어야 합니다");
    }

    @Test
    @DisplayName("STRING 타입 필드에 JSON 큰 정수 입력 → 오류 없이 정상 처리")
    void testStringFieldWithLargeIntegerValue() throws Exception {
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"STR_VALUE\":9876543210}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
        msg.setData(data);

        String result = (String) msg.getData();
        assertNotNull(result, "결과값이 null이 아니어야 합니다");
        assertTrue(result.contains("STR_VALUE"), "STR_VALUE가 결과에 포함되어야 합니다");
    }

    // ─────────────────────────────────────────────────────────────────
    // 4. 지수 표현(Scientific Notation) 정규화 테스트
    //    transformer.scientific.normalize=true 일 때만 적용
    //    4-a) 정규화 ON + validation ON
    //    4-b) 정규화 OFF → 기존 동작(false positive / false negative) 확인
    // ─────────────────────────────────────────────────────────────────

    @Test
    @DisplayName("[정규화 ON] 지수형 정수부 초과(1.23e5=123000 > 4자리 제한) → MessageSetDataException")
    void testScientificNotationIntegerOverflow_withNormalize() {
        enableValidation();
        enableScientificNormalize();

        // 1.23e5 = 123000, 정수부 6자리 > 4자리 제한
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":1.23e5}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsMessage(ex, "Invalid decimal point format"),
                "'Invalid decimal point format' 메시지가 포함되어야 합니다");
    }

    @Test
    @DisplayName("[정규화 ON] 지수형 문자열 정수부 초과(\"1.23e5\" > 4자리 제한) → MessageSetDataException")
    void testScientificNotationStringOverflow_withNormalize() {
        enableValidation();
        enableScientificNormalize();

        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":\"1.23e5\"}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        MessageSetDataException ex = assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data)
        );
        assertTrue(containsMessage(ex, "Invalid decimal point format"),
                "'Invalid decimal point format' 메시지가 포함되어야 합니다");
    }

    @Test
    @DisplayName("[정규화 ON] 지수형 BIG15_VALUE 정상 범위(1.23e5=123000 ≤ 15자리) → 정상 처리")
    void testScientificNotationBig15Valid_withNormalize() throws Exception {
        enableValidation();
        enableScientificNormalize();

        // 1.23e5 = 123000, 6자리 ≤ 15자리 제한 → 정상
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG15_VALUE\":1.23e5}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
        msg.setData(data);

        String result = (String) msg.getData();
        assertNotNull(result, "결과값이 null이 아니어야 합니다");
        assertTrue(result.contains("BIG15_VALUE"), "BIG15_VALUE가 결과에 포함되어야 합니다");
    }

    @Test
    @DisplayName("[정규화 OFF] 지수형 BIG15_VALUE 정상값(1.23e5)이 false negative로 거부됨 → MessageSetDataException")
    void testScientificNotationBig15Valid_withoutNormalize() {
        enableValidation();
        disableScientificNormalize();

        // 1.23e5 = 123000, 유효값(6자리 ≤ 15자리)이지만 정규화 없이는 "1.23E+5".split(".")=[2개] → 거부
        String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG15_VALUE\":1.23e5}}";
        Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");

        assertThrows(
                MessageSetDataException.class,
                () -> msg.setData(data),
                "정규화 OFF 시 false negative로 예외가 발생해야 합니다"
        );
    }

    // ─────────────────────────────────────────────────────────────────
    // 유틸 메서드
    // ─────────────────────────────────────────────────────────────────

    private void enableValidation() {
        System.setProperty(SystemKeys.TRANSFROMER_VALIDATION, "true");
        SystemKeys.reload();
    }

    private void enableScientificNormalize() {
        System.setProperty(SystemKeys.TRANSFROMER_SCIENTIFIC_NORMALIZE, "true");
        SystemKeys.reload();
    }

    private void disableScientificNormalize() {
        System.setProperty(SystemKeys.TRANSFROMER_SCIENTIFIC_NORMALIZE, "false");
        SystemKeys.reload();
    }

    private boolean containsCause(Throwable e, Class<?> causeClass) {
        Throwable t = e;
        while (t != null) {
            if (causeClass.isInstance(t)) return true;
            t = t.getCause();
        }
        return false;
    }

    private boolean containsMessage(Throwable e, String keyword) {
        Throwable t = e;
        while (t != null) {
            if (t.getMessage() != null && t.getMessage().contains(keyword)) return true;
            t = t.getCause();
        }
        return false;
    }
}
