/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */

package com.yami.shop.admin.controller;

import cn.hutool.core.util.StrUtil;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.security.admin.dto.CaptchaAuthenticationDTO;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
import com.yami.shop.security.common.enums.SysTypeEnum;
import com.yami.shop.security.common.manager.PasswordCheckManager;
import com.yami.shop.security.common.manager.PasswordManager;
import com.yami.shop.security.common.manager.TokenStore;
import com.yami.shop.security.common.vo.TokenInfoVO;
import com.yami.shop.sys.constant.Constant;
import com.yami.shop.sys.model.SysMenu;
import com.yami.shop.sys.model.SysUser;
import com.yami.shop.sys.service.SysMenuService;
import com.yami.shop.sys.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import jakarta.validation.Valid;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 后台登录相关的控制器类，主要处理管理员登录的逻辑，包括验证码校验、用户信息验证、密码验证以及生成和返回登录后的token等操作。
 *
 * @author FrozenWatermelon
 * @date 2020/6/30
 */
@RestController
@Tag(name = "登录")
public class AdminLoginController {

    // 注入TokenStore，用于存储和获取用户登录相关的token信息，比如生成token、从存储中获取token相关数据等操作。
    @Autowired
    private TokenStore tokenStore;

    // 注入SysUserService，用于与系统用户相关的数据库操作，比如根据用户名查询用户信息等。
    @Autowired
    private SysUserService sysUserService;

    // 注入SysMenuService，用于与系统菜单相关的数据库操作，例如查询系统菜单列表等，此处主要用于获取权限相关信息。
    @Autowired
    private SysMenuService sysMenuService;

    // 注入PasswordCheckManager，用于对用户输入的密码进行合法性、安全性等方面的检查，比如验证密码是否符合规则、是否在一定时间内多次输错被限制登录等。
    @Autowired
    private PasswordCheckManager passwordCheckManager;

    // 注入CaptchaService，用于处理验证码相关的操作，比如验证验证码是否正确、是否过期等。
    @Autowired
    private CaptchaService captchaService;

    // 注入PasswordManager，用于对密码进行解密等相关操作，比如将前端传来的加密密码进行解密，以便后续进行密码验证。
    @Autowired
    private PasswordManager passwordManager;

    /**
     * 处理管理员登录的接口方法，接收包含账号、密码以及验证码等信息的请求体，进行一系列登录验证操作，若验证通过则返回登录成功后的token信息。
     *
     * @param captchaAuthenticationDTO 包含了账号、密码以及验证码验证信息的请求参数对象，通过@Valid注解进行参数合法性校验。
     * @return 返回ServerResponseEntity类型的响应结果，若登录成功则返回包含token信息的成功响应，若验证失败则返回相应的错误提示信息。
     */
    @PostMapping("/adminLogin")
    @Operation(summary = "账号密码 + 验证码登录(用于后台登录)", description = "通过账号/手机号/用户名密码登录")
    public ServerResponseEntity<?> login(
            @Valid @RequestBody CaptchaAuthenticationDTO captchaAuthenticationDTO) {

        // 登陆后台登录需要再校验一遍验证码
        // 创建CaptchaVO对象，用于传递给验证码服务进行验证，将前端传来的验证码验证信息设置进去。
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(captchaAuthenticationDTO.getCaptchaVerification());
        // 调用验证码服务的验证方法，传入CaptchaVO对象，获取验证结果响应模型。
        ResponseModel response = captchaService.verification(captchaVO);
        // 如果验证不成功，即验证码有误或者已过期，返回相应的错误提示信息给前端。
        if (!response.isSuccess()) {
            return ServerResponseEntity.showFailMsg("验证码有误或已过期");
        }

        // 根据用户名从数据库中查询系统用户信息，如果未查询到用户，说明账号不存在，抛出相应的异常提示账号或密码不正确。
        SysUser sysUser = sysUserService.getByUserName(captchaAuthenticationDTO.getUserName());
        if (sysUser == null) {
            throw new YamiShopBindException("账号或密码不正确");
        }

        // 半小时内密码输入错误十次，已限制登录30分钟
        // 先对前端传来的密码进行解密操作，以便后续与数据库中存储的密码进行比对验证。
        String decryptPassword = passwordManager.decryptPassword(captchaAuthenticationDTO.getPassWord());
        // 调用密码检查管理器，传入系统类型、用户名、解密后的密码以及数据库中存储的用户密码，进行密码验证，若不符合规则会抛出相应异常。
        passwordCheckManager.checkPassword(SysTypeEnum.ADMIN, captchaAuthenticationDTO.getUserName(), decryptPassword, sysUser.getPassword());

        // 不是店铺超级管理员，并且是禁用状态，无法登录
        if (Objects.equals(sysUser.getStatus(), 0)) {
            // 若用户状态为禁用（这里假设状态0表示禁用），抛出异常提示未找到此用户信息（此处实际意思应该是用户不可用）。
            // 未找到此用户信息
            throw new YamiShopBindException("未找到此用户信息");
        }

        // 创建用于存储在token中的用户信息对象，用于后续生成token以及传递给前端展示相关用户信息。
        UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
        userInfoInToken.setUserId(String.valueOf(sysUser.getUserId()));
        userInfoInToken.setSysType(SysTypeEnum.ADMIN.value());
        userInfoInToken.setEnabled(sysUser.getStatus() == 1);
        userInfoInToken.setPerms(getUserPermissions(sysUser.getUserId()));
        userInfoInToken.setNickName(sysUser.getUsername());
        userInfoInToken.setShopId(sysUser.getShopId());

        // 调用token存储服务，将用户信息存储并生成token相关信息，获取包含token等详细信息的TokenInfoVO对象，然后返回登录成功的响应结果给前端，包含了token信息。
        TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
        return ServerResponseEntity.success(tokenInfoVO);
    }


    /**
     * 根据用户ID获取用户的权限信息集合，根据用户是否为系统超级管理员（通过特定的ID判断）采用不同的方式获取权限信息，
     * 最终将获取到的权限字符串列表转换为去重后的权限集合返回。
     *
     * @param userId 用户的ID，用于确定是哪个用户的权限信息以及判断是否为系统超级管理员。
     * @return 返回包含用户权限信息的字符串集合，每个元素代表一个权限标识。
     */
    private Set<String> getUserPermissions(Long userId) {
        List<String> permsList;

        // 系统管理员，拥有最高权限
        if (userId == Constant.SUPER_ADMIN_ID) {
            // 如果是系统超级管理员，查询所有的系统菜单列表，获取每个菜单对应的权限字符串，组成权限列表。
            List<SysMenu> menuList = sysMenuService.list(Wrappers.emptyWrapper());
            permsList = menuList.stream().map(SysMenu::getPerms).collect(Collectors.toList());
        } else {
            // 如果不是系统超级管理员，调用用户服务的方法查询该用户的所有权限信息，得到权限列表。
            permsList = sysUserService.queryAllPerms(userId);
        }

        // 将权限列表中的每个权限字符串进行分割（假设权限字符串之间以逗号分隔），然后扁平化处理，去除重复的权限，最终收集为一个权限集合返回。
        return permsList.stream().flatMap((perms) -> {
                    if (StrUtil.isBlank(perms)) {
                        return null;
                    }
                    return Arrays.stream(perms.trim().split(StrUtil.COMMA));
                }
        ).collect(Collectors.toSet());
    }
}