package com.ruoyi.framework.service;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.constants.CacheConstant;
import com.ruoyi.common.constants.UserConstant;
import com.ruoyi.common.enums.DictEnum;
import com.ruoyi.common.utils.ContextUtil;
import com.ruoyi.common.utils.CryptoUtil;
import com.ruoyi.common.utils.RedisUtil;
import com.ruoyi.common.utils.SecurityUtil;
import com.ruoyi.common.web.domain.model.LoginBody;
import com.ruoyi.common.web.domain.model.LoginUser;
import com.ruoyi.common.web.domain.model.RegisterBody;
import com.ruoyi.common.web.domain.entity.SysUser;
import com.ruoyi.common.web.exception.ServiceException;
import com.ruoyi.framework.config.LoginConfig;
import com.ruoyi.system.domain.SysLoginLog;
import com.ruoyi.system.service.*;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 认证处理
 * @author ruoyi
 */
@Component
public class LoginService {

    // 登录成功
    public static final String LOGIN_SUCCESS = "Success";
    // 登录失败
    public static final String LOGIN_FAIL = "Error";
    // 注销
    public static final String LOGOUT = "Logout";
    // 注册
    public static final String REGISTER = "Register";

    @Inject
    LoginConfig loginConfig;

    @Inject
    CaptchaService captchaService;

    @Inject
    RedisUtil redisUtil;

    @Inject
    ISysUserService sysUserService;

    @Inject
    ISysRoleService sysRoleService;

    @Inject
    ISysMenuService sysMenuService;

    @Inject
    ISysLoginLogService sysLoginLogService;

    @Inject
    ISysConfigService sysConfigService;

    /**
     * 登录
     * @param loginBody
     * @param deviceType
     * @return
     */
    public Map<String, Object> login(LoginBody loginBody, String deviceType) {
        // 前置校验
        String password = checkLoginPre(loginBody);
        String username = loginBody.getUsername();

        // 查询用户
        SysUser sysUser = sysUserService.selectSysUserByUserName(username);
        // 校验登录用户
        checkLoginUser(username, password, sysUser, deviceType);

        // 以上校验通过后，登录成功
        return loginSuccess(sysUser, deviceType);
    }

    /**
     * 获取登录用户信息
     */
    public Map<String, Object> getUserInfo() {
        LoginUser loginUser = SecurityUtil.getLoginUser();
        if(ObjectUtil.isNull(loginUser)) {
            throw new ServiceException("登录失效");
        }
        Map<String, Object> map = new HashMap<>();
        map.put("user", loginUser.getSysUser());
        map.put("roles", loginUser.getRoles());
        map.put("permissions", loginUser.getPermissions());
        return map;
    }

    /**
     * 登出
     */
    public void logout() {
        String userName = SecurityUtil.getUserName();
        recordLoginLog(userName, LOGIN_FAIL, "登出成功", SecurityUtil.getDeviceType());
        SecurityUtil.logout();
    }

    /**
     * 注册
     * @param registerBody
     * @param deviceType
     */
    public void register(RegisterBody registerBody, String deviceType) {
        // 是否开启注册用户
        String registerUser = sysConfigService.selectConfigByKey("sys.account.registerUser");
        if(!"true".equals(registerUser)) {
            throw new ServiceException("当前系统没有开启注册功能！");
        }

        // 前置校验
        String password = checkLoginPre(registerBody);
        String username = registerBody.getUsername();

        SysUser sysUser = new SysUser();
        sysUser.setUserName(username);
        if(!sysUserService.checkUserNameUnique(sysUser)) {
            throw new ServiceException("注册失败，账号已存在");
        }
        sysUser.setNickName(username);
        sysUser.setPassword(SecurityUtil.encryptPassword(password));
        sysUser.setStatus(DictEnum.COMMON_STATUS.OK.getValue());
        int i = sysUserService.registerUser(sysUser);
        if(i <= 0) {
            throw new ServiceException("注册失败，请联系系统管理人员");
        }
        recordLoginLog(username, REGISTER, "注册成功", deviceType);
    }

    /**
     * 前置校验（验证码、账号密码、登录失败次数）
     * @param loginBody
     * @return 解密后的密码
     */
    private String checkLoginPre(LoginBody loginBody) {
        // 1、校验验证码
        captchaService.checkCapcha(loginBody.getCode(), loginBody.getUuid());

        // 2、校验账号密码
        String username = loginBody.getUsername();
        String password = loginBody.getPassword();
        if(!StrUtil.isAllNotEmpty(username, password)) {
            throw new ServiceException("账号/密码必须填写");
        }
        // 解密密码
        try {
            password = CryptoUtil.rsaDecrypt(password);
        } catch (Exception ignored) {
            throw new ServiceException("密码异常");
        }
        // 密码是否在指定长度范围内
        if (password.length() < UserConstant.PASSWORD_MIN_LENGTH || password.length() > UserConstant.PASSWORD_MAX_LENGTH) {
            throw new ServiceException("密码长度不在指定范围");
        }
        // 账号是否在指定长度范围内
        if (username.length() < UserConstant.USERNAME_MIN_LENGTH || username.length() > UserConstant.USERNAME_MAX_LENGTH) {
            throw new ServiceException("账号长度不在指定范围");
        }

        // 3、校验登录失败
        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + username;
        Object cacheTotal = redisUtil.getCacheObject(loginErrorKey);
        if (ObjectUtil.isNotEmpty(cacheTotal)) {
            // 剩余时间
            long time = redisUtil.getCacheObjectExpire(loginErrorKey);
            // 登录失败次数
            long total = (Long) cacheTotal;
            if(total >= loginConfig.getErrorCount()) {
                long t = time / 1000 / 60 + 1L;
                throw new ServiceException(String.format("账号已锁定，请 %s 分钟后再试", t));
            }
        }

        return password;
    }

    /**
     * 校验登录用户
     * @param username
     * @param password
     * @param sysUser
     * @param deviceType
     */
    private void checkLoginUser(String username, String password, SysUser sysUser, String deviceType) {
        if(ObjectUtil.isNull(sysUser)) {
            loginError(username);
        }
        if(DictEnum.DEL_FLAG.DELETED.getValue() == sysUser.getDelFlag()) {
            String msg = "对不起，您的账号已被删除";
            recordLoginLog(username, LOGIN_FAIL, msg, deviceType);
            throw new ServiceException(msg);
        }
        if(StrUtil.isBlank(sysUser.getStatus()) || DictEnum.COMMON_STATUS.DISABLE.getValue().equals(sysUser.getStatus())) {
            String msg = "账号已停用，请联系管理员";
            recordLoginLog(username, LOGIN_FAIL, msg, deviceType);
            throw new ServiceException(msg);
        }
        // 密码是否正确
        if(!SecurityUtil.matchesPassword(password, sysUser.getPassword())) {
            recordLoginLog(username, LOGIN_FAIL, "密码错误", deviceType);
            loginError(username);
        }
        // 清除密码
        sysUser.setPassword(null);
    }

    /**
     * 登录失败
     * @param username
     */
    private void loginError(String username) {
        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + username;
        long total = 1L;
        Object cacheTotal = redisUtil.getCacheObject(loginErrorKey);
        if(ObjectUtil.isNotEmpty(cacheTotal)) {
            total = (Long) cacheTotal;
            total++;
        }
        redisUtil.setCacheObject(loginErrorKey, total, Duration.ofMinutes(loginConfig.getLockTime()));
        if(total >= loginConfig.getErrorCount()) {
            throw new ServiceException(String.format("账号已锁定，请 %s 分钟后再试", loginConfig.getLockTime()));
        } else {
            long n = loginConfig.getErrorCount() - total;
            throw new ServiceException(String.format("账号或密码错误，还剩 %s 次机会", n));
        }
    }

    /**
     * 登录成功
     * @param sysUser
     * @param deviceType 登录设备类型
     * @return
     */
    private Map<String, Object> loginSuccess(SysUser sysUser, String deviceType) {
        LoginUser loginUser = new LoginUser();
        loginUser.setSysUser(sysUser);
        loginUser.setUserid(sysUser.getUserId());
        String username = sysUser.getUserName();
        loginUser.setUsername(username);
        loginUser.setDeviceType(deviceType);
        loginUser.setIpaddr(ContextUtil.getIpAddr());
        // 角色集合
        Set<String> roles = sysRoleService.getRolePermission(sysUser.getUserId());
        loginUser.setRoles(roles);
        // 权限集合
        Set<String> permissions = sysMenuService.getMenuPermission(sysUser);
        loginUser.setPermissions(permissions);
        LoginUser login = SecurityUtil.login(loginUser, deviceType);
        // 删除登录失败
        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + loginUser.getUsername();
        redisUtil.deleteCacheObject(loginErrorKey);

        recordLoginLog(username, LOGIN_SUCCESS, "登录成功", deviceType);

        Map<String, Object> map = new HashMap<>();
        map.put("access_token", login.getToken());
        map.put("expires_in", login.getExpireTime());
        return map;
    }

    /**
     * 记录登录日志
     * @param username 账号
     * @param status 状态
     * @param message 消息内容
     * @param deviceType 登录设备类型
     * @return
     */
    public void recordLoginLog(String username, String status, String message, String deviceType) {
        SysLoginLog sysLoginLog = new SysLoginLog();
        sysLoginLog.setUserName(username);
        sysLoginLog.setIpaddr(ContextUtil.getIpAddr());
        sysLoginLog.setDeviceType(deviceType);
        sysLoginLog.setRemark(message);
        // 日志状态
        if (StrUtil.equalsAny(status, LOGIN_SUCCESS, LOGOUT, REGISTER)) {
            sysLoginLog.setStatus(DictEnum.COMMON_SUCCESS_FAIL.SUCCESS.getValue());
        } else if (LOGIN_FAIL.equals(status)) {
            sysLoginLog.setStatus(DictEnum.COMMON_SUCCESS_FAIL.FAIL.getValue());
        }
        sysLoginLogService.save(sysLoginLog);
    }

}
