package com.agileboot.system.menu.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import com.agileboot.common.core.enums.common.MenuTypeEnum;
import com.agileboot.common.core.enums.common.StatusEnum;
import com.agileboot.common.core.exception.BizException;
import com.agileboot.common.core.exception.error.ErrorCode;
import com.agileboot.common.core.utils.jackson.JacksonUtil;
import com.agileboot.common.satoken.pojo.LoginUser;
import com.agileboot.system.menu.dto.*;
import com.agileboot.system.menu.entity.SysMenu;
import com.agileboot.system.menu.mapper.SysMenuMapper;
import com.agileboot.system.menu.service.ISysMenuService;
import com.agileboot.system.menu.vo.RouterVO;
import com.agileboot.system.role.mapper.SysRoleMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 菜单应用服务
 *
 * @author valarchie
 */
@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements ISysMenuService {

    private final SysRoleMapper roleMapper;

    @Override
    public List<MenuDTO> getMenuList(MenuQuery query) {
        List<SysMenu> list = super.list(query.toQueryWrapper());
        return list.stream().map(MenuDTO::new)
                .sorted(Comparator.comparing(MenuDTO::getRank, Comparator.nullsLast(Integer::compareTo)))
                .collect(Collectors.toList());
    }

    @Override
    public MenuDetailDTO getMenuInfo(Long menuId) {
        SysMenu sysMenu = super.getById(menuId);
        return new MenuDetailDTO(sysMenu);
    }

    @Override
    public List<Tree<Long>> getDropdownList(LoginUser loginUser) {
        List<SysMenu> menuEntityList =
//                loginUser.isAdmin() ?
                super.list();
//                        :
//                        this.baseMapper.selectMenuListByUserId(loginUser.getUserId());

        return buildMenuTreeSelect(menuEntityList);
    }

    @Override
    public void addMenu(AddMenuDTO addCommand) {
        SysMenu entity = new SysMenu();
        BeanUtils.copyProperties(addCommand, entity, "menuId");
        String metaInfo = JacksonUtil.to(addCommand.getMeta());
        entity.setMetaInfo(metaInfo);

        this.checkMenuNameUnique(entity.getMenuName(), entity.getMenuId(), entity.getParentId());
        this.checkAddButtonInIframeOrOutLink(entity.getIsButton(), entity.getParentId());
        this.checkAddMenuNotInCatalog(entity.getIsButton(), entity.getParentId());

        super.save(entity);
    }

    @Override
    public void updateMenu(UpdateMenuDTO dto) {
        Long menuId = dto.getMenuId();
        SysMenu byId = super.getById(menuId);
        if (byId == null) {
            throw new BizException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, menuId, "菜单");
        }
        if (!Objects.equals(byId.getMenuType(), dto.getMenuType()) && !byId.getIsButton()) {
            throw new BizException(ErrorCode.Business.MENU_CAN_NOT_CHANGE_MENU_TYPE);
        }
        BeanUtil.copyProperties(dto, byId, "menuId");
        String metaInfo = JacksonUtil.to(dto.getMeta());
        byId.setMetaInfo(metaInfo);

        this.checkMenuNameUnique(byId.getMenuName(), byId.getMenuId(), byId.getParentId());
        this.checkAddButtonInIframeOrOutLink(byId.getIsButton(), byId.getParentId());
        this.checkAddMenuNotInCatalog(byId.getIsButton(), byId.getParentId());
        this.checkParentIdConflict(byId.getMenuId(), byId.getParentId());

        super.updateById(byId);
    }

    @Override
    public void remove(Long menuId) {
        // 是否存在菜单子节点
        if (super.lambdaQuery().eq(SysMenu::getParentId, menuId).exists()) {
            throw new BizException(ErrorCode.Business.MENU_EXIST_CHILD_MENU_NOT_ALLOW_DELETE);
        }
        // 查询菜单是否存在角色
        if (roleMapper.isMenuAssignToRoles(menuId)) {
            throw new BizException(ErrorCode.Business.MENU_ALREADY_ASSIGN_TO_ROLE_NOT_ALLOW_DELETE);
        }
        super.removeById(menuId);
    }

    @Override
    public List<Long> getMenuIdsByRoleId(Long roleId) {
        return this.baseMapper.selectMenuIdsByRoleId(roleId);
    }

    @Override
    public List<RouterVO> getRouterTree(LoginUser loginUser) {
        List<Tree<Long>> trees = buildMenuEntityTree(loginUser);
        return buildRouterTree(trees);
    }

    public List<Tree<Long>> buildMenuEntityTree(LoginUser loginUser) {
        List<SysMenu> allMenus;
        if (loginUser.getIsAdmin() == 1) {
            allMenus = super.list();
        } else {
            allMenus = this.baseMapper.selectMenuListByUserId(loginUser.getUserId());
        }

        // 传给前端的路由排除掉按钮和停用的菜单
        List<SysMenu> noButtonMenus = allMenus.stream()
                .filter(menu -> !menu.getIsButton())
                .filter(menu -> StatusEnum.ENABLE.getValue().equals(menu.getStatus()))
                .collect(Collectors.toList());

        TreeNodeConfig config = new TreeNodeConfig();
        // 默认为id 可以不设置
        config.setIdKey("menuId");

        return TreeUtil.build(noButtonMenus, 0L, config, (menu, tree) -> {
            // 也可以使用 tree.setId(dept.getId());等一些默认值
            tree.setId(menu.getMenuId());
            tree.setParentId(menu.getParentId());
            // TODO 可以取meta中的rank来排序
//            tree.setWeight(menu.getRank());
            tree.putExtra("entity", menu);
        });

    }


    public List<RouterVO> buildRouterTree(List<Tree<Long>> trees) {
        List<RouterVO> routers = new LinkedList<>();
        if (CollUtil.isNotEmpty(trees)) {
            for (Tree<Long> tree : trees) {
                Object entity = tree.get("entity");
                if (entity != null) {
                    RouterVO routerDTO = new RouterVO((SysMenu) entity);
                    List<Tree<Long>> children = tree.getChildren();
                    if (CollUtil.isNotEmpty(children)) {
                        routerDTO.setChildren(buildRouterTree(children));
                    }
                    routers.add(routerDTO);
                }

            }
        }
        return routers;
    }

    /**
     * 构建前端所需要树结构
     *
     * @param menus 菜单列表
     * @return 树结构列表
     */
    private List<Tree<Long>> buildMenuTreeSelect(List<SysMenu> menus) {
        TreeNodeConfig config = new TreeNodeConfig();
        //默认为id可以不设置
        config.setIdKey("menuId");
        return TreeUtil.build(menus, 0L, config, (menu, tree) -> {
            // 也可以使用 tree.setId(dept.getId());等一些默认值
            tree.setId(menu.getMenuId());
            tree.setParentId(menu.getParentId());
            tree.putExtra("label", menu.getMenuName());
        });
    }

    private void checkMenuNameUnique(String menuName, Long menuId, Long parentId) {
        LambdaQueryWrapper<SysMenu> queryWrapper = Wrappers.lambdaQuery(SysMenu.class)
                .eq(SysMenu::getMenuName, menuName)
                .ne(menuId != null, SysMenu::getMenuId, menuId)
                .eq(parentId != null, SysMenu::getParentId, parentId);
        if (this.baseMapper.exists(queryWrapper)) {
            throw new BizException(ErrorCode.Business.MENU_NAME_IS_NOT_UNIQUE, menuName);
        }
    }

    private void checkParentIdConflict(Long menuId, Long parentId) {
        if (menuId.equals(parentId)) {
            throw new BizException(ErrorCode.Business.MENU_PARENT_ID_NOT_ALLOW_SELF);
        }
    }

    /**
     * Iframe和外链跳转类型  不允许添加按钮
     */
    private void checkAddButtonInIframeOrOutLink(Boolean isButton, Long parentId) {
        SysMenu parentMenu = super.getById(parentId);

        if (parentMenu != null && isButton
                && (Objects.equals(parentMenu.getMenuType(), MenuTypeEnum.IFRAME.getValue())
                || Objects.equals(parentMenu.getMenuType(), MenuTypeEnum.OUTSIDE_LINK_REDIRECT.getValue())
        )) {
            throw new BizException(ErrorCode.Business.MENU_NOT_ALLOWED_TO_CREATE_BUTTON_ON_IFRAME_OR_OUT_LINK);
        }
    }

    /**
     * 只允许在目录菜单类型底下 添加子菜单
     */
    private void checkAddMenuNotInCatalog(Boolean isButton, Long parentId) {
        SysMenu parentMenu = super.getById(parentId);

        if (parentMenu != null && !isButton
                && (!Objects.equals(parentMenu.getMenuType(), MenuTypeEnum.CATALOG.getValue())
        )) {
            throw new BizException(ErrorCode.Business.MENU_ONLY_ALLOWED_TO_CREATE_SUB_MENU_IN_CATALOG);
        }
    }
}
