package com.mosquito.project.persistence.repository;

import com.mosquito.project.persistence.entity.ApiKeyEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.TestPropertySource;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Optional;

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

/**
 * ApiKeyRepository 数据访问层测试
 * 测试API密钥的CRUD操作和安全相关查询方法
 */
@DataJpaTest
@org.springframework.context.annotation.Import(com.mosquito.project.config.TestCacheConfig.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
@TestPropertySource(properties = {
        "spring.cache.type=NONE",
        "spring.jpa.hibernate.ddl-auto=create-drop",
        "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration"
})
class ApiKeyRepositoryTest {

    @Autowired
    private ApiKeyRepository apiKeyRepository;

    private static final Long ACTIVITY_ID = 1L;
    private OffsetDateTime now;

    @BeforeEach
    void setUp() {
        now = OffsetDateTime.now(ZoneOffset.UTC);
        apiKeyRepository.deleteAll();
    }

    @Test
    void shouldSaveAndFindApiKeyById() {
        // 创建API密钥
        ApiKeyEntity apiKey = createApiKey("Test Key", "hash123", "salt123", "prefix123", "encrypted123");
        ApiKeyEntity saved = apiKeyRepository.save(apiKey);

        // 验证保存
        assertNotNull(saved.getId(), "保存后ID不应为空");
        assertEquals("Test Key", saved.getName());

        // 通过ID查询
        ApiKeyEntity found = apiKeyRepository.findById(saved.getId()).orElseThrow();
        assertEquals("hash123", found.getKeyHash());
    }

    @Test
    void shouldFindByKeyHash() {
        // 创建并保存API密钥
        ApiKeyEntity apiKey = createApiKey("Production Key", "secure_hash_123", "salt_abc", "prod_", "enc_data");
        apiKeyRepository.save(apiKey);

        // 通过keyHash查询
        Optional<ApiKeyEntity> found = apiKeyRepository.findByKeyHash("secure_hash_123");

        // 验证
        assertTrue(found.isPresent(), "应该能通过keyHash找到实体");
        assertEquals("Production Key", found.get().getName());
        assertEquals("secure_hash_123", found.get().getKeyHash());
    }

    @Test
    void shouldReturnEmptyWhenKeyHashNotFound() {
        // 查询不存在的keyHash
        Optional<ApiKeyEntity> found = apiKeyRepository.findByKeyHash("non_existent_hash");
        assertFalse(found.isPresent(), "不存在的keyHash应该返回空Optional");
    }

    @Test
    void shouldFindByKeyPrefix() {
        // 创建并保存API密钥
        ApiKeyEntity apiKey = createApiKey("Staging Key", "hash_stg_456", "salt_def", "stg_", "enc_staging");
        apiKeyRepository.save(apiKey);

        // 通过keyPrefix查询
        Optional<ApiKeyEntity> found = apiKeyRepository.findByKeyPrefix("stg_");

        // 验证
        assertTrue(found.isPresent(), "应该能通过keyPrefix找到实体");
        assertEquals("stg_", found.get().getKeyPrefix());
    }

    @Test
    void shouldReturnEmptyWhenKeyPrefixNotFound() {
        // 查询不存在的keyPrefix
        Optional<ApiKeyEntity> found = apiKeyRepository.findByKeyPrefix("nonexistent_");
        assertFalse(found.isPresent(), "不存在的keyPrefix应该返回空Optional");
    }

    @Test
    void shouldEnforceUniqueKeyHashConstraint() {
        // 创建第一个密钥
        ApiKeyEntity key1 = createApiKey("Key 1", "duplicate_hash", "salt1", "pre1_", "enc1");
        apiKeyRepository.save(key1);

        // 创建第二个密钥，使用相同的keyHash
        ApiKeyEntity key2 = createApiKey("Key 2", "duplicate_hash", "salt2", "pre2_", "enc2");

        // 验证违反唯一约束
        assertThrows(Exception.class, () -> {
            apiKeyRepository.saveAndFlush(key2);
        }, "重复的keyHash应该违反唯一约束");
    }

    @Test
    void shouldAllowSameKeyPrefixForDifferentKeys() {
        // 两个不同的密钥使用相同的keyPrefix应该允许
        ApiKeyEntity key1 = createApiKey("Key 1", "hash1", "salt1", "shared_prefix_", "enc1");
        ApiKeyEntity key2 = createApiKey("Key 2", "hash2", "salt2", "same_prefix_", "enc2");

        assertDoesNotThrow(() -> {
            apiKeyRepository.save(key1);
            apiKeyRepository.save(key2);
        }, "不同的密钥应该可以共存");

        // 查询第一个密钥
        Optional<ApiKeyEntity> found1 = apiKeyRepository.findByKeyPrefix("shared_prefix_");
        assertTrue(found1.isPresent(), "应该能通过keyPrefix找到Key 1");
        assertEquals("Key 1", found1.get().getName());

        // 查询第二个密钥
        Optional<ApiKeyEntity> found2 = apiKeyRepository.findByKeyPrefix("same_prefix_");
        assertTrue(found2.isPresent(), "应该能通过keyPrefix找到Key 2");
        assertEquals("Key 2", found2.get().getName());
    }

    @Test
    void shouldUpdateApiKeyFields() {
        // 创建并保存初始密钥
        ApiKeyEntity apiKey = createApiKey("Original Name", "hash123", "salt123", "pre_", "enc");
        ApiKeyEntity saved = apiKeyRepository.save(apiKey);

        // 更新多个字段
        saved.setName("Updated Name");
        saved.setLastUsedAt(now.plusMinutes(5));
        saved.setRevokedAt(now.plusHours(1));
        apiKeyRepository.save(saved);

        // 验证更新
        ApiKeyEntity updated = apiKeyRepository.findById(saved.getId()).orElseThrow();
        assertEquals("Updated Name", updated.getName());
        assertNotNull(updated.getLastUsedAt());
        assertNotNull(updated.getRevokedAt());
    }

    @Test
    void shouldTrackKeyLifecycle() {
        // 创建新密钥
        ApiKeyEntity apiKey = createApiKey("Lifecycle Test Key", "lifecycle_hash", "salt", "lc_", "enc");
        apiKey.setCreatedAt(now);
        ApiKeyEntity saved = apiKeyRepository.save(apiKey);

        // 验证初始状态
        assertNotNull(saved.getCreatedAt());
        assertNull(saved.getLastUsedAt());
        assertNull(saved.getRevokedAt());
        assertNull(saved.getRevealedAt());

        // 模拟使用
        saved.setLastUsedAt(now.plusMinutes(10));
        apiKeyRepository.save(saved);

        // 模拟显示
        ApiKeyEntity updated = apiKeyRepository.findById(saved.getId()).orElseThrow();
        updated.setRevealedAt(now.plusMinutes(20));
        apiKeyRepository.save(updated);

        // 模拟撤销
        ApiKeyEntity revoked = apiKeyRepository.findById(saved.getId()).orElseThrow();
        revoked.setRevokedAt(now.plusHours(1));
        apiKeyRepository.save(revoked);

        // 验证最终状态
        ApiKeyEntity finalState = apiKeyRepository.findById(saved.getId()).orElseThrow();
        assertNotNull(finalState.getCreatedAt());
        assertNotNull(finalState.getLastUsedAt());
        assertNotNull(finalState.getRevealedAt());
        assertNotNull(finalState.getRevokedAt());
    }

    @Test
    void shouldDeleteApiKey() {
        // 创建并保存密钥
        ApiKeyEntity apiKey = createApiKey("To Be Deleted", "delete_hash", "salt", "del_", "enc");
        ApiKeyEntity saved = apiKeyRepository.save(apiKey);
        Long id = saved.getId();

        // 删除
        apiKeyRepository.deleteById(id);

        // 验证删除
        assertFalse(apiKeyRepository.existsById(id), "删除后应该不存在");
        assertTrue(apiKeyRepository.findByKeyHash("delete_hash").isEmpty(), "通过hash查询也应该找不到");
    }

    @Test
    void shouldSupportMultipleApiKeysPerActivity() {
        // 为一个活动创建多个密钥
        for (int i = 0; i < 5; i++) {
            ApiKeyEntity apiKey = createApiKey("Key " + i, "hash_" + i, "salt_" + i, "pre_" + i + "_", "enc_" + i);
            apiKeyRepository.save(apiKey);
        }

        // 验证保存数量
        assertEquals(5, apiKeyRepository.count(), "应该有5个API密钥");
    }

    @Test
    void shouldHandleLongHashAndEncryptedKey() {
        // 创建包含长字符串的密钥
        String longHash = "a".repeat(255);
        String longEncryptedKey = "b".repeat(512);

        ApiKeyEntity apiKey = new ApiKeyEntity();
        apiKey.setName("Long Key Test");
        apiKey.setKeyHash(longHash);
        apiKey.setSalt("salt");
        apiKey.setKeyPrefix("pre_");
        apiKey.setEncryptedKey(longEncryptedKey);
        apiKey.setActivityId(ACTIVITY_ID);
        apiKey.setCreatedAt(now);

        ApiKeyEntity saved = apiKeyRepository.save(apiKey);

        // 验证保存和查询
        ApiKeyEntity found = apiKeyRepository.findByKeyHash(longHash).orElseThrow();
        assertEquals(longHash, found.getKeyHash());
        assertEquals(longEncryptedKey, found.getEncryptedKey());
    }

    @Test
    void shouldPreserveAllFieldsOnSaveAndRetrieve() {
        // 创建完整记录
        ApiKeyEntity apiKey = new ApiKeyEntity();
        apiKey.setName("Complete Test Key");
        apiKey.setKeyHash("complete_hash");
        apiKey.setSalt("complete_salt");
        apiKey.setActivityId(ACTIVITY_ID);
        apiKey.setKeyPrefix("comp_");
        apiKey.setEncryptedKey("complete_encrypted_data");
        apiKey.setCreatedAt(now);
        apiKey.setRevokedAt(now.plusDays(30));
        apiKey.setLastUsedAt(now.plusMinutes(5));
        apiKey.setRevealedAt(now.plusMinutes(1));

        ApiKeyEntity saved = apiKeyRepository.save(apiKey);

        // 查询并验证所有字段
        ApiKeyEntity found = apiKeyRepository.findById(saved.getId()).orElseThrow();
        assertEquals("Complete Test Key", found.getName());
        assertEquals("complete_hash", found.getKeyHash());
        assertEquals("complete_salt", found.getSalt());
        assertEquals(ACTIVITY_ID, found.getActivityId());
        assertEquals("comp_", found.getKeyPrefix());
        assertEquals("complete_encrypted_data", found.getEncryptedKey());
        assertNotNull(found.getCreatedAt());
        assertNotNull(found.getRevokedAt());
        assertNotNull(found.getLastUsedAt());
        assertNotNull(found.getRevealedAt());
    }

    /**
     * 辅助方法：创建API密钥实体
     */
    private ApiKeyEntity createApiKey(String name, String keyHash, String salt, String keyPrefix, String encryptedKey) {
        ApiKeyEntity apiKey = new ApiKeyEntity();
        apiKey.setName(name);
        apiKey.setKeyHash(keyHash);
        apiKey.setSalt(salt);
        apiKey.setKeyPrefix(keyPrefix);
        apiKey.setEncryptedKey(encryptedKey);
        apiKey.setActivityId(ACTIVITY_ID);
        apiKey.setCreatedAt(now);
        return apiKey;
    }
}
