package com.social.media.service.impl;

import com.social.media.dto.LoginRequest;
import com.social.media.dto.LoginResponse;
import com.social.media.dto.UserDto;
import com.social.media.dto.UserCredentialsDto;
import com.social.media.service.UserService;
import com.social.media.service.UserCredentialsService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.time.LocalDateTime;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * Testes unitários para AuthServiceImpl
 */
@ExtendWith(MockitoExtension.class)
class AuthServiceImplTest {

    @Mock
    private UserService userService;

    @Mock
    private UserCredentialsService userCredentialsService;

    @Mock
    private PasswordEncoder passwordEncoder;

    @InjectMocks
    private AuthServiceImpl authService;

    private LoginRequest validLoginRequest;
    private UserDto activeUser;
    private UserCredentialsDto validCredentials;

    @BeforeEach
    void setUp() {
        // Configurar dados de teste
        validLoginRequest = new LoginRequest();
        validLoginRequest.setEmail("usuario@exemplo.com");
        validLoginRequest.setPassword("senha123");
        validLoginRequest.setCompanyId(1L);

        activeUser = new UserDto();
        activeUser.setId(1L);
        activeUser.setName("Usuario Teste");
        activeUser.setEmail("usuario@exemplo.com");
        activeUser.setCompanyId(1L);
        activeUser.setType("USER");
        activeUser.setStatus("ACTIVE");
        activeUser.setDeleted(false);
        activeUser.setLastAccessDate(LocalDateTime.now().minusDays(1));

        validCredentials = new UserCredentialsDto();
        validCredentials.setUserId(1L);
        validCredentials.setPasswordHash("$2a$10$hashedPassword");
        validCredentials.setFailedLoginAttempts(0);
        validCredentials.setTwoFactorEnabled(false);
        validCredentials.setPasswordChangedAt(LocalDateTime.now().minusDays(30));
        validCredentials.setIsAccountLocked(false);
        validCredentials.setIsPermanentlyLocked(false);
    }

    @Test
    void testAuthenticateSuccess() {
        // Arrange
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenReturn(Optional.of(activeUser));
        when(userCredentialsService.findByUserId(activeUser.getId()))
                .thenReturn(Optional.of(validCredentials));
        when(userCredentialsService.isAccountLocked(activeUser.getId()))
                .thenReturn(false);
        when(passwordEncoder.matches(validLoginRequest.getPassword(), validCredentials.getPasswordHash()))
                .thenReturn(true);

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertTrue(response.isSuccess());
        assertEquals("Login realizado com sucesso", response.getMessage());
        assertEquals(activeUser.getId(), response.getUserId());
        assertEquals(activeUser.getName(), response.getName());
        assertEquals(activeUser.getEmail(), response.getEmail());
        assertNotNull(response.getToken());
        assertFalse(response.isAccountLocked());
        assertFalse(response.isPasswordExpired());

        // Verificar se os métodos corretos foram chamados
        verify(userCredentialsService).recordSuccessfulLogin(activeUser.getId());
        verify(userService).updateLastAccess(activeUser.getId());
    }

    @Test
    void testAuthenticateUserNotFound() {
        // Arrange
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenReturn(Optional.empty());

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("Email ou senha inválidos", response.getMessage());
        assertNull(response.getUserId());
        assertNull(response.getToken());
    }

    @Test
    void testAuthenticateUserInactive() {
        // Arrange
        activeUser.setStatus("INACTIVE");
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenReturn(Optional.of(activeUser));

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("Conta inativa ou bloqueada", response.getMessage());
        assertNull(response.getUserId());
        assertNull(response.getToken());
    }

    @Test
    void testAuthenticateUserDeleted() {
        // Arrange
        activeUser.setDeleted(true);
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenReturn(Optional.of(activeUser));

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("Conta inativa ou bloqueada", response.getMessage());
        assertNull(response.getUserId());
        assertNull(response.getToken());
    }

    @Test
    void testAuthenticateCredentialsNotFound() {
        // Arrange
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenReturn(Optional.of(activeUser));
        when(userCredentialsService.findByUserId(activeUser.getId()))
                .thenReturn(Optional.empty());

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("Erro interno - credenciais não encontradas", response.getMessage());
        assertNull(response.getUserId());
        assertNull(response.getToken());
    }

    @Test
    void testAuthenticateAccountLocked() {
        // Arrange
        validCredentials.setIsAccountLocked(true);
        validCredentials.setRemainingLockoutMinutes(15L);
        
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenReturn(Optional.of(activeUser));
        when(userCredentialsService.findByUserId(activeUser.getId()))
                .thenReturn(Optional.of(validCredentials));
        when(userCredentialsService.isAccountLocked(activeUser.getId()))
                .thenReturn(true);

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("bloqueada"));
        assertTrue(response.isAccountLocked());
        assertNull(response.getUserId());
        assertNull(response.getToken());
    }

    @Test
    void testAuthenticateInvalidPassword() {
        // Arrange
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenReturn(Optional.of(activeUser));
        when(userCredentialsService.findByUserId(activeUser.getId()))
                .thenReturn(Optional.of(validCredentials));
        when(userCredentialsService.isAccountLocked(activeUser.getId()))
                .thenReturn(false);
        when(passwordEncoder.matches(validLoginRequest.getPassword(), validCredentials.getPasswordHash()))
                .thenReturn(false);

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("Email ou senha inválidos", response.getMessage());
        assertNull(response.getUserId());
        assertNull(response.getToken());

        // Verificar se tentativa falhada foi registrada
        verify(userCredentialsService).recordFailedLoginAttempt(activeUser.getId());
    }

    @Test
    void testAuthenticatePasswordExpired() {
        // Arrange
        validCredentials.setPasswordChangedAt(LocalDateTime.now().minusDays(100)); // Senha com mais de 90 dias
        
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenReturn(Optional.of(activeUser));
        when(userCredentialsService.findByUserId(activeUser.getId()))
                .thenReturn(Optional.of(validCredentials));
        when(userCredentialsService.isAccountLocked(activeUser.getId()))
                .thenReturn(false);
        when(passwordEncoder.matches(validLoginRequest.getPassword(), validCredentials.getPasswordHash()))
                .thenReturn(true);

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("expirada"));
        assertTrue(response.isPasswordExpired());
        assertEquals(activeUser.getId(), response.getUserId()); // Dados do usuário são retornados para permitir redefinição
        assertNull(response.getToken());
    }

    @Test
    void testAuthenticatePasswordNeverChanged() {
        // Arrange
        validCredentials.setPasswordChangedAt(null); // Senha nunca foi alterada
        
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenReturn(Optional.of(activeUser));
        when(userCredentialsService.findByUserId(activeUser.getId()))
                .thenReturn(Optional.of(validCredentials));
        when(userCredentialsService.isAccountLocked(activeUser.getId()))
                .thenReturn(false);
        when(passwordEncoder.matches(validLoginRequest.getPassword(), validCredentials.getPasswordHash()))
                .thenReturn(true);

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertTrue(response.getMessage().contains("expirada"));
        assertTrue(response.isPasswordExpired());
        assertEquals(activeUser.getId(), response.getUserId());
        assertNull(response.getToken());
    }

    @Test
    void testLogoutSuccess() {
        // Act
        boolean result = authService.logout(1L);

        // Assert
        assertTrue(result);
    }

    @Test
    void testValidateSessionSuccess() {
        // Act
        boolean result = authService.validateSession(1L, "some-token");

        // Assert
        assertTrue(result);
    }

    @Test
    void testRefreshTokenNotImplemented() {
        // Act
        String result = authService.refreshToken("refresh-token");

        // Assert
        assertNull(result);
    }

    @Test
    void testAuthenticateExceptionHandling() {
        // Arrange
        when(userService.findByEmail(validLoginRequest.getEmail()))
                .thenThrow(new RuntimeException("Database error"));

        // Act
        LoginResponse response = authService.authenticate(validLoginRequest);

        // Assert
        assertFalse(response.isSuccess());
        assertEquals("Erro interno durante autenticação", response.getMessage());
        assertNull(response.getUserId());
        assertNull(response.getToken());
    }
}
