package com.hertz.modules.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hertz.common.exception.BusinessException;
import com.hertz.modules.system.dto.MenuDto;
import com.hertz.modules.system.entity.SysMenu;
import com.hertz.modules.system.entity.SysRoleMenu;
import com.hertz.modules.system.mapper.SysMenuMapper;
import com.hertz.modules.system.mapper.SysRoleMapper;
import com.hertz.modules.system.mapper.SysRoleMenuMapper;
import com.hertz.modules.system.service.MenuService;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MenuServiceImpl implements MenuService {
  private final SysMenuMapper menuMapper;
  private final SysRoleMapper roleMapper;
  private final SysRoleMenuMapper roleMenuMapper;

  public MenuServiceImpl(SysMenuMapper menuMapper, SysRoleMapper roleMapper, SysRoleMenuMapper roleMenuMapper) {
    this.menuMapper = menuMapper;
    this.roleMapper = roleMapper;
    this.roleMenuMapper = roleMenuMapper;
  }

  @Override
  public List<MenuDto> getMenuTreeByUserId(long userId) {
    var roleKeys = roleMapper.selectRoleKeysByUserId(userId);
    var menus = roleKeys.stream().anyMatch(r -> "ADMIN".equalsIgnoreCase(r))
        ? menuMapper.selectAllVisibleMenus()
        : menuMapper.selectMenusByUserId(userId);

    var map = new HashMap<Long, MenuDto>();
    for (var m : menus) {
      var dto = new MenuDto();
      dto.setId(m.getId());
      dto.setParentId(m.getParentId());
      dto.setType(m.getType());
      dto.setName(m.getName());
      dto.setPath(m.getPath());
      dto.setComponent(m.getComponent());
      dto.setPerms(m.getPerms());
      dto.setIcon(m.getIcon());
      dto.setSort(m.getSort());
      dto.setStatus(m.getStatus());
      map.put(dto.getId(), dto);
    }

    var roots = new ArrayList<MenuDto>();
    for (var dto : map.values()) {
      var parentId = dto.getParentId() == null ? 0L : dto.getParentId();
      if (parentId == 0L || !map.containsKey(parentId)) {
        roots.add(dto);
      } else {
        map.get(parentId).getChildren().add(dto);
      }
    }

    Comparator<MenuDto> comparator = Comparator
        .comparing((MenuDto d) -> d.getSort() == null ? 0 : d.getSort())
        .thenComparing(d -> d.getId() == null ? 0 : d.getId());
    sortRecursively(roots, comparator);
    return roots;
  }

  @Override
  public IPage<SysMenu> pageMenus(int page, int size, String keyword) {
    var wrapper = new LambdaQueryWrapper<SysMenu>().orderByAsc(SysMenu::getSort);
    if (keyword != null && !keyword.isBlank()) {
      wrapper.like(SysMenu::getName, keyword);
    }
    return menuMapper.selectPage(Page.of(page, size), wrapper);
  }

  @Override
  @Transactional
  public void saveMenu(SysMenu menu) {
    menu.setCreatedAt(java.time.LocalDateTime.now());
    menu.setUpdatedAt(java.time.LocalDateTime.now());
    menuMapper.insert(menu);
  }

  @Override
  @Transactional
  public void updateMenu(SysMenu menu) {
    var existing = menuMapper.selectById(menu.getId());
    if (existing == null) {
      throw new BusinessException(404, "Menu not found");
    }
    existing.setParentId(menu.getParentId());
    existing.setType(menu.getType());
    existing.setName(menu.getName());
    existing.setPath(menu.getPath());
    existing.setComponent(menu.getComponent());
    existing.setPerms(menu.getPerms());
    existing.setIcon(menu.getIcon());
    existing.setSort(menu.getSort());
    existing.setVisible(menu.getVisible());
    existing.setStatus(menu.getStatus());
    existing.setUpdatedAt(java.time.LocalDateTime.now());
    menuMapper.updateById(existing);
  }

  @Override
  @Transactional
  public void deleteMenu(Long id) {
    if (menuMapper.selectCount(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, id)) > 0) {
      throw new BusinessException(400, "Has sub-menus, cannot delete");
    }
    menuMapper.deleteById(id);
    roleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getMenuId, id));
  }

  private void sortRecursively(List<MenuDto> nodes, Comparator<MenuDto> comparator) {
    nodes.sort(comparator);
    for (var n : nodes) {
      if (n.getChildren() != null && !n.getChildren().isEmpty()) {
        sortRecursively(n.getChildren(), comparator);
      }
    }
  }
}

