package cn.novalon.gym.manage.sys.core.service.impl;

import cn.novalon.gym.manage.common.util.StatusConstants;
import cn.novalon.gym.manage.sys.audit.service.IAuditLogService;
import cn.novalon.gym.manage.sys.audit.domain.AuditLog;
import cn.novalon.gym.manage.sys.core.domain.SysUser;
import cn.novalon.gym.manage.sys.core.domain.UserRole;
import cn.novalon.gym.manage.sys.core.repository.ISysUserRepository;
import cn.novalon.gym.manage.sys.core.repository.ISysRoleRepository;
import cn.novalon.gym.manage.sys.core.repository.IUserRoleRepository;
import cn.novalon.gym.manage.sys.core.command.CreateUserCommand;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.util.Arrays;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

/**
 * 用户服务单元测试
 * 
 * @author 张翔
 * @date 2026-04-02
 */
@ExtendWith(MockitoExtension.class)
class SysUserServiceTest {

    @Mock
    private ISysUserRepository userRepository;

    @Mock
    private ISysRoleRepository roleRepository;

    @Mock
    private IUserRoleRepository userRoleRepository;

    @Mock
    private PasswordEncoder passwordEncoder;

    @Mock
    private IAuditLogService auditLogService;

    private SysUserService userService;

    @BeforeEach
    void setUp() {
        userService = new SysUserService(userRepository, roleRepository, userRoleRepository, passwordEncoder, auditLogService);
    }

    @Test
    void testFindById() {
        SysUser user = new SysUser();
        user.setId(1L);
        user.setUsername("testuser");
        user.setEmail("test@example.com");

        when(userRepository.findById(1L)).thenReturn(Mono.just(user));

        StepVerifier.create(userService.findById(1L))
                .expectNextMatches(u -> u.getId().equals(1L) && u.getUsername().equals("testuser"))
                .verifyComplete();

        verify(userRepository, times(1)).findById(1L);
    }

    @Test
    void testFindByIdNotFound() {
        when(userRepository.findById(999L)).thenReturn(Mono.empty());

        StepVerifier.create(userService.findById(999L))
                .verifyComplete();

        verify(userRepository, times(1)).findById(999L);
    }

    @Test
    void testFindAll() {
        SysUser user1 = new SysUser();
        user1.setId(1L);
        user1.setUsername("user1");

        SysUser user2 = new SysUser();
        user2.setId(2L);
        user2.setUsername("user2");

        when(userRepository.findByDeletedAtIsNull()).thenReturn(Flux.just(user1, user2));

        StepVerifier.create(userService.findAll(false))
                .expectNext(user1)
                .expectNext(user2)
                .verifyComplete();

        verify(userRepository, times(1)).findByDeletedAtIsNull();
    }

    @Test
    void testCreateUser() {
        SysUser user = new SysUser();
        user.setUsername("newuser");
        user.setPassword("plainPassword");
        user.setEmail("newuser@example.com");

        when(passwordEncoder.encode(anyString())).thenReturn("$2b$12$encodedPassword");
        when(userRepository.save(any(SysUser.class))).thenAnswer(invocation -> {
            SysUser savedUser = invocation.getArgument(0);
            savedUser.setId(1L);
            return Mono.just(savedUser);
        });

        StepVerifier.create(userService.createUser(user))
                .expectNextMatches(savedUser -> 
                    savedUser.getId().equals(1L) && 
                    savedUser.getPassword().equals("$2b$12$encodedPassword") &&
                    savedUser.getStatus().equals(StatusConstants.ENABLED)
                )
                .verifyComplete();

        verify(passwordEncoder, times(1)).encode("plainPassword");
        verify(userRepository, times(1)).save(any(SysUser.class));
    }

    @Test
    void testCreateUserWithCommand() {
        CreateUserCommand command = mock(CreateUserCommand.class);
        when(command.username()).thenReturn(mock(cn.novalon.gym.manage.sys.primitive.Username.class));
        when(command.password()).thenReturn(mock(cn.novalon.gym.manage.sys.primitive.Password.class));
        when(command.email()).thenReturn(mock(cn.novalon.gym.manage.sys.primitive.Email.class));
        when(command.username().getValue()).thenReturn("testuser");
        when(command.password().getValue()).thenReturn("password123");
        when(command.email().getValue()).thenReturn("test@example.com");
        when(command.nickname()).thenReturn("Test User");
        when(command.phone()).thenReturn("13800138000");
        when(command.roleId()).thenReturn(1L);
        when(command.status()).thenReturn(null);

        when(passwordEncoder.encode(anyString())).thenReturn("$2b$12$encodedPassword");
        when(userRepository.save(any(SysUser.class))).thenAnswer(invocation -> {
            SysUser savedUser = invocation.getArgument(0);
            savedUser.setId(1L);
            return Mono.just(savedUser);
        });

        StepVerifier.create(userService.createUser(command))
                .expectNextMatches(savedUser -> 
                    savedUser.getUsername().equals("testuser") &&
                    savedUser.getPassword().equals("$2b$12$encodedPassword") &&
                    savedUser.getEmail().equals("test@example.com")
                )
                .verifyComplete();

        verify(passwordEncoder, times(1)).encode("password123");
        verify(userRepository, times(1)).save(any(SysUser.class));
    }

    @Test
    void testUpdateUser() {
        SysUser user = new SysUser();
        user.setId(1L);
        user.setUsername("testuser");
        user.setEmail("updated@example.com");

        when(userRepository.findById(1L)).thenReturn(Mono.just(user));
        when(userRepository.update(any(SysUser.class))).thenReturn(Mono.just(user));

        StepVerifier.create(userService.updateUser(user))
                .expectNextMatches(updatedUser -> 
                    updatedUser.getId().equals(1L) &&
                    updatedUser.getEmail().equals("updated@example.com")
                )
                .verifyComplete();

        verify(userRepository, times(1)).findById(1L);
        verify(userRepository, times(1)).update(any(SysUser.class));
    }

    @Test
    void testDeleteUser() {
        SysUser user = new SysUser();
        user.setId(1L);
        user.setUsername("testuser");

        when(userRepository.findById(1L)).thenReturn(Mono.just(user));
        when(userRoleRepository.deleteByUserId(1L)).thenReturn(Mono.empty());
        when(userRepository.deleteById(1L)).thenReturn(Mono.empty());

        StepVerifier.create(userService.deleteUser(1L))
                .verifyComplete();

        verify(userRepository, times(1)).findById(1L);
        verify(userRoleRepository, times(1)).deleteByUserId(1L);
        verify(userRepository, times(1)).deleteById(1L);
    }

    @Test
    void testDeleteUserNotFound() {
        when(userRepository.findById(999L)).thenReturn(Mono.empty());

        StepVerifier.create(userService.deleteUser(999L))
                .expectErrorMatches(error -> error instanceof RuntimeException && 
                    error.getMessage().equals("User not found"))
                .verify();

        verify(userRepository, times(1)).findById(999L);
        verify(userRoleRepository, never()).deleteByUserId(anyLong());
        verify(userRepository, never()).deleteById(anyLong());
    }

    @Test
    void testChangePassword() {
        SysUser user = new SysUser();
        user.setId(1L);
        user.setUsername("testuser");
        user.setPassword("$2b$12$oldPassword");

        when(userRepository.findById(1L)).thenReturn(Mono.just(user));
        when(passwordEncoder.matches("oldPassword", "$2b$12$oldPassword")).thenReturn(true);
        when(passwordEncoder.encode("newPassword")).thenReturn("$2b$12$newPassword");
        when(userRepository.update(any(SysUser.class))).thenAnswer(invocation -> Mono.just(invocation.getArgument(0)));

        StepVerifier.create(userService.changePassword(1L, "oldPassword", "newPassword"))
                .expectNextMatches(updatedUser -> 
                    updatedUser.getPassword().equals("$2b$12$newPassword")
                )
                .verifyComplete();

        verify(passwordEncoder, times(1)).matches("oldPassword", "$2b$12$oldPassword");
        verify(passwordEncoder, times(1)).encode("newPassword");
        verify(userRepository, times(1)).update(any(SysUser.class));
    }

    @Test
    void testChangePasswordIncorrectOldPassword() {
        SysUser user = new SysUser();
        user.setId(1L);
        user.setUsername("testuser");
        user.setPassword("$2b$12$oldPassword");

        when(userRepository.findById(1L)).thenReturn(Mono.just(user));
        when(passwordEncoder.matches("wrongPassword", "$2b$12$oldPassword")).thenReturn(false);

        StepVerifier.create(userService.changePassword(1L, "wrongPassword", "newPassword"))
                .expectErrorMatches(error -> error instanceof RuntimeException && 
                    error.getMessage().equals("旧密码不正确"))
                .verify();

        verify(passwordEncoder, times(1)).matches("wrongPassword", "$2b$12$oldPassword");
        verify(passwordEncoder, never()).encode(anyString());
        verify(userRepository, never()).save(any(SysUser.class));
    }

    @Test
    void testExistsByUsername() {
        when(userRepository.findByUsername("existinguser")).thenReturn(Mono.just(new SysUser()));
        when(userRepository.findByUsername("nonexistinguser")).thenReturn(Mono.empty());

        StepVerifier.create(userService.existsByUsername("existinguser"))
                .expectNext(true)
                .verifyComplete();

        StepVerifier.create(userService.existsByUsername("nonexistinguser"))
                .expectNext(false)
                .verifyComplete();

        verify(userRepository, times(1)).findByUsername("existinguser");
        verify(userRepository, times(1)).findByUsername("nonexistinguser");
    }

    @Test
    void testAssignRolesToUser() {
        Long userId = 1L;
        java.util.List<Long> roleIds = Arrays.asList(1L, 2L);

        when(userRoleRepository.deleteByUserId(userId)).thenReturn(Mono.empty());
        when(userRoleRepository.save(any(UserRole.class))).thenReturn(Mono.just(new UserRole()));

        StepVerifier.create(userService.assignRolesToUser(userId, roleIds))
                .verifyComplete();

        verify(userRoleRepository, times(1)).deleteByUserId(userId);
        verify(userRoleRepository, times(2)).save(any(UserRole.class));
    }
}
