package com.flightticket.service;

import com.flightticket.dto.user.CreateUserRequest;
import com.flightticket.dto.user.UpdateUserRequest;
import com.flightticket.dto.user.UserResponse;
import com.flightticket.exception.DuplicateUserException;
import com.flightticket.exception.UnauthorizedAccessException;
import com.flightticket.exception.UserNotFoundException;
import com.flightticket.model.entity.Agency;
import com.flightticket.model.entity.User;
import com.flightticket.model.enums.AgencyStatus;
import com.flightticket.model.enums.Role;
import com.flightticket.model.enums.UserStatus;
import com.flightticket.repository.AgencyRepository;
import com.flightticket.repository.UserRepository;
import com.flightticket.security.PermissionService;
import com.flightticket.service.impl.UserManagementServiceImpl;
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.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import java.util.*;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

/**
 * Unit tests for UserManagementService
 * Requirements: 2.2, 2.3, 2.4
 */
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class UserManagementServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @Mock
    private AgencyRepository agencyRepository;
    
    @Mock
    private PermissionService permissionService;
    
    @InjectMocks
    private UserManagementServiceImpl userManagementService;
    
    private UUID testAgencyId;
    private UUID testUserId;
    private UUID createdById;
    
    @BeforeEach
    void setUp() {
        testAgencyId = UUID.randomUUID();
        testUserId = UUID.randomUUID();
        createdById = UUID.randomUUID();
    }
    
    @Test
    void shouldCreateUserSuccessfully() {
        // Given
        CreateUserRequest request = new CreateUserRequest(
                "testuser",
                "test@example.com",
                testAgencyId,
                Set.of(Role.AGENT),
                false
        );
        
        when(permissionService.canManageUsers()).thenReturn(true);
        when(permissionService.isSuperAdmin()).thenReturn(true);
        when(permissionService.canAssignRole(Role.AGENT.name())).thenReturn(true);
        when(userRepository.existsByUsername(request.getUsername())).thenReturn(false);
        when(userRepository.existsByEmail(request.getEmail())).thenReturn(false);
        when(agencyRepository.existsById(testAgencyId)).thenReturn(true);
        
        User savedUser = createTestUser();
        when(userRepository.save(any(User.class))).thenReturn(savedUser);
        
        // When
        UserResponse response = userManagementService.createUser(request, createdById);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getUsername()).isEqualTo("testuser");
        assertThat(response.getEmail()).isEqualTo("test@example.com");
        assertThat(response.getAgencyId()).isEqualTo(testAgencyId);
        assertThat(response.getRoles()).contains(Role.AGENT);
        
        verify(userRepository).save(any(User.class));
    }
    
    @Test
    void shouldThrowExceptionWhenCreatingUserWithDuplicateUsername() {
        // Given
        CreateUserRequest request = new CreateUserRequest(
                "existinguser",
                "test@example.com",
                testAgencyId,
                Set.of(Role.AGENT),
                false
        );
        
        when(permissionService.canManageUsers()).thenReturn(true);
        when(userRepository.existsByUsername(request.getUsername())).thenReturn(true);
        
        // When & Then
        assertThatThrownBy(() -> userManagementService.createUser(request, createdById))
                .isInstanceOf(DuplicateUserException.class)
                .hasMessageContaining("username");
        
        verify(userRepository, never()).save(any(User.class));
    }
    
    @Test
    void shouldThrowExceptionWhenCreatingUserWithDuplicateEmail() {
        // Given
        CreateUserRequest request = new CreateUserRequest(
                "testuser",
                "existing@example.com",
                testAgencyId,
                Set.of(Role.AGENT),
                false
        );
        
        when(permissionService.canManageUsers()).thenReturn(true);
        when(userRepository.existsByUsername(request.getUsername())).thenReturn(false);
        when(userRepository.existsByEmail(request.getEmail())).thenReturn(true);
        
        // When & Then
        assertThatThrownBy(() -> userManagementService.createUser(request, createdById))
                .isInstanceOf(DuplicateUserException.class)
                .hasMessageContaining("email");
        
        verify(userRepository, never()).save(any(User.class));
    }
    
    @Test
    void shouldThrowExceptionWhenAgencyAdminCreatesUserForDifferentAgency() {
        // Given
        UUID differentAgencyId = UUID.randomUUID();
        CreateUserRequest request = new CreateUserRequest(
                "testuser",
                "test@example.com",
                differentAgencyId,
                Set.of(Role.AGENT),
                false
        );
        
        when(permissionService.canManageUsers()).thenReturn(true);
        when(permissionService.isAgencyAdmin()).thenReturn(true);
        when(permissionService.isSuperAdmin()).thenReturn(false);
        when(permissionService.getCurrentUserAgencyId()).thenReturn(testAgencyId.toString());
        
        // When & Then
        assertThatThrownBy(() -> userManagementService.createUser(request, createdById))
                .isInstanceOf(UnauthorizedAccessException.class)
                .hasMessageContaining("own agency");
        
        verify(userRepository, never()).save(any(User.class));
    }
    
    @Test
    void shouldUpdateUserSuccessfully() {
        // Given
        UpdateUserRequest request = new UpdateUserRequest();
        request.setEmail("newemail@example.com");
        request.setStatus(UserStatus.ACTIVE);
        
        User existingUser = createTestUser();
        when(userRepository.findById(testUserId)).thenReturn(Optional.of(existingUser));
        when(permissionService.canManageUsers()).thenReturn(true);
        when(permissionService.isSuperAdmin()).thenReturn(true);
        when(userRepository.existsByEmail(request.getEmail())).thenReturn(false);
        when(userRepository.save(any(User.class))).thenReturn(existingUser);
        
        // When
        UserResponse response = userManagementService.updateUser(testUserId, request);
        
        // Then
        assertThat(response).isNotNull();
        verify(userRepository).save(any(User.class));
    }
    
    @Test
    void shouldThrowExceptionWhenUpdatingNonExistentUser() {
        // Given
        UpdateUserRequest request = new UpdateUserRequest();
        request.setEmail("newemail@example.com");
        
        when(userRepository.findById(testUserId)).thenReturn(Optional.empty());
        
        // When & Then
        assertThatThrownBy(() -> userManagementService.updateUser(testUserId, request))
                .isInstanceOf(UserNotFoundException.class);
        
        verify(userRepository, never()).save(any(User.class));
    }
    
    @Test
    void shouldDeleteUserSuccessfully() {
        // Given
        User existingUser = createTestUser();
        when(userRepository.findById(testUserId)).thenReturn(Optional.of(existingUser));
        when(permissionService.canManageUsers()).thenReturn(true);
        when(permissionService.isSuperAdmin()).thenReturn(true);
        when(userRepository.save(any(User.class))).thenReturn(existingUser);
        
        // When
        userManagementService.deleteUser(testUserId);
        
        // Then
        verify(userRepository).save(argThat(user -> 
                user.getStatus() == UserStatus.INACTIVE
        ));
    }
    
    @Test
    void shouldGetUserByIdSuccessfully() {
        // Given
        User existingUser = createTestUser();
        when(userRepository.findById(testUserId)).thenReturn(Optional.of(existingUser));
        when(permissionService.isSuperAdmin()).thenReturn(true);
        
        // When
        UserResponse response = userManagementService.getUserById(testUserId);
        
        // Then
        assertThat(response).isNotNull();
        assertThat(response.getId()).isEqualTo(testUserId);
    }
    
    @Test
    void shouldGetUsersByAgencySuccessfully() {
        // Given
        List<User> users = Arrays.asList(createTestUser(), createTestUser());
        when(userRepository.findByAgencyId(testAgencyId)).thenReturn(users);
        doNothing().when(permissionService).validateAgencyAccess(testAgencyId.toString());
        
        // When
        List<UserResponse> responses = userManagementService.getUsersByAgency(testAgencyId);
        
        // Then
        assertThat(responses).hasSize(2);
    }
    
    @Test
    void shouldAssignRoleSuccessfully() {
        // Given
        User existingUser = createTestUser();
        when(userRepository.findById(testUserId)).thenReturn(Optional.of(existingUser));
        when(permissionService.canManageUsers()).thenReturn(true);
        when(permissionService.isSuperAdmin()).thenReturn(true);
        when(permissionService.canAssignRole(Role.FINANCE_USER.name())).thenReturn(true);
        when(userRepository.save(any(User.class))).thenReturn(existingUser);
        
        // When
        userManagementService.assignRole(testUserId, Role.FINANCE_USER);
        
        // Then
        verify(userRepository).save(argThat(user -> 
                user.getRoles().contains(Role.FINANCE_USER)
        ));
    }
    
    @Test
    void shouldRevokeRoleSuccessfully() {
        // Given
        User existingUser = createTestUser();
        existingUser.getRoles().add(Role.FINANCE_USER);
        
        when(userRepository.findById(testUserId)).thenReturn(Optional.of(existingUser));
        when(permissionService.canManageUsers()).thenReturn(true);
        when(permissionService.isSuperAdmin()).thenReturn(true);
        when(permissionService.canAssignRole(Role.FINANCE_USER.name())).thenReturn(true);
        when(userRepository.save(any(User.class))).thenReturn(existingUser);
        
        // When
        userManagementService.revokeRole(testUserId, Role.FINANCE_USER);
        
        // Then
        verify(userRepository).save(argThat(user -> 
                !user.getRoles().contains(Role.FINANCE_USER)
        ));
    }
    
    @Test
    void shouldThrowExceptionWhenRevokingLastRole() {
        // Given
        User existingUser = createTestUser();
        existingUser.setRoles(new HashSet<>(Set.of(Role.AGENT)));
        
        when(userRepository.findById(testUserId)).thenReturn(Optional.of(existingUser));
        when(permissionService.canManageUsers()).thenReturn(true);
        when(permissionService.isSuperAdmin()).thenReturn(true);
        when(permissionService.canAssignRole(Role.AGENT.name())).thenReturn(true);
        
        // When & Then
        assertThatThrownBy(() -> userManagementService.revokeRole(testUserId, Role.AGENT))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("at least one role");
        
        verify(userRepository, never()).save(any(User.class));
    }
    
    @Test
    void shouldGetAllUsersAsSuperAdmin() {
        // Given
        List<User> users = Arrays.asList(createTestUser(), createTestUser());
        when(permissionService.isSuperAdmin()).thenReturn(true);
        when(userRepository.findAll()).thenReturn(users);
        
        // When
        List<UserResponse> responses = userManagementService.getAllUsers();
        
        // Then
        assertThat(responses).hasSize(2);
    }
    
    @Test
    void shouldThrowExceptionWhenNonSuperAdminTriesToGetAllUsers() {
        // Given
        when(permissionService.isSuperAdmin()).thenReturn(false);
        
        // When & Then
        assertThatThrownBy(() -> userManagementService.getAllUsers())
                .isInstanceOf(UnauthorizedAccessException.class)
                .hasMessageContaining("Super Admin");
    }
    
    private User createTestUser() {
        User user = new User();
        user.setId(testUserId);
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setAgencyId(testAgencyId);
        user.setRoles(new HashSet<>(Set.of(Role.AGENT)));
        user.setMfaEnabled(false);
        user.setStatus(UserStatus.ACTIVE);
        return user;
    }
}
