package com.eactive.eai.agent.encryption;

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

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * EncryptionManager 단위 테스트 — DAMO 솔루션 모드.
 *
 * damo-manager.jar 가 DAMO 라이브러리(com.penta.scpdb.ScpDbAgent) 없이 로드되면
 * 자동으로 FAKE MODE 로 동작한다:
 *   encrypt(plain) → Base64 인코딩
 *   decrypt(b64)   → Base64 디코딩
 * 따라서 encrypt → decrypt 라운드트립이 완전히 검증 가능하다.
 *
 * EncryptionManager 생성자가 private 이므로 리플렉션으로 인스턴스를 생성하고
 * 필드를 직접 주입한다.
 */
class EncryptionManagerTest {

    private EncryptionManager manager;

    @BeforeEach
    void setUp() throws Exception {
        Constructor<EncryptionManager> ctor = EncryptionManager.class.getDeclaredConstructor();
        ctor.setAccessible(true);
        manager = ctor.newInstance();
    }

    // -------------------------------------------------------------------------
    // 헬퍼
    // -------------------------------------------------------------------------

    private void setField(String name, Object value) throws Exception {
        Field f = EncryptionManager.class.getDeclaredField(name);
        f.setAccessible(true);
        f.set(manager, value);
    }

    private void enableDamo() throws Exception {
        setField("encryptYN", "Y");
        setField("dbEncryptSolutionName", "DAMO");
    }

    private void disableEncrypt() throws Exception {
        setField("encryptYN", "N");
        setField("dbEncryptSolutionName", "DAMO");
    }

    // =========================================================================
    // 1. DAMO 모드 — encrypt + decrypt 라운드트립
    // =========================================================================

    @Test
    @DisplayName("1-1. DAMO 암호화 후 복호화 시 원문 복원 — 한글")
    void testRoundTrip_korean() throws Exception {
        enableDamo();
        String plain = "홍길동";
        String encrypted = manager.encryptDBData(plain);
        assertNotEquals(plain, encrypted, "암호화 결과가 원문과 달라야 한다");
        assertEquals(plain, manager.decryptDBData(encrypted));
    }

    @Test
    @DisplayName("1-2. DAMO 암호화 후 복호화 시 원문 복원 — 영문/숫자 혼합")
    void testRoundTrip_alphanumeric() throws Exception {
        enableDamo();
        String plain = "test@example.com";
        String encrypted = manager.encryptDBData(plain);
        assertNotEquals(plain, encrypted);
        assertEquals(plain, manager.decryptDBData(encrypted));
    }

    @Test
    @DisplayName("1-3. DAMO 암호화 후 복호화 시 원문 복원 — 주민등록번호 형식")
    void testRoundTrip_idNumber() throws Exception {
        enableDamo();
        String plain = "900101-1234567";
        String encrypted = manager.encryptDBData(plain);
        // 숫자와 '-' 만으로 이루어진 원문은 암호화되어 Base64 형식으로 변환된다
        assertNotEquals(plain, encrypted);
        assertEquals(plain, manager.decryptDBData(encrypted));
    }

    @Test
    @DisplayName("1-4. DAMO 암호화 후 복호화 시 원문 복원 — 다양한 입력 일괄 검증")
    void testRoundTrip_multipleValues() throws Exception {
        enableDamo();
        String[] inputs = {
            "홍길동",
            "test@example.com",
            "900101-1234567",
            "ABC테스트123",
            "special chars: !@#$%",
            "긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열긴문자열"
        };
        for (String input : inputs) {
            String encrypted = manager.encryptDBData(input);
            String decrypted = manager.decryptDBData(encrypted);
            assertEquals(input, decrypted, "라운드트립 불일치: [" + input + "]");
        }
    }

    // =========================================================================
    // 2. 암호화 비활성(encryptYN=N)
    // =========================================================================

    @Test
    @DisplayName("2-1. encryptYN=N 이면 encryptDBData 가 원문 그대로 반환")
    void testEncrypt_disabled_returnsPlain() throws Exception {
        disableEncrypt();
        String plain = "홍길동";
        assertEquals(plain, manager.encryptDBData(plain));
    }

    @Test
    @DisplayName("2-2. encryptYN=N 상태의 원문은 isEncrypted=false 이므로 decryptDBData 도 원문 반환")
    void testDecrypt_plainTextNotBase64_returnsOriginal() throws Exception {
        disableEncrypt();
        String plain = "홍길동";
        assertEquals(plain, manager.decryptDBData(plain));
    }

    // =========================================================================
    // 3. 특수 입력 처리
    // =========================================================================

    @Test
    @DisplayName("3-1. decryptDBData — null 입력 시 null 반환")
    void testDecrypt_null() throws Exception {
        enableDamo();
        assertNull(manager.decryptDBData(null));
    }

    @Test
    @DisplayName("3-2. decryptDBData — 빈 문자열 입력 시 빈 문자열 반환")
    void testDecrypt_empty() throws Exception {
        enableDamo();
        assertEquals("", manager.decryptDBData(""));
    }

    @Test
    @DisplayName("3-3. decryptDBData — 숫자로만 구성된 값은 복호화하지 않고 원문 반환 (연락처)")
    void testDecrypt_numericOnly_returnsOriginal() throws Exception {
        enableDamo();
        assertEquals("01012345678", manager.decryptDBData("01012345678"));
        assertEquals("0101234-5678", manager.decryptDBData("0101234-5678"));
    }

    @Test
    @DisplayName("3-4. decryptDBData — Base64 형식이 아닌 문자열은 원문 반환")
    void testDecrypt_nonBase64_returnsOriginal() throws Exception {
        enableDamo();
        String plain = "이름: 홍길동";  // 공백 포함, Base64 문자셋 아님
        assertEquals(plain, manager.decryptDBData(plain));
    }

    // =========================================================================
    // 4. isEncrypt() / getEncryptYN() 상태 확인
    // =========================================================================

    @Test
    @DisplayName("4-1. encryptYN=Y 이면 isEncrypt()=true")
    void testIsEncrypt_true() throws Exception {
        setField("encryptYN", "Y");
        assertTrue(manager.isEncrypt());
    }

    @Test
    @DisplayName("4-2. encryptYN=N 이면 isEncrypt()=false")
    void testIsEncrypt_false() throws Exception {
        setField("encryptYN", "N");
        assertFalse(manager.isEncrypt());
    }

    @Test
    @DisplayName("4-3. getDBEncryptSolutionName() 반환값 확인")
    void testGetDBEncryptSolutionName() throws Exception {
        setField("dbEncryptSolutionName", "DAMO");
        assertEquals("DAMO", manager.getDBEncryptSolutionName());
    }
}
