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

import cn.novalon.gym.manage.sys.core.domain.SysMenu;
import cn.novalon.gym.manage.sys.core.repository.ISysMenuRepository;
import cn.novalon.gym.manage.sys.core.command.CreateMenuCommand;
import cn.novalon.gym.manage.sys.core.command.UpdateMenuCommand;
import cn.novalon.gym.manage.sys.audit.service.IAuditLogService;
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 static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class SysMenuServiceTest {

    @Mock
    private ISysMenuRepository menuRepository;

    @Mock
    private IAuditLogService auditLogService;

    private SysMenuService menuService;
    private SysMenu testMenu;

    @BeforeEach
    void setUp() {
        menuService = new SysMenuService(menuRepository, auditLogService);

        testMenu = new SysMenu();
        testMenu.setId(1L);
        testMenu.setMenuName("系统管理");
        testMenu.setParentId(0L);
        testMenu.setOrderNum(1);
        testMenu.setMenuType("M");
        testMenu.setPerms("system");
        testMenu.setComponent("system");
        testMenu.setStatus(1);
        testMenu.setCreatedAt(LocalDateTime.now());
    }

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

        Mono<SysMenu> result = menuService.findById(1L);

        StepVerifier.create(result)
                .expectNext(testMenu)
                .verifyComplete();

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

    @Test
    void testFindAll() {
        when(menuRepository.findAll()).thenReturn(Flux.just(testMenu));

        Flux<SysMenu> result = menuService.findAll();

        StepVerifier.create(result)
                .expectNext(testMenu)
                .verifyComplete();

        verify(menuRepository).findAll();
    }

    @Test
    void testFindByParentId() {
        when(menuRepository.findByParentId(0L)).thenReturn(Flux.just(testMenu));

        Flux<SysMenu> result = menuService.findByParentId(0L);

        StepVerifier.create(result)
                .expectNext(testMenu)
                .verifyComplete();

        verify(menuRepository).findByParentId(0L);
    }

    @Test
    void testCreateMenu() {
        when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(testMenu));

        Mono<SysMenu> result = menuService.createMenu(testMenu);

        StepVerifier.create(result)
                .expectNextMatches(menu -> menu.getId().equals(1L) &&
                        menu.getMenuName().equals("系统管理") &&
                        menu.getCreatedAt() != null)
                .verifyComplete();

        verify(menuRepository).save(any(SysMenu.class));
    }

    @Test
    void testCreateMenuWithCommand() {
        CreateMenuCommand command = new CreateMenuCommand(
                0L, "用户管理", "M", 2, "user", "user:manage", 1);

        SysMenu createdMenu = new SysMenu();
        createdMenu.setId(2L);
        createdMenu.setMenuName("用户管理");
        createdMenu.setParentId(0L);
        createdMenu.setOrderNum(2);
        createdMenu.setMenuType("M");
        createdMenu.setPerms("user:manage");
        createdMenu.setComponent("user");
        createdMenu.setStatus(1);
        createdMenu.setCreatedAt(LocalDateTime.now());

        when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(createdMenu));

        Mono<SysMenu> result = menuService.createMenu(command);

        StepVerifier.create(result)
                .expectNextMatches(menu -> menu.getMenuName().equals("用户管理") &&
                        menu.getParentId().equals(0L) &&
                        menu.getCreatedAt() != null)
                .verifyComplete();

        verify(menuRepository).save(any(SysMenu.class));
    }

    @Test
    void testUpdateMenu() {
        when(menuRepository.findById(1L)).thenReturn(Mono.just(testMenu));
        when(menuRepository.update(any(SysMenu.class))).thenReturn(Mono.just(testMenu));

        Mono<SysMenu> result = menuService.updateMenu(testMenu);

        StepVerifier.create(result)
                .expectNextMatches(menu -> menu.getId().equals(1L) &&
                        menu.getUpdatedAt() != null)
                .verifyComplete();

        verify(menuRepository).findById(1L);
        verify(menuRepository).update(any(SysMenu.class));
    }

    @Test
    void testUpdateMenuWithCommand() {
        UpdateMenuCommand command = new UpdateMenuCommand(
                1L, 0L, "系统管理（更新）", "M", 1, "system", "system:manage", 1);

        when(menuRepository.findById(1L)).thenReturn(Mono.just(testMenu));
        when(menuRepository.update(any(SysMenu.class))).thenReturn(Mono.just(testMenu));

        Mono<SysMenu> result = menuService.updateMenu(command);

        StepVerifier.create(result)
                .expectNextMatches(menu -> menu.getMenuName().equals("系统管理（更新）") &&
                        menu.getUpdatedAt() != null)
                .verifyComplete();

        verify(menuRepository).findById(1L);
        verify(menuRepository).update(any(SysMenu.class));
    }

    @Test
    void testUpdateMenuWithCommand_NotFound() {
        UpdateMenuCommand command = new UpdateMenuCommand(
                999L, 0L, "不存在的菜单", "M", 1, "system", "system:manage", 1);

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

        Mono<SysMenu> result = menuService.updateMenu(command);

        StepVerifier.create(result)
                .expectErrorMatches(ex -> ex.getMessage().contains("Menu not found"))
                .verify();

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

    @Test
    void testUpdateMenuWithCommand_WithPartialFields() {
        SysMenu existingMenu = new SysMenu();
        existingMenu.setId(1L);
        existingMenu.setMenuName("系统管理");
        existingMenu.setParentId(0L);
        existingMenu.setOrderNum(1);
        existingMenu.setMenuType("M");
        existingMenu.setPerms("system");
        existingMenu.setComponent("system");
        existingMenu.setStatus(1);

        SysMenu updatedMenu = new SysMenu();
        updatedMenu.setId(1L);
        updatedMenu.setMenuName("系统管理");
        updatedMenu.setParentId(0L);
        updatedMenu.setOrderNum(1);
        updatedMenu.setMenuType("M");
        updatedMenu.setPerms("system");
        updatedMenu.setComponent("system");
        updatedMenu.setStatus(1);
        updatedMenu.setUpdatedAt(LocalDateTime.now());

        when(menuRepository.findById(1L)).thenReturn(Mono.just(existingMenu));
        when(menuRepository.update(any(SysMenu.class))).thenReturn(Mono.just(updatedMenu));

        UpdateMenuCommand command = new UpdateMenuCommand(
                1L, null, null, null, null, null, null, null);

        StepVerifier.create(menuService.updateMenu(command))
                .expectNextMatches(menu -> menu.getUpdatedAt() != null)
                .verifyComplete();

        verify(menuRepository).findById(1L);
        verify(menuRepository).update(any(SysMenu.class));
    }

    @Test
    void testUpdateMenuWithCommand_WithAllFields() {
        SysMenu existingMenu = new SysMenu();
        existingMenu.setId(1L);
        existingMenu.setMenuName("系统管理");
        existingMenu.setParentId(0L);
        existingMenu.setOrderNum(1);
        existingMenu.setMenuType("M");
        existingMenu.setPerms("system");
        existingMenu.setComponent("system");
        existingMenu.setStatus(1);

        SysMenu updatedMenu = new SysMenu();
        updatedMenu.setId(1L);
        updatedMenu.setMenuName("系统管理（更新）");
        updatedMenu.setParentId(2L);
        updatedMenu.setOrderNum(2);
        updatedMenu.setMenuType("C");
        updatedMenu.setPerms("system:manage_updated");
        updatedMenu.setComponent("system_updated");
        updatedMenu.setStatus(0);
        updatedMenu.setUpdatedAt(LocalDateTime.now());

        when(menuRepository.findById(1L)).thenReturn(Mono.just(existingMenu));
        when(menuRepository.update(any(SysMenu.class))).thenReturn(Mono.just(updatedMenu));

        UpdateMenuCommand command = new UpdateMenuCommand(
                1L, 2L, "系统管理（更新）", "C", 2, "system_updated", "system:manage_updated", 0);

        StepVerifier.create(menuService.updateMenu(command))
                .expectNextMatches(menu -> menu.getUpdatedAt() != null)
                .verifyComplete();

        verify(menuRepository).findById(1L);
        verify(menuRepository).update(any(SysMenu.class));
    }

    @Test
    void testDeleteMenu() {
        when(menuRepository.findById(1L)).thenReturn(Mono.just(testMenu));
        when(menuRepository.deleteById(1L)).thenReturn(Mono.empty());

        Mono<Void> result = menuService.deleteMenu(1L);

        StepVerifier.create(result)
                .verifyComplete();

        verify(menuRepository).findById(1L);
        verify(menuRepository).deleteById(1L);
    }

    @Test
    void testBuildMenuTree() {
        SysMenu parentMenu = new SysMenu();
        parentMenu.setId(1L);
        parentMenu.setMenuName("系统管理");
        parentMenu.setParentId(0L);

        SysMenu childMenu = new SysMenu();
        childMenu.setId(2L);
        childMenu.setMenuName("用户管理");
        childMenu.setParentId(1L);

        when(menuRepository.findAll()).thenReturn(Flux.just(parentMenu, childMenu));

        Flux<SysMenu> result = menuService.buildMenuTree(menuService.findAll());

        StepVerifier.create(result)
                .expectNextMatches(menu -> menu.getId().equals(1L) &&
                        menu.getChildren() != null &&
                        menu.getChildren().size() == 1)
                .verifyComplete();
    }

    @Test
    void testFindById_WhenMenuNotFound() {
        when(menuRepository.findById(999L)).thenReturn(Mono.empty());

        Mono<SysMenu> result = menuService.findById(999L);

        StepVerifier.create(result)
                .expectNextCount(0)
                .verifyComplete();

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

    @Test
    void testFindAll_WhenNoMenusExist() {
        when(menuRepository.findAll()).thenReturn(Flux.empty());

        Flux<SysMenu> result = menuService.findAll();

        StepVerifier.create(result)
                .expectNextCount(0)
                .verifyComplete();

        verify(menuRepository).findAll();
    }

    @Test
    void testFindByParentId_WhenNoChildrenExist() {
        when(menuRepository.findByParentId(999L)).thenReturn(Flux.empty());

        Flux<SysMenu> result = menuService.findByParentId(999L);

        StepVerifier.create(result)
                .expectNextCount(0)
                .verifyComplete();

        verify(menuRepository).findByParentId(999L);
    }

    @Test
    void testCreateMenu_WithDefaultStatus() {
        SysMenu newMenu = new SysMenu();
        newMenu.setMenuName("新菜单");
        newMenu.setParentId(0L);
        newMenu.setOrderNum(1);
        newMenu.setMenuType("M");
        newMenu.setPerms("new:menu");
        newMenu.setComponent("new");
        newMenu.setStatus(null);

        SysMenu savedMenu = new SysMenu();
        savedMenu.setId(1L);
        savedMenu.setMenuName("新菜单");
        savedMenu.setParentId(0L);
        savedMenu.setOrderNum(1);
        savedMenu.setMenuType("M");
        savedMenu.setPerms("new:menu");
        savedMenu.setComponent("new");
        savedMenu.setStatus(1);
        savedMenu.setCreatedAt(LocalDateTime.now());

        when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(savedMenu));

        Mono<SysMenu> result = menuService.createMenu(newMenu);

        StepVerifier.create(result)
                .expectNextMatches(menu -> menu.getStatus().equals(1) &&
                        menu.getCreatedAt() != null)
                .verifyComplete();

        verify(menuRepository).save(any(SysMenu.class));
    }

    @Test
    void testCreateMenuWithCommand_WithDefaultStatus() {
        CreateMenuCommand command = new CreateMenuCommand(
                0L, "日志管理", "M", 3, "log", "log:manage", null);

        SysMenu createdMenu = new SysMenu();
        createdMenu.setId(3L);
        createdMenu.setMenuName("日志管理");
        createdMenu.setParentId(0L);
        createdMenu.setOrderNum(3);
        createdMenu.setMenuType("M");
        createdMenu.setPerms("log:manage");
        createdMenu.setComponent("log");
        createdMenu.setStatus(1);
        createdMenu.setCreatedAt(LocalDateTime.now());

        when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(createdMenu));

        Mono<SysMenu> result = menuService.createMenu(command);

        StepVerifier.create(result)
                .expectNextMatches(menu -> menu.getMenuName().equals("日志管理") &&
                        menu.getStatus().equals(1) &&
                        menu.getCreatedAt() != null)
                .verifyComplete();

        verify(menuRepository).save(any(SysMenu.class));
    }

    @Test
    void testBuildMenuTree_WithEmptyTree() {
        when(menuRepository.findAll()).thenReturn(Flux.empty());

        Flux<SysMenu> result = menuService.buildMenuTree(menuService.findAll());

        StepVerifier.create(result)
                .expectNextCount(0)
                .verifyComplete();

        verify(menuRepository).findAll();
    }

    @Test
    void testBuildMenuTree_WithMultiLevelTree() {
        SysMenu rootMenu = new SysMenu();
        rootMenu.setId(1L);
        rootMenu.setMenuName("系统管理");
        rootMenu.setParentId(0L);

        SysMenu level1Menu = new SysMenu();
        level1Menu.setId(2L);
        level1Menu.setMenuName("用户管理");
        level1Menu.setParentId(1L);

        SysMenu level2Menu = new SysMenu();
        level2Menu.setId(3L);
        level2Menu.setMenuName("用户列表");
        level2Menu.setParentId(2L);

        when(menuRepository.findAll()).thenReturn(Flux.just(rootMenu, level1Menu, level2Menu));

        Flux<SysMenu> result = menuService.buildMenuTree(menuService.findAll());

        StepVerifier.create(result)
                .expectNextMatches(menu -> menu.getId().equals(1L) &&
                        menu.getChildren() != null &&
                        menu.getChildren().size() == 1 &&
                        menu.getChildren().get(0).getChildren() != null &&
                        menu.getChildren().get(0).getChildren().size() == 1)
                .verifyComplete();

        verify(menuRepository).findAll();
    }

    @Test
    void testBuildMenuTree_WithMultipleRootMenus() {
        SysMenu root1 = new SysMenu();
        root1.setId(1L);
        root1.setMenuName("系统管理");
        root1.setParentId(0L);

        SysMenu root2 = new SysMenu();
        root2.setId(2L);
        root2.setMenuName("监控管理");
        root2.setParentId(0L);

        SysMenu child1 = new SysMenu();
        child1.setId(3L);
        child1.setMenuName("用户管理");
        child1.setParentId(1L);

        SysMenu child2 = new SysMenu();
        child2.setId(4L);
        child2.setMenuName("性能监控");
        child2.setParentId(2L);

        when(menuRepository.findAll()).thenReturn(Flux.just(root1, root2, child1, child2));

        Flux<SysMenu> result = menuService.buildMenuTree(menuService.findAll());

        StepVerifier.create(result)
                .expectNextCount(2)
                .verifyComplete();

        verify(menuRepository).findAll();
    }
}