package com.social.media;

import com.social.media.dto.UserCredentialsDto;
import com.social.media.service.UserCredentialsService;
import com.social.media.repository.UserCredentialsRepository;
import com.social.media.domain.entity.UserCredentials;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

import static org.junit.jupiter.api.Assertions.*;

/**
 * Test class to demonstrate password encryption and authentication
 * with the password: Leader@2025!Strong#Pass
 */
@SpringBootTest
@Transactional
public class PasswordEncryptionTest {
    
    @Autowired
    private UserCredentialsService userCredentialsService;
    
    @Autowired
    private UserCredentialsRepository userCredentialsRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    private static final String TEST_PASSWORD = "Leader@2025!Strong#Pass";
    private static final Long TEST_USER_ID = 999L; // Using a test user ID
    
    @Test
    public void testPasswordRegistrationAndAuthentication() {
        System.out.println("=== TESTE DE CRIPTOGRAFIA E AUTENTICAÇÃO DE SENHA ===");
        System.out.println("Senha a ser testada: " + TEST_PASSWORD);
        
        // 1. Verificar se a senha atende aos critérios de política
        testPasswordPolicy();
        
        // 2. Criar hash da senha (simular registro)
        String hashedPassword = passwordEncoder.encode(TEST_PASSWORD);
        System.out.println("\n=== REGISTRO DA SENHA ===");
        System.out.println("Hash gerado: " + hashedPassword);
        
        // 3. Criar credenciais para o usuário de teste
        createTestUserCredentials(hashedPassword);
        
        // 4. Testar autenticação com senha correta
        testSuccessfulAuthentication();
        
        // 5. Testar autenticação com senha incorreta
        testFailedAuthentication();
        
        // 6. Demonstrar diferentes hashes para a mesma senha
        demonstrateDifferentHashes();
        
        // 7. Cleanup - remover usuário de teste
        cleanupTestUser();
        
        System.out.println("\n=== TESTE CONCLUÍDO COM SUCESSO ===");
    }
    
    private void testPasswordPolicy() {
        System.out.println("\n=== VERIFICAÇÃO DE POLÍTICA DE SENHA ===");
        
        // Verificar se a senha atende aos critérios
        boolean meetsPolicy = userCredentialsService.meetsPasswordPolicy(TEST_PASSWORD);
        System.out.println("Senha atende à política: " + meetsPolicy);
        
        // Obter pontuação de força da senha
        int strengthScore = userCredentialsService.getPasswordStrengthScore(TEST_PASSWORD);
        System.out.println("Pontuação de força: " + strengthScore + "/100");
        
        // Verificar se é considerada forte
        boolean isStrong = userCredentialsService.isPasswordStrong(TEST_PASSWORD);
        System.out.println("Senha é considerada forte: " + isStrong);
        
        // Mostrar violações se existirem
        var violations = userCredentialsService.getPasswordPolicyViolations(TEST_PASSWORD);
        if (violations.isEmpty()) {
            System.out.println("✅ Nenhuma violação de política encontrada");
        } else {
            System.out.println("❌ Violações encontradas:");
            violations.forEach(violation -> System.out.println("  - " + violation));
        }
        
        assertTrue(meetsPolicy, "A senha deve atender à política de segurança");
        assertTrue(isStrong, "A senha deve ser considerada forte");
        assertTrue(strengthScore >= 80, "A pontuação de força deve ser pelo menos 80");
    }
    
    private void createTestUserCredentials(String hashedPassword) {
        System.out.println("\n=== CRIANDO CREDENCIAIS DO USUÁRIO ===");
        
        // Verificar se já existe e remover se necessário
        userCredentialsRepository.findByUserId(TEST_USER_ID).ifPresent(existing -> {
            userCredentialsRepository.delete(existing);
            System.out.println("Credenciais existentes removidas");
        });
        
        // Criar novas credenciais
        UserCredentialsDto credentialsDto = new UserCredentialsDto();
        credentialsDto.setUserId(TEST_USER_ID);
        credentialsDto.setPasswordHash(TEST_PASSWORD); // O service irá criptografar
        credentialsDto.setTwoFactorEnabled(false);
        credentialsDto.setFailedLoginAttempts(0);
        credentialsDto.setMaxFailedAttempts(5);
        credentialsDto.setLockoutDurationMinutes(30);
        credentialsDto.setIsAccountLocked(false);
        credentialsDto.setIsPermanentlyLocked(false);
        credentialsDto.setPasswordChangedAt(LocalDateTime.now());
        
        UserCredentialsDto created = userCredentialsService.create(credentialsDto);
        System.out.println("✅ Credenciais criadas para usuário ID: " + created.getUserId());
        
        // Verificar no banco
        UserCredentials savedCredentials = userCredentialsRepository.findByUserId(TEST_USER_ID).orElse(null);
        assertNotNull(savedCredentials, "Credenciais devem ser salvas no banco");
        System.out.println("Hash salvo no banco: " + savedCredentials.getPasswordHash());
        
        // Verificar se o hash é diferente da senha original
        assertNotEquals(TEST_PASSWORD, savedCredentials.getPasswordHash(), 
                       "Hash deve ser diferente da senha original");
    }
    
    private void testSuccessfulAuthentication() {
        System.out.println("\n=== TESTE DE AUTENTICAÇÃO CORRETA ===");
        
        // Verificar estado antes da autenticação
        UserCredentials beforeAuth = userCredentialsRepository.findByUserId(TEST_USER_ID).orElse(null);
        assertNotNull(beforeAuth);
        System.out.println("Tentativas falhas antes: " + beforeAuth.getFailedLoginAttempts());
        System.out.println("Conta bloqueada antes: " + beforeAuth.isAccountLocked());
        
        // Testar autenticação
        boolean authResult = userCredentialsService.authenticate(TEST_USER_ID, TEST_PASSWORD);
        System.out.println("Resultado da autenticação: " + authResult);
        
        // Verificar estado após autenticação
        UserCredentials afterAuth = userCredentialsRepository.findByUserId(TEST_USER_ID).orElse(null);
        assertNotNull(afterAuth);
        System.out.println("Tentativas falhas depois: " + afterAuth.getFailedLoginAttempts());
        System.out.println("Último login: " + afterAuth.getLastLoginAt());
        
        assertTrue(authResult, "Autenticação deve ser bem-sucedida com senha correta");
        assertEquals(0, afterAuth.getFailedLoginAttempts(), 
                    "Tentativas falhas devem ser resetadas após login bem-sucedido");
        assertNotNull(afterAuth.getLastLoginAt(), 
                     "Data do último login deve ser registrada");
        
        System.out.println("✅ Autenticação com senha correta funcionou perfeitamente");
    }
    
    private void testFailedAuthentication() {
        System.out.println("\n=== TESTE DE AUTENTICAÇÃO COM SENHA INCORRETA ===");
        
        String wrongPassword = "SenhaIncorreta123!";
        
        // Verificar estado antes
        UserCredentials beforeAuth = userCredentialsRepository.findByUserId(TEST_USER_ID).orElse(null);
        int failedAttemptsBefore = beforeAuth.getFailedLoginAttempts();
        System.out.println("Tentativas falhas antes: " + failedAttemptsBefore);
        
        // Testar autenticação com senha incorreta
        boolean authResult = userCredentialsService.authenticate(TEST_USER_ID, wrongPassword);
        System.out.println("Resultado da autenticação (senha incorreta): " + authResult);
        
        // Verificar estado após
        UserCredentials afterAuth = userCredentialsRepository.findByUserId(TEST_USER_ID).orElse(null);
        System.out.println("Tentativas falhas depois: " + afterAuth.getFailedLoginAttempts());
        
        assertFalse(authResult, "Autenticação deve falhar com senha incorreta");
        assertEquals(failedAttemptsBefore + 1, afterAuth.getFailedLoginAttempts(),
                    "Tentativas falhas devem ser incrementadas");
        
        System.out.println("✅ Autenticação com senha incorreta foi rejeitada corretamente");
    }
    
    private void demonstrateDifferentHashes() {
        System.out.println("\n=== DEMONSTRAÇÃO: DIFERENTES HASHES PARA A MESMA SENHA ===");
        
        // Gerar múltiplos hashes para a mesma senha
        for (int i = 1; i <= 3; i++) {
            String hash = passwordEncoder.encode(TEST_PASSWORD);
            boolean matches = passwordEncoder.matches(TEST_PASSWORD, hash);
            
            System.out.println("Hash " + i + ": " + hash);
            System.out.println("Verifica corretamente: " + matches);
            
            assertTrue(matches, "Cada hash deve verificar corretamente contra a senha original");
        }
        
        System.out.println("💡 Observe que cada hash é diferente, mas todos verificam corretamente!");
        System.out.println("   Isso demonstra a funcionalidade salt do BCrypt.");
    }
    
    private void cleanupTestUser() {
        System.out.println("\n=== LIMPEZA: REMOVENDO USUÁRIO DE TESTE ===");
        
        userCredentialsRepository.findByUserId(TEST_USER_ID).ifPresent(credentials -> {
            userCredentialsRepository.delete(credentials);
            System.out.println("✅ Usuário de teste removido");
        });
    }
    
    @Test
    public void testPasswordStrengthAnalysis() {
        System.out.println("\n=== ANÁLISE DETALHADA DA FORÇA DA SENHA ===");
        
        // Analisar diferentes senhas
        String[] passwords = {
            TEST_PASSWORD,
            "123456",
            "password",
            "Admin123",
            "MyStrongP@ssw0rd!",
            "weak",
            "VeryLongPasswordButNoSpecialChars123"
        };
        
        for (String pwd : passwords) {
            System.out.println("\nSenha: " + pwd);
            int score = userCredentialsService.getPasswordStrengthScore(pwd);
            boolean isStrong = userCredentialsService.isPasswordStrong(pwd);
            boolean meetsPolicy = userCredentialsService.meetsPasswordPolicy(pwd);
            
            System.out.println("  Pontuação: " + score + "/100");
            System.out.println("  É forte: " + isStrong);
            System.out.println("  Atende política: " + meetsPolicy);
            
            var violations = userCredentialsService.getPasswordPolicyViolations(pwd);
            if (!violations.isEmpty()) {
                System.out.println("  Violações:");
                violations.forEach(v -> System.out.println("    - " + v));
            }
        }
    }
    
    @Test
    public void testDirectBCryptUsage() {
        System.out.println("\n=== TESTE DIRETO DO BCRYPT ===");
        
        // Demonstrar uso direto do BCrypt
        String rawPassword = TEST_PASSWORD;
        
        // Gerar hash
        String hash1 = passwordEncoder.encode(rawPassword);
        String hash2 = passwordEncoder.encode(rawPassword);
        
        System.out.println("Senha original: " + rawPassword);
        System.out.println("Hash 1: " + hash1);
        System.out.println("Hash 2: " + hash2);
        System.out.println("Hashes são diferentes: " + !hash1.equals(hash2));
        
        // Verificar ambos os hashes
        boolean matches1 = passwordEncoder.matches(rawPassword, hash1);
        boolean matches2 = passwordEncoder.matches(rawPassword, hash2);
        
        System.out.println("Hash 1 verifica: " + matches1);
        System.out.println("Hash 2 verifica: " + matches2);
        
        // Testar senha incorreta
        boolean wrongMatch = passwordEncoder.matches("senhaIncorreta", hash1);
        System.out.println("Senha incorreta verifica: " + wrongMatch);
        
        assertTrue(matches1, "Hash 1 deve verificar corretamente");
        assertTrue(matches2, "Hash 2 deve verificar corretamente");
        assertFalse(wrongMatch, "Senha incorreta não deve verificar");
        assertNotEquals(hash1, hash2, "Hashes devem ser diferentes devido ao salt");
    }
}
