package cn.reghao.oss.console.account.service.impl;

import cn.reghao.oss.console.account.db.repository.MenuRepository;
import cn.reghao.oss.console.account.db.repository.RoleRepository;
import cn.reghao.oss.console.account.model.constant.MenuType;
import cn.reghao.oss.console.account.service.MenuService;
import cn.reghao.jutil.jdk.result.Result;
import cn.reghao.jutil.jdk.result.ResultStatus;
import cn.reghao.oss.console.account.model.dto.MenuDTO;
import cn.reghao.oss.console.account.model.po.Menu;
import cn.reghao.oss.console.account.model.po.Role;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author reghao
 * @date 2021-05-17 17:16:19
 */
@Slf4j
@Service
public class MenuServiceImpl implements MenuService {
    private final MenuRepository menuRepository;
    private final RoleRepository roleRepository;

    public MenuServiceImpl(MenuRepository menuRepository, RoleRepository roleRepository) {
        this.menuRepository = menuRepository;
        this.roleRepository = roleRepository;
    }

    @Override
    public synchronized Result addMenu(Menu menu) {
        Result result = moreThanTwoParents(menu.getPid());
        if (result.getCode() != ResultStatus.SUCCESS.getCode()) {
            return result;
        }

        // 调整 menu 组内元素的位置
        insertNewMenu(menu);
        menu.setEnabled(true);
        Menu menuEntity = menuRepository.save(menu);

        //Set<Role> roles = menu.getRoles();
        Set<Role> roles = Collections.emptySet();
        roles.forEach(role -> role.getMenus().add(menuEntity));
        roleRepository.saveAll(new ArrayList<>(roles));
        return Result.result(ResultStatus.SUCCESS);
    }

    /**
     * 检查菜单的层级, Menu 最多只能有两个 parent, 即最多只能有三级菜单
     *
     * @param
     * @return
     * @date 2021-07-15 上午11:15
     */
    private Result moreThanTwoParents(int pid) {
        if (pid != 0) {
            Menu menu1 = menuRepository.getOne(pid);
            int pid1 = menu1.getPid();
            if (pid1 != 0) {
                Menu menu2 = menuRepository.getOne(pid1);
                int pid2 = menu2.getPid();
                if (pid2 != 0) {
                    return Result.result(ResultStatus.FAIL, "Menu 最多只能有两个 parent，即最多只能有三级菜单");
                }
            }
        }

        return Result.result(ResultStatus.SUCCESS);
    }

    /**
     * 向 menu 组插入元素时调整组内 menu 的位置
     *
     * @date 2021-05-18 上午9:54
     */
    private void insertNewMenu(Menu menu) {
        int pid = menu.getPid();
        if (pid != 0) {
            Menu pMenu = menuRepository.getOne(pid);
            if (pMenu.getType().equals(MenuType.page.name())) {
                log.error("父级菜单的类型不能是 PAGE");
                return;
            }
        }

        List<Menu> menus = menuRepository.findByPid(pid);
        menus.sort(Comparator.comparingInt(Menu::getPos));
        // 组内没有 menu 或新增的 menu 在组内排在最后一位
        if (menus.isEmpty()) {
            menu.setPos(1);
            return;
        }

        // menu 的新位置
        int pos = menu.getPos()+1;
        int size = menus.size();
        if (pos == 1) {
            // menu 的位置在 menu 组的首位, 组内的其他元素位置向后移一位
            menu.setPos(1);
            moveBackward(menus, 1);
        } else if (pos >= size+1) {
            // menu 的位置在 menu 组的末尾, 组内的其他元素位置不变
            menu.setPos(size+1);
        } else {
            // menu 的位置在 menu 组的中间, pos 开始的元素位置向后移一位
            menu.setPos(pos);
            moveBackward(menus, pos);
        }
    }

    /**
     * 向后移动元素(向 menu 组新增元素时调用)
     *
     * @return
     * @date 2021-07-15 下午5:07
     */
    private void moveBackward(List<Menu> menus, int startPos) {
        for (int i = startPos-1; i < menus.size(); i++) {
            Menu tmpMenu = menus.get(i);
            int newPos = tmpMenu.getPos()+1;
            tmpMenu.setPos(newPos);
        }
        menuRepository.saveAll(menus);
    }

    /**
     * 向前移动元素(从 menu 组删除元素时调用)
     *
     * @param
     * @return
     * @date 2021-07-15 下午5:41
     */
    private void moveForward(List<Menu> menus, int startPos) {
        for (int i = startPos; i < menus.size(); i++) {
            Menu tmpMenu = menus.get(i);
            int newPos = tmpMenu.getPos()-1;
            tmpMenu.setPos(newPos);
        }
        menuRepository.saveAll(menus);
    }

    @Override
    public synchronized Result updateMenu(MenuDTO menuDTO) {
        int menuId = menuDTO.getMenuId();
        Menu menuEntity = menuRepository.getOne(menuId);
        if (menuEntity == null) {
            return Result.result(ResultStatus.FAIL, String.format("ID 为 %s 的 Menu 不存在", menuId));
        }

        int oldPos = menuEntity.getPos();
        // menu 的新位置
        int newPos = menuDTO.getPos()+1;
        int pid = menuDTO.getPid();
        if (pid != menuEntity.getPid()) {
            Result result = moreThanTwoParents(pid);
            if (result.getCode() != ResultStatus.SUCCESS.getCode()) {
                return result;
            }

            // menu 更换到了新的 menu 组
            menuEntity.setPid(pid);
            menuEntity.setPos(menuDTO.getPos());
            insertNewMenu(menuEntity);
        } else if (newPos != oldPos) {
            reOrderMenus(pid, oldPos, newPos);
            menuEntity.setPos(newPos);
        }

        menuEntity.setName(menuDTO.getName());
        menuEntity.setUrl(menuDTO.getUrl());
        menuEntity.setIcon(menuDTO.getIcon());
        menuEntity.setUpdateTime(LocalDateTime.now());
        menuRepository.save(menuEntity);

        Set<Role> roles = menuDTO.getRoles();
        roles.forEach(role -> role.getMenus().add(menuEntity));
        roleRepository.saveAll(new ArrayList<>(roles));
        return Result.result(ResultStatus.SUCCESS);
    }

    public void updateMenusStatus(boolean status, List<Integer> menuIds) {
        menuRepository.findAllById(menuIds).forEach(menu -> {
            menu.setEnabled(status);
            menuRepository.save(menu);
        });
    }

    /**
     * 对组内的菜单进行重排序，只需调整 oldPos～newPos 之间的元素位置
     *
     * @param
     * @return
     * @date 2021-07-21 上午11:07
     */
    private void reOrderMenus(int pid, int oldPos, int newPos) {
        Map<Integer, Menu> map = menuRepository.findByPid(pid).stream()
                .collect(Collectors.toMap(Menu::getPos, menu -> menu));
        map.remove(oldPos);
        if (newPos < oldPos) {
            // 向前移动
            for (int i = newPos, j = 1; i < oldPos; i++, j++) {
                map.get(i).setPos(newPos+j);
            }
        } else {
            // 向后移动
            for (int i = newPos-1, j = 1; i > oldPos; i--, j++) {
                map.get(i).setPos(newPos-j);
            }
        }
        menuRepository.saveAll(new ArrayList<>(map.values()));
    }

    @Override
    public synchronized Result deleteMenu(Integer menuId) {
        Menu menu = menuRepository.getOne(menuId);

        // 删除 Role 关联的 Menu
        for (Role role : menu.getRoles()) {
            role.getMenus().remove(menu);
            roleRepository.save(role);
        }

        // 重新调整组内排序
        int pid = menu.getPid();
        List<Menu> menus = menuRepository.findByPid(pid);
        menus.sort(Comparator.comparingInt(Menu::getPos));
        int pos = menu.getPos();
        moveForward(menus, pos);
        menuRepository.delete(menu);
        return Result.result(ResultStatus.SUCCESS);
    }
}
