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

import cn.novalon.gym.manage.sys.audit.service.IAuditLogService;
import cn.novalon.gym.manage.common.util.StatusConstants;
import cn.novalon.gym.manage.sys.core.domain.SysRole;
import cn.novalon.gym.manage.sys.core.query.SysRoleQuery;
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.repository.ISysRolePermissionRepository;
import cn.novalon.gym.manage.sys.core.service.ISysUserService;
import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.time.LocalDateTime;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

/**
 * 角色服务单元测试类
 * 
 * @author 张翔
 * @date 2026-03-14
 */
@ExtendWith(MockitoExtension.class)
class SysRoleServiceTest {

    @Mock
    private ISysRoleRepository roleRepository;

    @Mock
    private ISysUserService userService;

    @Mock
    private IUserRoleRepository userRoleRepository;

    @Mock
    private ISysRolePermissionRepository rolePermissionRepository;

    @Mock
    private IAuditLogService auditLogService;

    private SysRoleService roleService;

    private SysRole testRole;

    @BeforeEach
    void setUp() {
        roleService = new SysRoleService(roleRepository, userService, userRoleRepository, rolePermissionRepository, auditLogService);

        testRole = new SysRole();
        testRole.setId(1L);
        testRole.setRoleName("admin");
        testRole.setRoleKey("admin");
        testRole.setStatus(StatusConstants.ENABLED);
        testRole.setCreatedAt(LocalDateTime.now());
        testRole.setUpdatedAt(LocalDateTime.now());
    }

    @Test
    void testFindById() {
        when(roleRepository.findById(1L)).thenReturn(Mono.just(testRole));

        StepVerifier.create(roleService.findById(1L))
                .expectNext(testRole)
                .verifyComplete();

        verify(roleRepository).findById(1L);
    }

    @Test
    void testFindAll() {
        when(roleRepository.findAll()).thenReturn(Flux.just(testRole));

        StepVerifier.create(roleService.findAll())
                .expectNext(testRole)
                .verifyComplete();

        verify(roleRepository).findAll();
    }

    @Test
    void testFindRolesByPage() {
        PageRequest pageRequest = new PageRequest();
        pageRequest.setPage(0);
        pageRequest.setSize(10);
        pageRequest.setKeyword("admin");

        PageResponse<SysRole> pageResponse = new PageResponse<>();
        pageResponse.setContent(List.of(testRole));
        pageResponse.setTotalElements(1L);

        when(roleRepository.findByQueryWithPagination(any(SysRoleQuery.class), eq(pageRequest)))
                .thenReturn(Mono.just(pageResponse));

        StepVerifier.create(roleService.findRolesByPage(pageRequest))
                .expectNextMatches(response -> response.getTotalElements() == 1L)
                .verifyComplete();

        verify(roleRepository).findByQueryWithPagination(any(SysRoleQuery.class), eq(pageRequest));
    }

    @Test
    void testCount() {
        when(roleRepository.count()).thenReturn(Mono.just(5L));

        StepVerifier.create(roleService.count())
                .expectNext(5L)
                .verifyComplete();

        verify(roleRepository).count();
    }

    @Test
    void testCreateRole() {
        SysRole newRole = new SysRole();
        newRole.setRoleName("user");
        newRole.setRoleKey("user");

        when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole));

        StepVerifier.create(roleService.createRole(newRole))
                .expectNextMatches(role -> 
                    role.getStatus().equals(StatusConstants.ENABLED) &&
                    role.getCreatedAt() != null)
                .verifyComplete();

        verify(roleRepository).save(any(SysRole.class));
    }

    @Test
    void testFindRolesByPage_WithKeyword() {
        PageRequest pageRequest = new PageRequest();
        pageRequest.setPage(0);
        pageRequest.setSize(10);
        pageRequest.setKeyword("admin");

        PageResponse<SysRole> pageResponse = new PageResponse<>();
        pageResponse.setContent(List.of(testRole));
        pageResponse.setTotalElements(1L);

        when(roleRepository.findByQueryWithPagination(any(cn.novalon.gym.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest)))
                .thenReturn(Mono.just(pageResponse));

        StepVerifier.create(roleService.findRolesByPage(pageRequest))
                .expectNextMatches(response -> response.getTotalElements() == 1L)
                .verifyComplete();

        verify(roleRepository).findByQueryWithPagination(any(cn.novalon.gym.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest));
    }

    @Test
    void testFindRolesByPage_WithoutKeyword() {
        PageRequest pageRequest = new PageRequest();
        pageRequest.setPage(0);
        pageRequest.setSize(10);

        PageResponse<SysRole> pageResponse = new PageResponse<>();
        pageResponse.setContent(List.of(testRole));
        pageResponse.setTotalElements(1L);

        when(roleRepository.findByQueryWithPagination(any(cn.novalon.gym.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest)))
                .thenReturn(Mono.just(pageResponse));

        StepVerifier.create(roleService.findRolesByPage(pageRequest))
                .expectNextMatches(response -> response.getTotalElements() == 1L)
                .verifyComplete();

        verify(roleRepository).findByQueryWithPagination(any(cn.novalon.gym.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest));
    }

    @Test
    void testFindRolesByPage_WithEmptyKeyword() {
        PageRequest pageRequest = new PageRequest();
        pageRequest.setPage(0);
        pageRequest.setSize(10);
        pageRequest.setKeyword("");

        PageResponse<SysRole> pageResponse = new PageResponse<>();
        pageResponse.setContent(List.of(testRole));
        pageResponse.setTotalElements(1L);

        when(roleRepository.findByQueryWithPagination(any(cn.novalon.gym.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest)))
                .thenReturn(Mono.just(pageResponse));

        StepVerifier.create(roleService.findRolesByPage(pageRequest))
                .expectNextMatches(response -> response.getTotalElements() == 1L)
                .verifyComplete();

        verify(roleRepository).findByQueryWithPagination(any(cn.novalon.gym.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest));
    }

    @Test
    void testUpdateRoleWithCommand_WithAllFields() {
        SysRole existingRole = new SysRole();
        existingRole.setId(1L);
        existingRole.setRoleName("oldrole");
        existingRole.setRoleKey("oldkey");
        existingRole.setRoleSort(1);
        existingRole.setStatus(StatusConstants.ENABLED);

        when(roleRepository.findById(1L)).thenReturn(Mono.just(existingRole));
        when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole));

        cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand command = 
            new cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand(
                1L, "newrole", "newkey", 2, StatusConstants.DISABLED
            );

        StepVerifier.create(roleService.updateRole(command))
                .expectNextMatches(role -> role.getUpdatedAt() != null)
                .verifyComplete();

        verify(roleRepository).findById(1L);
        verify(roleRepository).updateRole(any(SysRole.class));
    }

    @Test
    void testUpdateRoleWithCommand_WithPartialFields() {
        SysRole existingRole = new SysRole();
        existingRole.setId(1L);
        existingRole.setRoleName("oldrole");
        existingRole.setRoleKey("oldkey");
        existingRole.setRoleSort(1);
        existingRole.setStatus(StatusConstants.ENABLED);

        when(roleRepository.findById(1L)).thenReturn(Mono.just(existingRole));
        when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole));

        cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand command = 
            new cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand(
                1L, null, null, null, null
            );

        StepVerifier.create(roleService.updateRole(command))
                .expectNextMatches(role -> role.getUpdatedAt() != null)
                .verifyComplete();

        verify(roleRepository).findById(1L);
        verify(roleRepository).updateRole(any(SysRole.class));
    }

    @Test
    void testUpdateRole() {
        SysRole updateRole = new SysRole();
        updateRole.setId(1L);
        updateRole.setRoleName("updated_admin");

        when(roleRepository.findById(1L)).thenReturn(Mono.just(testRole));
        when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole));

        StepVerifier.create(roleService.updateRole(updateRole))
                .expectNextMatches(role -> role.getUpdatedAt() != null)
                .verifyComplete();

        verify(roleRepository).findById(1L);
        verify(roleRepository).updateRole(any(SysRole.class));
    }

    @Test
    void testDeleteRole() {
        when(roleRepository.findById(1L)).thenReturn(Mono.just(testRole));
        when(userRoleRepository.deleteByRoleId(1L)).thenReturn(Mono.empty());
        when(rolePermissionRepository.deleteByRoleId(1L)).thenReturn(Mono.empty());
        when(userService.updateRoleIdToNullByRoleId(1L)).thenReturn(Mono.empty());
        when(roleRepository.deleteById(1L)).thenReturn(Mono.empty());

        StepVerifier.create(roleService.deleteRole(1L))
                .verifyComplete();

        verify(roleRepository).findById(1L);
        verify(userRoleRepository).deleteByRoleId(1L);
        verify(rolePermissionRepository).deleteByRoleId(1L);
        verify(userService).updateRoleIdToNullByRoleId(1L);
        verify(roleRepository).deleteById(1L);
    }

    @Test
    void testFindByRoleName() {
        when(roleRepository.findByRoleName("admin")).thenReturn(Mono.just(testRole));

        StepVerifier.create(roleService.findByRoleName("admin"))
                .expectNext(testRole)
                .verifyComplete();

        verify(roleRepository).findByRoleName("admin");
    }

    @Test
    void testExistsByRoleName_True() {
        when(roleRepository.existsByRoleName("admin")).thenReturn(Mono.just(true));

        StepVerifier.create(roleService.existsByRoleName("admin"))
                .expectNext(true)
                .verifyComplete();

        verify(roleRepository).existsByRoleName("admin");
    }

    @Test
    void testExistsByRoleName_False() {
        when(roleRepository.existsByRoleName("nonexistent")).thenReturn(Mono.just(false));

        StepVerifier.create(roleService.existsByRoleName("nonexistent"))
                .expectNext(false)
                .verifyComplete();

        verify(roleRepository).existsByRoleName("nonexistent");
    }

    @Test
    void testLogicalDeleteRole() {
        when(roleRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.just(testRole));
        when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole));

        StepVerifier.create(roleService.logicalDeleteRole(1L))
                .expectNextMatches(role -> role.getDeletedAt() != null)
                .verifyComplete();

        verify(roleRepository).findByIdIncludingDeleted(1L);
        verify(roleRepository).updateRole(any(SysRole.class));
    }

    @Test
    void testRestoreRole() {
        SysRole deletedRole = new SysRole();
        deletedRole.setId(1L);
        deletedRole.setDeletedAt(LocalDateTime.now());

        when(roleRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.just(deletedRole));
        when(roleRepository.updateRole(any(SysRole.class))).thenReturn(Mono.just(testRole));

        StepVerifier.create(roleService.restoreRole(1L))
                .expectNextMatches(role -> role.getDeletedAt() == null)
                .verifyComplete();

        verify(roleRepository).findByIdIncludingDeleted(1L);
        verify(roleRepository).updateRole(any(SysRole.class));
    }

    @Test
    void testCreateRole_WithNullStatus() {
        SysRole newRole = new SysRole();
        newRole.setRoleName("user");
        newRole.setRoleKey("user");
        newRole.setStatus(null);

        when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole));

        StepVerifier.create(roleService.createRole(newRole))
                .expectNextMatches(role -> 
                    role.getStatus().equals(StatusConstants.ENABLED) &&
                    role.getCreatedAt() != null)
                .verifyComplete();

        verify(roleRepository).save(any(SysRole.class));
    }

    @Test
    void testCreateRole_WithExistingStatus() {
        SysRole newRole = new SysRole();
        newRole.setRoleName("user");
        newRole.setRoleKey("user");
        newRole.setStatus(StatusConstants.DISABLED);

        SysRole savedRole = new SysRole();
        savedRole.setId(1L);
        savedRole.setRoleName("user");
        savedRole.setRoleKey("user");
        savedRole.setStatus(StatusConstants.DISABLED);
        savedRole.setCreatedAt(LocalDateTime.now());

        when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(savedRole));

        StepVerifier.create(roleService.createRole(newRole))
                .expectNextMatches(role -> 
                    role.getStatus().equals(StatusConstants.DISABLED) &&
                    role.getCreatedAt() != null)
                .verifyComplete();

        verify(roleRepository).save(any(SysRole.class));
    }

    @Test
    void testCreateRoleWithCommand_WithAllFields() {
        cn.novalon.gym.manage.sys.core.command.CreateRoleCommand command = 
            new cn.novalon.gym.manage.sys.core.command.CreateRoleCommand(
                "manager", "manager", 2, StatusConstants.ENABLED
            );

        SysRole savedRole = new SysRole();
        savedRole.setId(1L);
        savedRole.setRoleName("manager");
        savedRole.setRoleKey("manager");
        savedRole.setRoleSort(2);
        savedRole.setStatus(StatusConstants.ENABLED);
        savedRole.setCreatedAt(LocalDateTime.now());

        when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(savedRole));

        StepVerifier.create(roleService.createRole(command))
                .expectNextMatches(role -> 
                    role.getRoleName().equals("manager") &&
                    role.getRoleKey().equals("manager") &&
                    role.getRoleSort() == 2 &&
                    role.getStatus().equals(StatusConstants.ENABLED) &&
                    role.getCreatedAt() != null)
                .verifyComplete();

        verify(roleRepository).save(any(SysRole.class));
    }

    @Test
    void testCreateRoleWithCommand_WithDefaultStatus() {
        cn.novalon.gym.manage.sys.core.command.CreateRoleCommand command = 
            new cn.novalon.gym.manage.sys.core.command.CreateRoleCommand(
                "viewer", "viewer", 3, null
            );

        SysRole savedRole = new SysRole();
        savedRole.setId(1L);
        savedRole.setRoleName("viewer");
        savedRole.setRoleKey("viewer");
        savedRole.setRoleSort(3);
        savedRole.setStatus(StatusConstants.ENABLED);
        savedRole.setCreatedAt(LocalDateTime.now());

        when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(savedRole));

        StepVerifier.create(roleService.createRole(command))
                .expectNextMatches(role -> 
                    role.getRoleName().equals("viewer") &&
                    role.getRoleKey().equals("viewer") &&
                    role.getRoleSort() == 3 &&
                    role.getStatus().equals(StatusConstants.ENABLED) &&
                    role.getCreatedAt() != null)
                .verifyComplete();

        verify(roleRepository).save(any(SysRole.class));
    }

    @Test
    void testUpdateRoleWithCommand_WhenRoleNotFound() {
        cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand command = 
            new cn.novalon.gym.manage.sys.core.command.UpdateRoleCommand(
                999L, "newrole", "newkey", 2, StatusConstants.DISABLED
            );

        when(roleRepository.findById(999L)).thenReturn(Mono.empty());

        StepVerifier.create(roleService.updateRole(command))
                .expectError(RuntimeException.class)
                .verify();

        verify(roleRepository).findById(999L);
        verify(roleRepository, never()).save(any(SysRole.class));
    }

    @Test
    void testDeleteRole_WhenRoleNotFound() {
        when(roleRepository.findById(1L)).thenReturn(Mono.empty());

        StepVerifier.create(roleService.deleteRole(1L))
                .expectComplete()
                .verify();

        verify(roleRepository).findById(1L);
        verify(userService, never()).updateRoleIdToNullByRoleId(1L);
        verify(roleRepository, never()).deleteById(1L);
    }

    @Test
    void testLogicalDeleteRole_WhenRoleNotFound() {
        when(roleRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.empty());

        StepVerifier.create(roleService.logicalDeleteRole(1L))
                .expectNextCount(0)
                .verifyComplete();

        verify(roleRepository).findByIdIncludingDeleted(1L);
        verify(roleRepository, never()).updateRole(any(SysRole.class));
    }

    @Test
    void testRestoreRole_WhenRoleNotFound() {
        when(roleRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.empty());

        StepVerifier.create(roleService.restoreRole(1L))
                .expectNextCount(0)
                .verifyComplete();

        verify(roleRepository).findByIdIncludingDeleted(1L);
        verify(roleRepository, never()).updateRole(any(SysRole.class));
    }

    @Test
    void testFindById_WhenRoleNotFound() {
        when(roleRepository.findById(999L)).thenReturn(Mono.empty());

        StepVerifier.create(roleService.findById(999L))
                .expectNextCount(0)
                .verifyComplete();

        verify(roleRepository).findById(999L);
    }

    @Test
    void testFindByRoleName_WhenRoleNotFound() {
        when(roleRepository.findByRoleName("nonexistent")).thenReturn(Mono.empty());

        StepVerifier.create(roleService.findByRoleName("nonexistent"))
                .expectNextCount(0)
                .verifyComplete();

        verify(roleRepository).findByRoleName("nonexistent");
    }

    @Test
    void testFindAll_WhenNoRolesExist() {
        when(roleRepository.findAll()).thenReturn(Flux.empty());

        StepVerifier.create(roleService.findAll())
                .expectNextCount(0)
                .verifyComplete();

        verify(roleRepository).findAll();
    }

    @Test
    void testCount_WhenNoRolesExist() {
        when(roleRepository.count()).thenReturn(Mono.just(0L));

        StepVerifier.create(roleService.count())
                .expectNext(0L)
                .verifyComplete();

        verify(roleRepository).count();
    }
}
