package com.eactive.eai.common.security;

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

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

/**
 * ARIACryptoModuleExtension 단위 테스트
 * BouncyCastle ARIA 암복호화 로직을 검증한다.
 */
@TestMethodOrder(MethodOrderer.DisplayName.class)
class ARIACryptoModuleExtensionTest {

    private static final byte[] KEY_128 = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
    private static final byte[] KEY_256 = "0123456789abcdef0123456789abcdef".getBytes(StandardCharsets.UTF_8);
    private static final byte[] IV_16   = "abcdef0123456789".getBytes(StandardCharsets.UTF_8);
    private static final byte[] PLAIN   = "ARIA 암호화 테스트 평문입니다.".getBytes(StandardCharsets.UTF_8);

    // =========================================================================
    // 1. CBC 모드
    // =========================================================================

    @Test
    @DisplayName("1-1. ARIA/CBC/PKCS5Padding 128bit — 암복호화 라운드트립")
    void testCbc128_roundTrip() throws Exception {
        ARIACryptoModuleExtension ext = new ARIACryptoModuleExtension();
        ext.init("ARIA", "CBC", "PKCS5Padding", IV_16, KEY_128, KEY_128);

        byte[] encrypted = ext.encrypt(PLAIN, 0, PLAIN.length);
        byte[] decrypted = ext.decrypt(encrypted, 0, encrypted.length);

        assertNotNull(encrypted);
        assertFalse(Arrays.equals(PLAIN, encrypted), "암호문은 평문과 달라야 한다");
        assertArrayEquals(PLAIN, decrypted, "복호화 결과가 원문과 일치해야 한다");
    }

    @Test
    @DisplayName("1-2. ARIA/CBC/PKCS5Padding 256bit — 암복호화 라운드트립")
    void testCbc256_roundTrip() throws Exception {
        ARIACryptoModuleExtension ext = new ARIACryptoModuleExtension();
        ext.init("ARIA", "CBC", "PKCS5Padding", IV_16, KEY_256, KEY_256);

        byte[] encrypted = ext.encrypt(PLAIN, 0, PLAIN.length);
        byte[] decrypted = ext.decrypt(encrypted, 0, encrypted.length);

        assertArrayEquals(PLAIN, decrypted);
    }

    @Test
    @DisplayName("1-3. ARIA/CBC — 암호화 결과는 블록 크기(16바이트)의 배수")
    void testCbc_ciphertextIsMultipleOfBlockSize() throws Exception {
        ARIACryptoModuleExtension ext = new ARIACryptoModuleExtension();
        ext.init("ARIA", "CBC", "PKCS5Padding", IV_16, KEY_128, KEY_128);

        byte[] encrypted = ext.encrypt(PLAIN, 0, PLAIN.length);

        assertEquals(0, encrypted.length % 16, "ARIA CBC 암호문 길이는 16 바이트 배수여야 한다");
    }

    @Test
    @DisplayName("1-4. ARIA/CBC — 다른 IV로 암호화 시 다른 암호문 생성")
    void testCbc_differentIv_differentCiphertext() throws Exception {
        byte[] iv2 = "9876543210fedcba".getBytes(StandardCharsets.UTF_8);

        ARIACryptoModuleExtension ext1 = new ARIACryptoModuleExtension();
        ext1.init("ARIA", "CBC", "PKCS5Padding", IV_16, KEY_128, KEY_128);

        ARIACryptoModuleExtension ext2 = new ARIACryptoModuleExtension();
        ext2.init("ARIA", "CBC", "PKCS5Padding", iv2, KEY_128, KEY_128);

        byte[] enc1 = ext1.encrypt(PLAIN, 0, PLAIN.length);
        byte[] enc2 = ext2.encrypt(PLAIN, 0, PLAIN.length);

        assertFalse(Arrays.equals(enc1, enc2), "IV가 다르면 암호문도 달라야 한다");
    }

    @Test
    @DisplayName("1-5. ARIA/CBC — 잘못된 키로 복호화 시 예외 발생")
    void testCbc_wrongKeyThrowsException() throws Exception {
        ARIACryptoModuleExtension encExt = new ARIACryptoModuleExtension();
        encExt.init("ARIA", "CBC", "PKCS5Padding", IV_16, KEY_128, KEY_128);
        byte[] encrypted = encExt.encrypt(PLAIN, 0, PLAIN.length);

        byte[] wrongKey = "wrongkey12345678".getBytes(StandardCharsets.UTF_8);
        ARIACryptoModuleExtension decExt = new ARIACryptoModuleExtension();
        decExt.init("ARIA", "CBC", "PKCS5Padding", IV_16, wrongKey, wrongKey);

        assertThrows(Exception.class, () -> decExt.decrypt(encrypted, 0, encrypted.length),
                "잘못된 키로 복호화 시 예외가 발생해야 한다");
    }

    // =========================================================================
    // 2. 암/복호화 키 분리
    // =========================================================================

    @Test
    @DisplayName("2-1. init(alg, mode, pad, encKey, decKey) — IV null 편의 메서드")
    void testInitWithoutIv() throws Exception {
        ARIACryptoModuleExtension ext = new ARIACryptoModuleExtension();
        // IV 없이 초기화 (null → IvParameterSpec도 null → CBC에서 예외)
        assertThrows(Exception.class,
                () -> ext.init("ARIA", "CBC", "PKCS5Padding", KEY_128, KEY_128),
                "IV 없이 CBC 초기화 시 예외가 발생해야 한다");
    }

    // =========================================================================
    // 3. 바이트 배열 오프셋/길이 encrypt/decrypt
    // =========================================================================

    @Test
    @DisplayName("3-1. encrypt(src, sindex, slength, dst, dindex) — 버퍼 직접 지정 라운드트립")
    void testEncryptDecryptWithBuffer() throws Exception {
        ARIACryptoModuleExtension ext = new ARIACryptoModuleExtension();
        ext.init("ARIA", "CBC", "PKCS5Padding", IV_16, KEY_128, KEY_128);

        byte[] dst = new byte[256];
        int encLen = ext.encrypt(PLAIN, 0, PLAIN.length, dst, 0);

        byte[] decDst = new byte[256];
        int decLen = ext.decrypt(dst, 0, encLen, decDst, 0);

        assertArrayEquals(PLAIN, Arrays.copyOf(decDst, decLen));
    }

    // =========================================================================
    // 4. BouncyCastle Provider 자동 등록
    // =========================================================================

    @Test
    @DisplayName("4-1. 동일 평문을 여러 번 암호화 — 각각 새 인스턴스로 독립 처리")
    void testMultipleInstances_independentEncryption() throws Exception {
        ARIACryptoModuleExtension ext1 = new ARIACryptoModuleExtension();
        ext1.init("ARIA", "CBC", "PKCS5Padding", IV_16, KEY_128, KEY_128);

        ARIACryptoModuleExtension ext2 = new ARIACryptoModuleExtension();
        ext2.init("ARIA", "CBC", "PKCS5Padding", IV_16, KEY_128, KEY_128);

        byte[] enc1 = ext1.encrypt(PLAIN, 0, PLAIN.length);
        byte[] enc2 = ext2.encrypt(PLAIN, 0, PLAIN.length);

        // 같은 키/IV → 같은 암호문
        assertArrayEquals(enc1, enc2, "동일 키/IV라면 암호문이 동일해야 한다");

        // 각자 복호화
        assertArrayEquals(PLAIN, ext1.decrypt(enc1, 0, enc1.length));
        assertArrayEquals(PLAIN, ext2.decrypt(enc2, 0, enc2.length));
    }
}
