package com.mosquito.project.service;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.util.ReflectionTestUtils;

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

@ExtendWith(MockitoExtension.class)
@DisplayName("API密钥加密服务测试")
class ApiKeyEncryptionServiceTest {

    private ApiKeyEncryptionService encryptionService;
    private final String TEST_KEY = "32-byte-long-test-key-for-unit-tests!";
    private final String TEST_PLAIN_TEXT = "test-api-key-12345";

    @BeforeEach
    void setUp() {
        encryptionService = new ApiKeyEncryptionService();
        // 使用反射设置测试用的加密密钥
        ReflectionTestUtils.setField(encryptionService, "encryptionKey", TEST_KEY);
        encryptionService.init();
    }

    @Test
    @DisplayName("加密解密 - 基本功能")
    void shouldEncryptAndDecrypt_Basic() {
        // When
        String encrypted = encryptionService.encrypt(TEST_PLAIN_TEXT);
        String decrypted = encryptionService.decrypt(encrypted);

        // Then
        assertNotNull(encrypted);
        assertNotEquals(TEST_PLAIN_TEXT, encrypted); // 加密后应该不同
        assertEquals(TEST_PLAIN_TEXT, decrypted); // 解密后应该相同
    }

    @Test
    @DisplayName("加密 - null输入")
    void shouldReturnNull_WhenEncryptNull() {
        // When
        String result = encryptionService.encrypt(null);

        // Then
        assertNull(result);
    }

    @Test
    @DisplayName("加密 - 空字符串")
    void shouldReturnNull_WhenEncryptEmpty() {
        // When
        String result = encryptionService.encrypt("");

        // Then
        assertNull(result);
    }

    @Test
    @DisplayName("加密 - 空白字符串")
    void shouldReturnNull_WhenEncryptBlank() {
        // When
        String result = encryptionService.encrypt("   ");

        // Then
        assertNull(result);
    }

    @Test
    @DisplayName("解密 - null输入")
    void shouldReturnNull_WhenDecryptNull() {
        // When
        String result = encryptionService.decrypt(null);

        // Then
        assertNull(result);
    }

    @Test
    @DisplayName("解密 - 空字符串")
    void shouldReturnNull_WhenDecryptEmpty() {
        // When
        String result = encryptionService.decrypt("");

        // Then
        assertNull(result);
    }

    @Test
    @DisplayName("解密 - 空白字符串")
    void shouldReturnNull_WhenDecryptBlank() {
        // When
        String result = encryptionService.decrypt("   ");

        // Then
        assertNull(result);
    }

    @Test
    @DisplayName("加密解密 - 多次结果不同")
    void shouldProduceDifferentResults_WhenEncryptMultipleTimes() {
        // When
        String encrypted1 = encryptionService.encrypt(TEST_PLAIN_TEXT);
        String encrypted2 = encryptionService.encrypt(TEST_PLAIN_TEXT);

        // Then - 每次加密应该产生不同的结果（因为随机IV）
        assertNotEquals(encrypted1, encrypted2);
        
        // But both should decrypt to the same original
        assertEquals(TEST_PLAIN_TEXT, encryptionService.decrypt(encrypted1));
        assertEquals(TEST_PLAIN_TEXT, encryptionService.decrypt(encrypted2));
    }

    @Test
    @DisplayName("加密解密 - 长文本")
    void shouldHandleLongText() {
        // Given
        String longText = "a".repeat(1000) + "-very-long-api-key-for-testing-encryption-capabilities";

        // When
        String encrypted = encryptionService.encrypt(longText);
        String decrypted = encryptionService.decrypt(encrypted);

        // Then
        assertEquals(longText, decrypted);
        assertNotNull(encrypted);
    }

    @Test
    @DisplayName("加密解密 - 特殊字符")
    void shouldHandleSpecialCharacters() {
        // Given
        String specialText = "测试-🔑-API-Key-中文-ñ-á-Ω-ñ";

        // When
        String encrypted = encryptionService.encrypt(specialText);
        String decrypted = encryptionService.decrypt(encrypted);

        // Then
        assertEquals(specialText, decrypted);
        assertNotNull(encrypted);
    }

    @Test
    @DisplayName("解密 - 无效加密文本")
    void shouldThrowException_WhenDecryptInvalidText() {
        // Given
        String invalidEncrypted = "invalid-base64-encryption-string";

        // When & Then
        assertThrows(RuntimeException.class, () -> {
            encryptionService.decrypt(invalidEncrypted);
        });
    }

    @Test
    @DisplayName("解密 - 损坏的加密文本")
    void shouldThrowException_WhenDecryptCorruptedText() {
        // Given - 有效base64但解密会失败
        String corruptedText = java.util.Base64.getEncoder().encodeToString("corrupted".getBytes());

        // When & Then
        assertThrows(RuntimeException.class, () -> {
            encryptionService.decrypt(corruptedText);
        });
    }

    @Test
    @DisplayName("初始化 - 生产环境默认密钥禁止")
    void shouldFailInit_WhenDefaultKeyInProd() {
        ApiKeyEncryptionService service = new ApiKeyEncryptionService();
        ReflectionTestUtils.setField(service, "encryptionKey", "default-32-byte-key-for-dev-only!");
        MockEnvironment environment = new MockEnvironment();
        environment.setActiveProfiles("prod");
        ReflectionTestUtils.setField(service, "environment", environment);

        IllegalStateException exception = assertThrows(IllegalStateException.class, service::init);
        assertEquals("Encryption key must be set in production", exception.getMessage());
    }

    @Test
    @DisplayName("初始化 - 短密钥")
    void shouldHandleShortKey_WhenInit() {
        // Given
        String shortKey = "short";

        // When
        ApiKeyEncryptionService service = new ApiKeyEncryptionService();
        ReflectionTestUtils.setField(service, "encryptionKey", shortKey);
        assertDoesNotThrow(() -> service.init());

        // Then - 应该能够正常加密解密
        String encrypted = service.encrypt(TEST_PLAIN_TEXT);
        String decrypted = service.decrypt(encrypted);
        assertEquals(TEST_PLAIN_TEXT, decrypted);
    }

    @Test
    @DisplayName("初始化 - 长密钥")
    void shouldHandleLongKey_WhenInit() {
        // Given
        String longKey = "this-is-a-very-long-key-that-is-longer-than-32-bytes-for-testing";

        // When
        ApiKeyEncryptionService service = new ApiKeyEncryptionService();
        ReflectionTestUtils.setField(service, "encryptionKey", longKey);
        assertDoesNotThrow(() -> service.init());

        // Then - 应该能够正常加密解密
        String encrypted = service.encrypt(TEST_PLAIN_TEXT);
        String decrypted = service.decrypt(encrypted);
        assertEquals(TEST_PLAIN_TEXT, decrypted);
    }

    @Test
    @DisplayName("加密解密 - 数字和符号")
    void shouldHandleNumbersAndSymbols() {
        // Given
        String symbolicText = "API-KEY_123!@#$%^&*()_+-=[]{}|;':,./<>?";

        // When
        String encrypted = encryptionService.encrypt(symbolicText);
        String decrypted = encryptionService.decrypt(encrypted);

        // Then
        assertEquals(symbolicText, decrypted);
        assertNotNull(encrypted);
    }

    @Test
    @DisplayName("加密结果 - Base64格式")
    void shouldProduceValidBase64_WhenEncrypt() {
        // When
        String encrypted = encryptionService.encrypt(TEST_PLAIN_TEXT);

        // Then
        assertNotNull(encrypted);
        assertDoesNotThrow(() -> {
            java.util.Base64.getDecoder().decode(encrypted);
        });
    }

    @Test
    @DisplayName("加密结果长度合理性")
    void shouldProduceReasonableLength() {
        // When
        String encrypted = encryptionService.encrypt(TEST_PLAIN_TEXT);

        // Then
        assertTrue(encrypted.length() > 0);
        // 加密后应该比原文长（包含IV和tag）
        assertTrue(encrypted.length() > TEST_PLAIN_TEXT.length());
    }

    @Test
    @DisplayName("初始化 - 生产环境legacy默认密钥禁止")
    void shouldFailInit_WhenLegacyDefaultKeyInProd() {
        ApiKeyEncryptionService service = new ApiKeyEncryptionService();
        ReflectionTestUtils.setField(service, "encryptionKey", "default-32-byte-key-for-dev-only!!");
        MockEnvironment environment = new MockEnvironment();
        environment.setActiveProfiles("prod");
        ReflectionTestUtils.setField(service, "environment", environment);

        IllegalStateException exception = assertThrows(IllegalStateException.class, service::init);
        assertEquals("Encryption key must be set in production", exception.getMessage());
    }

    @Test
    @DisplayName("初始化 - 生产环境空白密钥禁止")
    void shouldFailInit_WhenBlankKeyInProd() {
        ApiKeyEncryptionService service = new ApiKeyEncryptionService();
        ReflectionTestUtils.setField(service, "encryptionKey", "   ");
        MockEnvironment environment = new MockEnvironment();
        environment.setActiveProfiles("prod");
        ReflectionTestUtils.setField(service, "environment", environment);

        IllegalStateException exception = assertThrows(IllegalStateException.class, service::init);
        assertEquals("Encryption key must be set in production", exception.getMessage());
    }

    @Test
    @DisplayName("初始化 - 非生产环境无environment对象")
    void shouldSucceedInit_WhenEnvironmentIsNull() {
        ApiKeyEncryptionService service = new ApiKeyEncryptionService();
        ReflectionTestUtils.setField(service, "encryptionKey", "default-32-byte-key-for-dev-only!");
        ReflectionTestUtils.setField(service, "environment", null);

        assertDoesNotThrow(() -> service.init());

        // 应该能够正常加密解密
        String encrypted = service.encrypt(TEST_PLAIN_TEXT);
        String decrypted = service.decrypt(encrypted);
        assertEquals(TEST_PLAIN_TEXT, decrypted);
    }

    @Test
    @DisplayName("初始化 - 非生产环境允许默认密钥")
    void shouldSucceedInit_WhenDefaultKeyInNonProd() {
        ApiKeyEncryptionService service = new ApiKeyEncryptionService();
        ReflectionTestUtils.setField(service, "encryptionKey", "default-32-byte-key-for-dev-only!");
        MockEnvironment environment = new MockEnvironment();
        environment.setActiveProfiles("dev");
        ReflectionTestUtils.setField(service, "environment", environment);

        assertDoesNotThrow(() -> service.init());

        // 应该能够正常加密解密
        String encrypted = service.encrypt(TEST_PLAIN_TEXT);
        String decrypted = service.decrypt(encrypted);
        assertEquals(TEST_PLAIN_TEXT, decrypted);
    }
}
