/*
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package top.charles7c.cnadmin.auth.service.impl;

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

import lombok.RequiredArgsConstructor;

import org.springframework.stereotype.Service;

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.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.json.JSONUtil;

import top.charles7c.cnadmin.auth.model.vo.MetaVO;
import top.charles7c.cnadmin.auth.model.vo.RouteVO;
import top.charles7c.cnadmin.auth.service.LoginService;
import top.charles7c.cnadmin.auth.service.PermissionService;
import top.charles7c.cnadmin.common.annotation.TreeField;
import top.charles7c.cnadmin.common.config.properties.ProjectProperties;
import top.charles7c.cnadmin.common.constant.RegexConsts;
import top.charles7c.cnadmin.common.constant.SysConsts;
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
import top.charles7c.cnadmin.common.enums.GenderEnum;
import top.charles7c.cnadmin.common.enums.MenuTypeEnum;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.util.SecureUtils;
import top.charles7c.cnadmin.common.util.TreeUtils;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
import top.charles7c.cnadmin.common.util.validate.CheckUtils;
import top.charles7c.cnadmin.system.enums.MessageTemplateEnum;
import top.charles7c.cnadmin.system.model.entity.RoleDO;
import top.charles7c.cnadmin.system.model.entity.UserDO;
import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
import top.charles7c.cnadmin.system.model.request.MessageRequest;
import top.charles7c.cnadmin.system.model.vo.DeptDetailVO;
import top.charles7c.cnadmin.system.model.vo.MenuVO;
import top.charles7c.cnadmin.system.service.*;

import me.zhyd.oauth.model.AuthUser;

/**
 * 登录业务实现
 *
 * @author Charles7c
 * @since 2022/12/21 21:49
 */
@Service
@RequiredArgsConstructor
public class LoginServiceImpl implements LoginService {

    private final UserService userService;
    private final DeptService deptService;
    private final RoleService roleService;
    private final MenuService menuService;
    private final PermissionService permissionService;
    private final UserRoleService userRoleService;
    private final UserSocialService userSocialService;
    private final MessageService messageService;
    private final ProjectProperties projectProperties;

    @Override
    public String accountLogin(String username, String password) {
        UserDO user = userService.getByUsername(username);
        CheckUtils.throwIfNull(user, "用户名或密码不正确");
        Long userId = user.getId();
        CheckUtils.throwIfNotEqual(SecureUtils.md5Salt(password, userId.toString()), user.getPassword(), "用户名或密码不正确");
        this.checkUserStatus(user);
        return this.login(user);
    }

    @Override
    public String phoneLogin(String phone) {
        UserDO user = userService.getByPhone(phone);
        CheckUtils.throwIfNull(user, "此手机号未绑定本系统账号");
        this.checkUserStatus(user);
        return this.login(user);
    }

    @Override
    public String emailLogin(String email) {
        UserDO user = userService.getByEmail(email);
        CheckUtils.throwIfNull(user, "此邮箱未绑定本系统账号");
        this.checkUserStatus(user);
        return this.login(user);
    }

    @Override
    public String socialLogin(AuthUser authUser) {
        String source = authUser.getSource();
        String openId = authUser.getUuid();
        UserSocialDO userSocial = userSocialService.getBySourceAndOpenId(source, openId);
        UserDO user;
        if (null == userSocial) {
            String username = authUser.getUsername();
            String nickname = authUser.getNickname();
            UserDO existsUser = userService.getByUsername(username);
            String randomStr = RandomUtil.randomString(RandomUtil.BASE_CHAR, 5);
            if (null != existsUser || !ReUtil.isMatch(RegexConsts.USERNAME, username)) {
                username = randomStr + IdUtil.fastSimpleUUID();
            }
            if (!ReUtil.isMatch(RegexConsts.GENERAL_NAME, nickname)) {
                nickname = source.toLowerCase() + randomStr;
            }
            user = new UserDO();
            user.setUsername(username);
            user.setNickname(nickname);
            user.setGender(GenderEnum.valueOf(authUser.getGender().name()));
            user.setAvatar(authUser.getAvatar());
            user.setDeptId(SysConsts.SUPER_DEPT_ID);
            Long userId = userService.save(user);
            RoleDO role = roleService.getByCode(SysConsts.ADMIN_ROLE_CODE);
            userRoleService.save(Collections.singletonList(role.getId()), userId);
            userSocial = new UserSocialDO();
            userSocial.setUserId(userId);
            userSocial.setSource(source);
            userSocial.setOpenId(openId);
            this.sendMsg(user);
        } else {
            user = BeanUtil.toBean(userService.get(userSocial.getUserId()), UserDO.class);
        }
        this.checkUserStatus(user);
        userSocial.setMetaJson(JSONUtil.toJsonStr(authUser));
        userSocial.setLastLoginTime(LocalDateTime.now());
        userSocialService.saveOrUpdate(userSocial);
        return this.login(user);
    }

    @Override
    public List<RouteVO> buildRouteTree(Long userId) {
        Set<String> roleCodeSet = permissionService.listRoleCodeByUserId(userId);
        if (CollUtil.isEmpty(roleCodeSet)) {
            return new ArrayList<>(0);
        }
        // 查询菜单列表
        Set<MenuVO> menuSet = new LinkedHashSet<>();
        if (roleCodeSet.contains(SysConsts.ADMIN_ROLE_CODE)) {
            menuSet.addAll(menuService.list());
        } else {
            roleCodeSet.forEach(roleCode -> menuSet.addAll(menuService.listByRoleCode(roleCode)));
        }
        List<MenuVO> menuList =
            menuSet.stream().filter(m -> !MenuTypeEnum.BUTTON.equals(m.getType())).collect(Collectors.toList());
        // 构建路由树
        TreeField treeField = MenuVO.class.getDeclaredAnnotation(TreeField.class);
        TreeNodeConfig treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField);
        List<Tree<Long>> treeList = TreeUtils.build(menuList, treeNodeConfig, (m, tree) -> {
            tree.setId(m.getId());
            tree.setParentId(m.getParentId());
            tree.setName(m.getTitle());
            tree.setWeight(m.getSort());
            tree.putExtra("path", m.getPath());
            tree.putExtra("name", m.getName());
            tree.putExtra("component", m.getComponent());
            MetaVO metaVO = new MetaVO();
            metaVO.setLocale(m.getTitle());
            metaVO.setIcon(m.getIcon());
            metaVO.setIgnoreCache(!m.getIsCache());
            metaVO.setHideInMenu(m.getIsHidden());
            metaVO.setOrder(m.getSort());
            tree.putExtra("meta", metaVO);
        });
        return BeanUtil.copyToList(treeList, RouteVO.class);
    }

    /**
     * 登录并缓存用户信息
     *
     * @param user
     *            用户信息
     * @return 令牌
     */
    private String login(UserDO user) {
        Long userId = user.getId();
        LoginUser loginUser = BeanUtil.copyProperties(user, LoginUser.class);
        loginUser.setPermissions(permissionService.listPermissionByUserId(userId));
        loginUser.setRoleCodes(permissionService.listRoleCodeByUserId(userId));
        loginUser.setRoles(roleService.listByUserId(userId));
        return LoginHelper.login(loginUser);
    }

    /**
     * 检查用户状态
     *
     * @param user
     *            用户信息
     */
    private void checkUserStatus(UserDO user) {
        CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, user.getStatus(), "此账号已被禁用，如有疑问，请联系管理员");
        DeptDetailVO deptDetailVO = deptService.get(user.getDeptId());
        CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, deptDetailVO.getStatus(), "此账号所属部门已被禁用，如有疑问，请联系管理员");
    }

    /**
     * 发送消息
     * 
     * @param user
     *            用户信息
     */
    private void sendMsg(UserDO user) {
        MessageRequest request = new MessageRequest();
        MessageTemplateEnum socialRegister = MessageTemplateEnum.SOCIAL_REGISTER;
        request.setTitle(socialRegister.getTitle());
        Map<String, Object> contentMap = MapUtil.newHashMap(2);
        contentMap.put("nickname", user.getNickname());
        contentMap.put("projectName", projectProperties.getName());
        request.setContent(socialRegister.getContent(), contentMap);
        request.setType(SysConsts.SYSTEM_MESSAGE_TYPE);
        messageService.add(request, CollUtil.toList(user.getId()));
    }
}
