package com.gnx.telemedicine.service;

import com.gnx.telemedicine.dto.auth.JwtResponseDto;
import com.gnx.telemedicine.dto.auth.UserLoginDto;
import com.gnx.telemedicine.exception.TwoFactorAuthenticationRequiredException;
import com.gnx.telemedicine.model.UserModel;
import com.gnx.telemedicine.model.enums.Role;
import com.gnx.telemedicine.repository.UserRepository;
import com.gnx.telemedicine.security.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
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.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

/**
 * Unit tests for AuthService.
 */
@ExtendWith(MockitoExtension.class)
class AuthServiceTest {

    @Mock
    private AuthenticationManager authenticationManager;

    @Mock
    private JwtUtils jwtUtils;

    @Mock
    private UserRepository userRepository;

    @Mock
    private LoginAttemptService loginAttemptService;

    @Mock
    private TwoFactorAuthService twoFactorAuthService;

    @Mock
    private HttpServletRequest request;

    @InjectMocks
    private AuthService authService;

    private UserModel testUser;
    private UserLoginDto validLoginDto;

    @BeforeEach
    void setUp() {
        testUser = new UserModel();
        testUser.setId(java.util.UUID.randomUUID());
        testUser.setEmail("test@example.com");
        testUser.setPassword("encodedPassword");
        testUser.setRole(Role.PATIENT);
        testUser.setIsActive(true);

        validLoginDto = new UserLoginDto("test@example.com", "password123", null, null);
    }

    @Test
    void testLoginSuccess() {
        // Arrange
        when(loginAttemptService.isAccountLocked(anyString())).thenReturn(false);
        when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
                .thenReturn(null);
        when(userRepository.findByEmail(anyString())).thenReturn(Optional.of(testUser));
        when(twoFactorAuthService.is2FAEnabled(anyString())).thenReturn(false);
        when(jwtUtils.generateToken(anyString())).thenReturn("test-token");

        // Act
        JwtResponseDto result = authService.login(validLoginDto, request);

        // Assert
        assertNotNull(result);
        assertEquals("test-token", result.token());
        verify(loginAttemptService).recordSuccessfulLogin(anyString(), any());
        verify(jwtUtils).generateToken(anyString());
    }

    @Test
    void testLoginWith2FARequired() {
        // Arrange
        when(loginAttemptService.isAccountLocked(anyString())).thenReturn(false);
        when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
                .thenReturn(null);
        when(userRepository.findByEmail(anyString())).thenReturn(Optional.of(testUser));
        when(twoFactorAuthService.is2FAEnabled(anyString())).thenReturn(true);

        // Act & Assert
        assertThrows(TwoFactorAuthenticationRequiredException.class, () -> {
            authService.login(validLoginDto, request);
        });

        verify(loginAttemptService, never()).recordFailedLogin(anyString(), anyString(), any());
    }

    @Test
    void testLoginAccountLocked() {
        // Arrange
        when(loginAttemptService.isAccountLocked(anyString())).thenReturn(true);

        // Act & Assert
        assertThrows(org.springframework.security.authentication.DisabledException.class, () -> {
            authService.login(validLoginDto, request);
        });

        verify(loginAttemptService).recordFailedLogin(anyString(), anyString(), any());
        verify(authenticationManager, never()).authenticate(any());
    }

    @Test
    void testLoginAccountDeactivated() {
        // Arrange
        testUser.setIsActive(false);
        when(loginAttemptService.isAccountLocked(anyString())).thenReturn(false);
        when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
                .thenReturn(null);
        when(userRepository.findByEmail(anyString())).thenReturn(Optional.of(testUser));

        // Act & Assert
        assertThrows(org.springframework.security.authentication.DisabledException.class, () -> {
            authService.login(validLoginDto, request);
        });

        verify(loginAttemptService).recordFailedLogin(anyString(), anyString(), any());
    }

    @Test
    void testLoginInvalidCredentials() {
        // Arrange
        when(loginAttemptService.isAccountLocked(anyString())).thenReturn(false);
        when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
                .thenThrow(new BadCredentialsException("Invalid credentials"));

        // Act & Assert
        assertThrows(BadCredentialsException.class, () -> {
            authService.login(validLoginDto, request);
        });

        verify(loginAttemptService).recordFailedLogin(anyString(), anyString(), any());
    }
}

