package com.sdtool.business.service;

import com.sdtool.business.dao.SystemDao;
import com.sdtool.common.api.MenuService;
import com.sdtool.common.api.NoSqlKeysService;
import com.sdtool.common.api.PermissionService;
import com.sdtool.common.api.SupplierInitService;
import com.sdtool.common.base.BaseService;
import com.sdtool.common.datas.RedisKeys;
import com.sdtool.common.entity.base.ZTreeData;
import com.sdtool.common.entity.site.ERPTokenUser;
import com.sdtool.common.entity.system.Menu;
import com.sdtool.common.tool.ERPUtils;
import com.sdtool.common.tool.TreeMenuUtils;
import com.dySweetFishPlugin.redis.RedisService;
import com.dySweetFishPlugin.sql.TableIdService;
import com.dySweetFishPlugin.sql.dao.TunaService;
import com.dySweetFishPlugin.tool.lang.ThreadFactoryWithNamePrefix;
import com.sweetfish.convert.json.JsonConvert;
import com.sweetfish.service.Local;
import com.sweetfish.service.RetResult;
import com.sweetfish.util.AnyValue;
import com.sweetfish.util.AutoLoad;
import com.sweetfish.util.ResourceType;
import org.apache.commons.lang3.StringUtils;
import org.rex.RMap;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@AutoLoad(false)
@Local
@ResourceType(MenuService.class)
public class MenuServiceImpl extends BaseService implements MenuService {

    @Resource
    RedisService redisService;

    @Resource
    NoSqlKeysService keysService;

    @Resource
    JsonConvert jsonConvert;

    @Resource
    TableIdService tableIdService;

    @Resource
    TunaService tunaService;

    @Resource
    PermissionService permissionService;

    @Resource
    SupplierInitService supplierInitService;

    private SystemDao systemDao;

    private ExecutorService menuExecutor;

    @Override
    public void init(AnyValue config) {
        super.init(config);
        menuExecutor = Executors.newFixedThreadPool(2, new ThreadFactoryWithNamePrefix("[菜单删除线程池]"));
    }

    @Override
    public void destroy(AnyValue config) {
        super.destroy(config);
        if (menuExecutor != null) {
            menuExecutor.shutdown();
        }
    }

    @Override
    public void start(AnyValue config) {
        systemDao = tunaService.generate(SystemDao.class);
    }

    //region 菜单管理

    private Menu getRedisMenu(long menuId, long supplierCode) {
        String json = redisService.hget(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode), String.valueOf(menuId));

        if (StringUtils.isEmpty(json)) {
            return null;
        }

        return jsonConvert.convertFrom(Menu.class, json);
    }

    private String getRedisMenuName(long menuId, long supplierCode) {
        Menu menu = getRedisMenu(menuId, supplierCode);

        if (menu == null) {
            return "";
        } else {
            return menu.getMenuName();
        }
    }

    /**
     * 获取菜单列表，供增加、编辑、删除使用
     *
     * @param searchData 查询条件
     * @return 菜单列表
     */
    @Override
    @SuppressWarnings("rawtypes")
    public RetResult<List<Menu>> getMenuList(RMap searchData, String dataSourceId, long supplierCode) {
        String searchMenuName = "";
        int searchStatus = -1;

        if ((searchData != null) && (searchData.containsKey("menuName"))) {
            searchMenuName = searchData.getString("menuName");
        }

        if ((searchData != null) && (searchData.containsKey("status"))) {
            searchStatus = searchData.getInt("status");
        }

        Map<String, String> menuMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode));
        List<Menu> menuList = new ArrayList<>();

        final String finalMenuName = searchMenuName;
        final int finalStatus = searchStatus;

        menuMap.forEach((k, v) -> {
            Menu menu = jsonConvert.convertFrom(Menu.class, v);
            boolean canAdd = true;

            if (StringUtils.isNotEmpty(finalMenuName)) {
                canAdd = menu.getMenuName().contains(finalMenuName);
            }

            if (finalStatus >= 0) {
                canAdd = canAdd && (menu.getVisible() == finalStatus);
            }

            if (canAdd) {
                menuList.add(menu);
            }
        });

        //排序
        menuList.sort((v1, v2) -> {
            if (v1.getParentId() == v2.getParentId()) {
                return Integer.compare(v1.getSortNum(), v2.getSortNum());
            } else {
                return Long.compare(v1.getParentId(), v2.getParentId());
            }
        });

        return RetResult.<List<Menu>>successT().result(menuList);
    }

    @Override
    public RetResult<List<ZTreeData>> getMenuTreeData(String dataSourceId, long supplierCode) {
        Map<String, String> menuMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode));
        List<ZTreeData> menuList = new ArrayList<>();
        menuMap.forEach((k, v) -> {
            Menu menu = jsonConvert.convertFrom(Menu.class, v);
            if (menu.getVisible() == 1) {
                ZTreeData data = new ZTreeData();
                data.setId(menu.getId());
                data.setpId(menu.getParentId());
                data.setName(menu.getMenuName());
                data.setTitle(menu.getMenuName());
                data.setChecked(false);
                data.setSortIndex(menu.getSortNum());

                menuList.add(data);
            }
        });

        //排序
        menuList.sort((v1, v2) -> {
            if (v1.getpId() == v2.getpId()) {
                return Integer.compare(v1.getSortIndex(), v2.getSortIndex());
            } else {
                return Long.compare(v1.getpId(), v2.getpId());
            }
        });
        return RetResult.<List<ZTreeData>>successT().result(menuList);
    }

    @Override
    public RetResult<List<ZTreeData>> getMenuTreeData2(String dataSourceId, long supplierCode) {
        Map<String, String> menuMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode));
        List<ZTreeData> menuList = new ArrayList<>();
        menuMap.forEach((k, v) -> {
            Menu menu = jsonConvert.convertFrom(Menu.class, v);
            if (menu.getVisible() == 1) {
                ZTreeData data = new ZTreeData();
                data.setId(menu.getId());
                data.setpId(menu.getParentId());
                if (StringUtils.isEmpty(menu.getPerms())) {
                    data.setName(menu.getMenuName());
                } else {
                    data.setName(menu.getMenuName() + "<font color=\"#888\">&nbsp;&nbsp;&nbsp;" + menu.getPerms() + "</font>");
                }
                data.setTitle(menu.getMenuName());
                data.setChecked(false);

                menuList.add(data);
            }
        });
        return RetResult.<List<ZTreeData>>successT().result(menuList);
    }

    /**
     * 获取菜单信息
     *
     * @param menuId 菜单id
     * @return 菜单信息
     */
    @Override
    public RetResult<Menu> getMenuInfo(long menuId, String dataSourceId, long supplierCode) {
        Menu menu = getRedisMenu(menuId, supplierCode);

        if (menu != null) {
            menu.setParentName(getRedisMenuName(menu.getParentId(), supplierCode));
        }

        return RetResult.<Menu>successT().result(menu);
    }

    /**
     * 增加菜单
     *
     * @param menu        菜单信息
     * @param currentUser 操作人
     * @return 操作信息
     */
    @Override
    public RetResult<Menu> addMenu(Menu menu, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        menu.setId(tableIdService.getTableIdMulti("tbMenu.id", 1, dataSourceId, String.valueOf(supplierCode)).intValue());
        menu.setVisible(1);

        if (menu.getParentId() <= 0) {
            menu.setParentId(0);
        }

        Menu.create(menu, currentUser.getId());

        redisService.hset(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode), String.valueOf(menu.getId()), jsonConvert.convertTo(menu));

        systemDao.addMenu(menu, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    /**
     * 修改菜单
     *
     * @param menu        菜单信息
     * @param currentUser 操作人
     * @return 操作信息
     */
    @Override
    public RetResult<Menu> updateMenu(Menu menu, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (menu == null) {
            return RetResult.<Menu>errorT().retinfo("请求数据错误");
        }

        if (menu.getId() == 0L) {
            return RetResult.<Menu>errorT().retinfo("请求数据错误");
        }

        if (menu.getId() == menu.getParentId()) {
            return RetResult.<Menu>errorT().retinfo("上级菜单不能为本菜单");
        }

        Menu.update(menu, currentUser.getId());

        Menu redisMenu = getRedisMenu(menu.getId(), supplierCode);
        if (redisMenu != null) {
            redisMenu.setParentId(menu.getParentId());
            redisMenu.setMenuName(menu.getMenuName());
            redisMenu.setSortNum(menu.getSortNum());
            redisMenu.setUrl(menu.getUrl());
            redisMenu.setMenuType(menu.getMenuType());
            redisMenu.setPerms(menu.getPerms());
            redisMenu.setIcon(menu.getIcon());
            redisMenu.setRemark(menu.getRemark());

            redisMenu.setRoute(menu.getRoute());
            redisMenu.setPathName(menu.getPathName());
            redisMenu.setRunKind(menu.getRunKind());
            redisMenu.setCacheFlag(menu.getCacheFlag());
            redisMenu.setDelphiPackage(menu.getDelphiPackage());
            redisMenu.setDelphiParams(menu.getDelphiParams());
            redisMenu.setDelphiRunKind(menu.getDelphiRunKind());
            redisMenu.setDelphiRunForm(menu.getDelphiRunForm());
            redisMenu.setDelphiFormShow(menu.getDelphiFormShow());

            redisMenu.setUpdateBy(menu.getUpdateBy());
            redisMenu.setUpdateTime(menu.getUpdateTime());

            redisService.hset(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode), String.valueOf(redisMenu.getId()), jsonConvert.convertTo(redisMenu));
        }

        systemDao.updateMenu(menu, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    /**
     * 删除菜单
     *
     * @param menu        菜单信息
     * @param currentUser 操作人
     * @return 操作信息
     */
    @Override
    public RetResult<Menu> removeMenu(Menu menu, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (menu == null) {
            return RetResult.<Menu>errorT().retinfo("请求数据错误");
        }

        if (menu.getId() == 0L) {
            return RetResult.<Menu>errorT().retinfo("请求数据错误");
        }

        Menu.update(menu, currentUser.getId());

        Menu redisMenu = getRedisMenu(menu.getId(), supplierCode);
        if (redisMenu != null) {
            redisMenu.setVisible(menu.getVisible());

            redisMenu.setUpdateBy(menu.getUpdateBy());
            redisMenu.setUpdateTime(menu.getUpdateTime());

            redisService.hset(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode), String.valueOf(redisMenu.getId()), jsonConvert.convertTo(redisMenu));
        }

        systemDao.voidMenu(menu, dataSourceId, supplierCode);

        return RetResult.successT();
    }

    /**
     * 请求删除菜单，
     * <p>
     * 删除菜单需要处理以下操作
     * <p>
     * 1、在数据库中删除该菜单和子级菜单tbMenu，如果不删除子级，子级菜单则无法在页面上构造出来，
     * <p>
     * 2、在数据库中删除菜单和子级菜单对应的角色菜单tbRoleMenu
     * <p>
     * 3、重建菜单角色缓存，刷新菜单权限信息
     * <p>
     * 4、如果用户删错了，如何补救？特别是删除了管理员的菜单，会存在没法把菜单加出来的情况，因为SuperResource也是依靠默认的角色权限读取菜单的
     *    系统暂时没有补救机制，光靠id和parentId无法控制，用户可以修改了parentId之后再删除。。。
     * <p>
     * 5、此功能生产环境下还是不提供了，免得错了一次维护半天
     * <p>
     * 这里做一下请求
     *
     * @param menu         菜单信息
     * @param currentUser  操作人
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 待删除的菜单列表
     */
    public RetResult<List<Menu>> requestDeleteMenu(Menu menu, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {

        if (!StringUtils.equalsIgnoreCase("dev", supplierInitService.getRunEnvironment())) {
            return RetResult.<List<Menu>>errorT().retinfo("无效的操作信息");
        }

        if (menu == null) {
            return RetResult.<List<Menu>>errorT().retinfo("请求数据错误");
        }

        if (menu.getId() == 0) {
            return RetResult.<List<Menu>>errorT().retinfo("请求数据错误");
        }

        if (menu.getId() == 2) {
            return RetResult.<List<Menu>>errorT().retinfo("系统菜单不允许删除");
        }

        Menu srcMenu = getRedisMenu(menu.getId(), supplierCode);
        if (srcMenu == null) {
            return RetResult.<List<Menu>>errorT().retinfo("请求数据错误");
        }

        //获取菜单的所有子集
        Map<String, String> menuMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode));
        List<Menu> menuList = menuMap.values().stream()
                .map((x) -> (Menu) jsonConvert.convertFrom(Menu.class, x))
                .sorted((v1, v2) -> {
                    if (v1.getParentId() == v2.getParentId()) {
                        return Integer.compare(v1.getSortNum(), v2.getSortNum());
                    } else {
                        return Long.compare(v1.getParentId(), v2.getParentId());
                    }
                })
                .toList();

        srcMenu.setChildren(TreeMenuUtils.generateTreeMenus(menuList, srcMenu.getId()));

        //展开数据
        List<Menu> resultList = Menu.flattenList(srcMenu);
        resultList.forEach((x) -> {
            x.setChildren(null);
        });
        //排序
        //排序
        resultList.sort((v1, v2) -> {
            if (v1.getParentId() == v2.getParentId()) {
                return Integer.compare(v1.getSortNum(), v2.getSortNum());
            } else {
                return Long.compare(v1.getParentId(), v2.getParentId());
            }
        });
        return RetResult.<List<Menu>>successT().result(resultList);
    }

    /**
     * 删除菜单
     *
     * @param menu         菜单信息
     * @param currentUser  操作人
     * @param dataSourceId 分库
     * @param supplierCode 分表
     * @return 待删除的菜单列表
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public RetResult<Menu> deleteMenu(Menu menu, ERPTokenUser currentUser, String dataSourceId, long supplierCode) {
        if (!StringUtils.equalsIgnoreCase("dev", supplierInitService.getRunEnvironment())) {
            return RetResult.<Menu>errorT().retinfo("无效的操作信息");
        }

        if (menu == null) {
            return RetResult.<Menu>errorT().retinfo("请求数据错误");
        }

        if (menu.getId() == 0L) {
            return RetResult.<Menu>errorT().retinfo("请求数据错误");
        }

        if (menu.getId() == 2) {
            return RetResult.<Menu>errorT().retinfo("系统菜单不允许删除");
        }

        Menu srcMenu = getRedisMenu(menu.getId(), supplierCode);
        if (srcMenu == null) {
            return RetResult.<Menu>errorT().retinfo("请求数据错误");
        }

        CompletableFuture.runAsync(() -> {

            //获取菜单的所有子集
            Map<String, String> menuMap = redisService.hgetAll(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode));
            List<Menu> menuList = menuMap.values().stream()
                    .map((x) -> (Menu) jsonConvert.convertFrom(Menu.class, x))
                    .sorted((v1, v2) -> {
                        if (v1.getParentId() == v2.getParentId()) {
                            return Integer.compare(v1.getSortNum(), v2.getSortNum());
                        } else {
                            return Long.compare(v1.getParentId(), v2.getParentId());
                        }
                    })
                    .toList();

            srcMenu.setChildren(TreeMenuUtils.generateTreeMenus(menuList, srcMenu.getId()));

            //展开数据
            List<Menu> resultList = Menu.flattenList(srcMenu);


            List<Integer> menuIdList = resultList.stream().map(Menu::getId).toList();

            redisService.hdel(keysService.getRedisKey(RedisKeys.KEY_SYSMENUS, supplierCode), ERPUtils.intArrayToStrArray(ERPUtils.intListToArray(menuIdList)));
            RMap deleteParams = new RMap<>();
            deleteParams.put("ids", menuIdList);
            systemDao.deleteMenu(deleteParams, dataSourceId, supplierCode);

            //重建下菜单缓存
            permissionService.buildRoleCache(true, dataSourceId, supplierCode);


        }, menuExecutor).whenComplete((v, e) -> {
            if (e != null) {
                logger.error(e.getMessage(), e);
            }
        });


        return RetResult.success();
    }

    //endregion
}
