package com.saas.tenant.service;

import com.saas.admin.entity.User;
import com.saas.admin.repository.UserRepository;
import com.saas.shared.util.PasswordGeneratorUtil;
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.util.Optional;

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

@ExtendWith(MockitoExtension.class)
class UserProvisioningServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private PasswordEncoder passwordEncoder;

    @InjectMocks
    private UserProvisioningService userProvisioningService;

    private static final String TEST_EMAIL = "doctor@test.com";
    private static final String TEST_FIRST_NAME = "John";
    private static final String TEST_LAST_NAME = "Doe";

    @BeforeEach
    void setUp() {
        // Setup common mocks
    }

    @Test
    void provisionDoctorUser_Success() {
        // Given
        when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.empty());
        when(passwordEncoder.encode(anyString())).thenReturn("encoded_password");
        when(userRepository.save(any(User.class))).thenAnswer(invocation -> invocation.getArgument(0));

        // When
        String generatedPassword = userProvisioningService.provisionDoctorUser(
                TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL);

        // Then
        assertNotNull(generatedPassword);
        assertTrue(generatedPassword.length() >= 12, "Password should be at least 12 characters");

        verify(userRepository).findByEmail(TEST_EMAIL);
        verify(passwordEncoder).encode(anyString());
        verify(userRepository).save(argThat(user -> user.getEmail().equals(TEST_EMAIL) &&
                user.getFirstName().equals(TEST_FIRST_NAME) &&
                user.getLastName().equals(TEST_LAST_NAME) &&
                user.getRole().equals("DOCTOR") &&
                user.getStatus().equals("ACTIVE")));
    }

    @Test
    void provisionDoctorUser_UserAlreadyExists_ThrowsException() {
        // Given
        User existingUser = new User();
        existingUser.setEmail(TEST_EMAIL);
        when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.of(existingUser));

        // When & Then
        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> userProvisioningService.provisionDoctorUser(TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL));

        assertTrue(exception.getMessage().contains("already exists"));
        verify(userRepository).findByEmail(TEST_EMAIL);
        verify(userRepository, never()).save(any());
    }

    @Test
    void userExists_UserExists_ReturnsTrue() {
        // Given
        User existingUser = new User();
        when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.of(existingUser));

        // When
        boolean exists = userProvisioningService.userExists(TEST_EMAIL);

        // Then
        assertTrue(exists);
        verify(userRepository).findByEmail(TEST_EMAIL);
    }

    @Test
    void userExists_UserDoesNotExist_ReturnsFalse() {
        // Given
        when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.empty());

        // When
        boolean exists = userProvisioningService.userExists(TEST_EMAIL);

        // Then
        assertFalse(exists);
        verify(userRepository).findByEmail(TEST_EMAIL);
    }

    @Test
    void provisionDoctorUser_GeneratesSecurePassword() {
        // Given
        when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.empty());
        when(passwordEncoder.encode(anyString())).thenReturn("encoded_password");
        when(userRepository.save(any(User.class))).thenAnswer(invocation -> invocation.getArgument(0));

        // When
        String password = userProvisioningService.provisionDoctorUser(
                TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL);

        // Then
        assertNotNull(password);
        assertTrue(password.length() >= 12);
        // Check password contains mix of characters (alphanumeric + symbols)
        assertTrue(password.matches(".*[A-Z].*"), "Should contain uppercase");
        assertTrue(password.matches(".*[a-z].*"), "Should contain lowercase");
        assertTrue(password.matches(".*[0-9].*"), "Should contain digit");
    }
}
