package com.eactive.eai.manage.tools;

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

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

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

/**
 * ToolsService 단위 테스트.
 *
 * <p>DAMO 관련 테스트는 damo-manager.jar가 네이티브 DAMO 라이브러리 없이 로드되는 FAKE MODE
 * (encrypt → Base64 인코딩, decrypt → Base64 디코딩)에서 동작한다는 전제(EncryptionManagerTest 참고)로,
 * 실행 환경과 무관하게 라운드트립이 성립함을 검증한다.
 */
class ToolsServiceTest {

    private final ToolsService service = new ToolsService();

    // =========================================================================
    // 1. base64
    // =========================================================================

    @Test
    @DisplayName("1-1. base64Encode/Decode — charset 생략 시 UTF-8 기준 라운드트립 (한글)")
    void testBase64_defaultCharset_roundTrip_korean() {
        String plain = "테스트 평문";

        String encoded = service.base64Encode(plain, null);
        assertNotEquals(plain, encoded);

        String decoded = service.base64Decode(encoded, null);
        assertEquals(plain, decoded);
    }

    @Test
    @DisplayName("1-2. base64Encode — 알려진 UTF-8 값 검증")
    void testBase64Encode_knownValue() {
        assertEquals("aGVsbG8=", service.base64Encode("hello", null));
    }

    @Test
    @DisplayName("1-3. base64Decode — 알려진 값 검증")
    void testBase64Decode_knownValue() {
        assertEquals("hello", service.base64Decode("aGVsbG8=", null));
    }

    @Test
    @DisplayName("1-4. base64Encode/Decode — 명시적 charset(EUC-KR) 라운드트립")
    void testBase64_explicitCharset_roundTrip() {
        String plain = "한글테스트";
        String encoded = service.base64Encode(plain, "EUC-KR");
        String decoded = service.base64Decode(encoded, "EUC-KR");
        assertEquals(plain, decoded);
    }

    @Test
    @DisplayName("1-5. base64Decode — 잘못된 Base64 입력은 예외 발생")
    void testBase64Decode_invalid_throwsException() {
        assertThrows(IllegalArgumentException.class, () -> service.base64Decode("not-valid-base64!!", null));
    }

    // =========================================================================
    // 2. hex
    // =========================================================================

    @Test
    @DisplayName("2-1. hexEncode — 알려진 UTF-8 값 검증 (대문자 Hex)")
    void testHexEncode_knownValue() {
        assertEquals("68656C6C6F", service.hexEncode("hello", null));
    }

    @Test
    @DisplayName("2-2. hexEncode/Decode — charset 생략 시 UTF-8 기준 라운드트립 (한글)")
    void testHex_defaultCharset_roundTrip_korean() {
        String plain = "테스트 평문";

        String encoded = service.hexEncode(plain, null);
        assertNotEquals(plain, encoded);

        String decoded = service.hexDecode(encoded, null);
        assertEquals(plain, decoded);
    }

    @Test
    @DisplayName("2-3. hexDecode — 소문자 Hex 입력도 정상 디코딩")
    void testHexDecode_lowerCaseHex() {
        assertEquals("hello", service.hexDecode("68656c6c6f", null));
    }

    @Test
    @DisplayName("2-4. hexDecode — 앞뒤 공백은 trim 후 디코딩")
    void testHexDecode_trimsWhitespace() {
        assertEquals("hello", service.hexDecode("  68656C6C6F  ", null));
    }

    @Test
    @DisplayName("2-5. hexDecode — 홀수 길이 Hex는 예외 발생")
    void testHexDecode_oddLength_throwsException() {
        assertThrows(IllegalArgumentException.class, () -> service.hexDecode("ABC", null));
    }

    @Test
    @DisplayName("2-6. hexDecode — Hex가 아닌 문자 포함 시 예외 발생")
    void testHexDecode_invalidChars_throwsException() {
        assertThrows(IllegalArgumentException.class, () -> service.hexDecode("ZZZZ", null));
    }

    // =========================================================================
    // 3. damo
    // =========================================================================

    @Test
    @DisplayName("3-1. damoEncrypt/Decrypt — 라운드트립 (한글)")
    void testDamo_roundTrip_korean() {
        // DamoManager의 실제 동작(FAKE MODE로 Base64 변환 / 네이티브 라이브러리 미초기화 시 원문 그대로
        // bypass)은 실행 환경에 따라 달라질 수 있어 encrypt 결과가 원문과 달라야 한다는 보장은 없다.
        // 라운드트립(암호화→복호화 시 원문 복원)만 검증한다.
        String plain = "홍길동";
        String encrypted = service.damoEncrypt(plain);
        assertEquals(plain, service.damoDecrypt(encrypted));
    }

    @Test
    @DisplayName("3-2. damoEncrypt/Decrypt — 라운드트립 (영문/숫자 혼합)")
    void testDamo_roundTrip_alphanumeric() {
        String plain = "test@example.com";
        String encrypted = service.damoEncrypt(plain);
        assertEquals(plain, service.damoDecrypt(encrypted));
    }

    @Test
    @DisplayName("3-3. damoEncrypt/Decrypt — 여러 입력 일괄 라운드트립 검증")
    void testDamo_roundTrip_multipleValues() {
        String[] inputs = {
                "홍길동",
                "test@example.com",
                "900101-1234567",
                "special chars: !@#$%",
                "긴문자열긴문자열긴문자열긴문자열긴문자열"
        };
        for (String input : inputs) {
            String encrypted = service.damoEncrypt(input);
            String decrypted = service.damoDecrypt(encrypted);
            assertEquals(input, decrypted, "라운드트립 불일치: [" + input + "]");
        }
    }

    @Test
    @DisplayName("3-4. 서로 다른 인코딩 방식(charset 지정 hex)도 UTF-8 바이트 기준과 일치")
    void testHexEncode_matchesManualUtf8Bytes() {
        String plain = "abc";
        byte[] expected = plain.getBytes(StandardCharsets.UTF_8);
        StringBuilder sb = new StringBuilder();
        for (byte b : expected) {
            sb.append(String.format("%02X", b));
        }
        assertEquals(sb.toString(), service.hexEncode(plain, "UTF-8"));
    }

    // =========================================================================
    // 4. version.info
    // =========================================================================

    @Test
    @DisplayName("4-1. parseVersionInfo() — version/buildTime 파싱")
    void testParseVersionInfo_parsesVersionAndBuildTime() throws Exception {
        String content = "version=4.5.1-SNAPSHOT\nbuildTime=2026-07-22 10:00:00\n";
        try (InputStream in = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
            VersionInfoDTO dto = service.parseVersionInfo(in);
            assertEquals("4.5.1-SNAPSHOT", dto.getVersion());
            assertEquals("2026-07-22 10:00:00", dto.getBuildTime());
        }
    }

    @Test
    @DisplayName("4-2. parseVersionInfo() — 알 수 없는 키만 있으면 필드는 null")
    void testParseVersionInfo_missingKeys_returnsNullFields() throws Exception {
        String content = "someOtherKey=value\n";
        try (InputStream in = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
            VersionInfoDTO dto = service.parseVersionInfo(in);
            assertNull(dto.getVersion());
            assertNull(dto.getBuildTime());
        }
    }

    @Test
    @DisplayName("4-3. parseVersionInfo() — module.<이름> 키를 서브모듈 버전 맵으로 파싱")
    void testParseVersionInfo_parsesModuleVersions() throws Exception {
        String content = "version=20260722_개발배포-dirty\nbuildTime=2026-07-23 08:51:29\n"
                + "module.elink-online-common=20260722-1-gcb0a588\n"
                + "module.elink-online-core=20260618_개발배포\n";
        try (InputStream in = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
            VersionInfoDTO dto = service.parseVersionInfo(in);
            assertEquals("20260722_개발배포-dirty", dto.getVersion());
            assertEquals("20260722-1-gcb0a588", dto.getModuleVersions().get("elink-online-common"));
            assertEquals("20260618_개발배포", dto.getModuleVersions().get("elink-online-core"));
        }
    }

    @Test
    @DisplayName("4-4. parseVersionInfo() — module.* 키가 없으면 moduleVersions는 빈 맵")
    void testParseVersionInfo_noModuleKeys_returnsEmptyModuleVersions() throws Exception {
        String content = "version=4.5.1-SNAPSHOT\nbuildTime=2026-07-22 10:00:00\n";
        try (InputStream in = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
            VersionInfoDTO dto = service.parseVersionInfo(in);
            assertTrue(dto.getModuleVersions().isEmpty());
        }
    }

    @Test
    @DisplayName("4-5. getVersionInfo() — classpath에 리소스가 없으면(빌드 태스크 미실행) null 반환, 예외 아님")
    void testGetVersionInfo_resourceMissing_returnsNullWithoutException() {
        // 이 테스트 클래스 자체가 클래스패스 루트에 없는 리소스를 조회하는 정상 케이스를 보장하진 않지만,
        // generateVersionInfo 태스크를 거치지 않은 환경(로컬 IDE 등)에서도 예외 없이 동작해야 한다.
        assertDoesNotThrow(() -> service.getVersionInfo());
    }
}
