package com.yami.shop.api.controller;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.model.User;
import com.yami.shop.bean.param.UserRegisterParam;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
import com.yami.shop.security.common.enums.SysTypeEnum;
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.service.UserService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import com.yami.shop.common.response.ServerResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import jakarta.validation.Valid;
import java.util.Date;

/**
 * 用户注册相关接口的控制器类，用于处理用户注册以及修改密码等与用户信息相关的核心操作，
 * 通过调用不同的服务层方法以及相关的工具类来完成业务逻辑，例如验证用户名是否已存在、密码加密、生成用户标识等操作，
 * 并以统一的响应格式返回操作结果给前端使用。
 *
 * @author SJL
 */
@RestController
@RequestMapping("/user")
@Tag(name = "用户注册相关接口")
@AllArgsConstructor
public class UserRegisterController {

    // 通过构造函数注入的方式引入用户服务层接口，用于调用与用户相关的业务逻辑方法，比如保存用户信息、根据条件查询用户等操作。
    private final UserService userService;
    // 通过构造函数注入的方式引入密码编码器，用于对用户密码进行加密处理，保证密码在存储和传输过程中的安全性。
    private final PasswordEncoder passwordEncoder;
    // 通过构造函数注入的方式引入令牌存储相关的类，用于处理用户登录后令牌的生成、存储以及获取相关信息等操作，实现用户身份认证和授权相关功能。
    private final TokenStore tokenStore;
    // 通过构造函数注入的方式引入密码管理相关的类，可能用于对用户输入的密码进行解密（如果有加密传输等情况）或者其他密码相关的管理操作。
    private final PasswordManager passwordManager;

    /**
     * 用户注册或绑定手机号的方法，接收包含用户注册信息的参数对象，先进行用户名的合法性验证（若昵称为空则使用用户名作为昵称），
     * 然后检查用户名是否已被注册，若未被注册则创建新用户对象，设置相关属性（如注册时间、状态等），对密码进行解密和加密处理后保存用户信息到数据库，
     * 最后模拟用户登录操作，生成并返回包含令牌信息的响应结果给前端，用于后续的身份认证和授权访问。
     *
     * @param userRegisterParam 包含用户注册信息的参数对象，通过请求体传入，且经过了参数验证（@Valid 注解），例如用户名、密码、邮箱等信息。
     * @return 包含令牌信息的 ServerResponseEntity，以 TokenInfoVO 类型封装令牌相关内容，若注册成功则返回对应的令牌信息，若用户名已注册等情况则抛出相应异常。
     */
    @PostMapping("/register")
    @Operation(summary = "注册", description = "用户注册或绑定手机号接口")
    public ServerResponseEntity<TokenInfoVO> register(@Valid @RequestBody UserRegisterParam userRegisterParam) {
        // 如果用户注册参数中的昵称（nickName）为空字符串，则将用户名（userName）作为昵称使用，保证昵称有值。
        if (StrUtil.isBlank(userRegisterParam.getNickName())) {
            userRegisterParam.setNickName(userRegisterParam.getUserName());
        }

        // 通过用户服务层的方法，使用 MyBatis Plus 的 LambdaQueryWrapper 构建查询条件，统计数据库中昵称与传入的昵称相同的用户数量，
        // 如果数量大于 0，则表示该用户名（昵称）已被注册，抛出异常提示用户无法重新注册。
        if (userService.count(new LambdaQueryWrapper<User>().eq(User::getNickName, userRegisterParam.getNickName())) > 0) {
            // 该用户名已注册，无法重新注册
            throw new YamiShopBindException("该用户名已注册，无法重新注册");
        }

        // 创建当前时间对象，用于设置用户的修改时间、注册时间等属性，确保记录的时间信息准确。
        Date now = new Date();
        // 创建一个新的用户对象，用于填充用户注册信息并保存到数据库中。
        User user = new User();
        // 设置用户的修改时间为当前时间，表示该用户信息的最近一次修改时间。
        user.setModifyTime(now);
        // 设置用户的注册时间为当前时间，记录用户注册的具体时间点。
        user.setUserRegtime(now);
        // 设置用户状态为有效（这里假设状态值为 1 表示有效，可根据实际业务定义调整）。
        user.setStatus(1);
        // 设置用户的昵称，使用经过前面逻辑处理后的昵称值（可能是原昵称或者用户名）。
        user.setNickName(userRegisterParam.getNickName());
        // 设置用户的邮箱信息，从注册参数中获取并赋值给用户对象。
        user.setUserMail(userRegisterParam.getUserMail());

        // 通过密码管理类的方法，对用户注册参数中传入的密码进行解密操作（可能是之前加密传输过来的情况），获取解密后的密码字符串。
        String decryptPassword = passwordManager.decryptPassword(userRegisterParam.getPassWord());
        // 使用密码编码器对解密后的密码进行加密处理，将加密后的密码设置到用户对象中，保证密码在数据库中以加密形式存储，提高安全性。
        user.setLoginPassword(passwordEncoder.encode(decryptPassword));

        // 使用 Hutool 工具类生成一个简单的唯一用户 ID（这里采用 UUID 的简化形式，可根据实际需求调整生成方式），作为用户的标识。
        String userId = IdUtil.simpleUUID();
        // 将生成的用户 ID 设置到用户对象中，确保每个用户在系统中有唯一的标识。
        user.setUserId(userId);

        // 调用用户服务层的保存方法，将填充好信息的用户对象保存到数据库中，完成用户注册操作。
        userService.save(user);

        // 模拟用户登录操作，创建一个用于存储在令牌中的用户信息对象，填充用户 ID、用户类型（这里设置为普通用户，通过枚举值获取）、是否为管理员（这里设置为否）以及是否启用等信息。
        UserInfoInTokenBO userInfoInTokenBO = new UserInfoInTokenBO();
        userInfoInTokenBO.setUserId(user.getUserId());
        userInfoInTokenBO.setSysType(SysTypeEnum.ORDINARY.value());
        userInfoInTokenBO.setIsAdmin(0);
        userInfoInTokenBO.setEnabled(true);

        // 通过令牌存储类的方法，将用户信息存储并生成对应的令牌信息对象，然后返回包含令牌信息的响应结果给前端，方便后续用户进行身份认证和授权访问。
        return ServerResponseEntity.success(tokenStore.storeAndGetVo(userInfoInTokenBO));
    }

    /**
     * 修改用户密码的方法，接收包含修改密码相关信息的参数对象，先根据用户名从数据库中查询用户信息，若用户不存在则抛出异常，
     * 接着对新密码进行解密和合法性验证（不能为空且不能与原密码相同），然后对新密码进行加密处理，更新用户的密码信息到数据库中，
     * 最后返回表示修改成功的响应结果给前端，若验证不通过等情况则抛出相应的异常。
     *
     * @param userPwdUpdateParam 包含修改密码相关信息的参数对象，通过请求体传入，且经过了参数验证（@Valid 注解），例如用户名、新密码等信息。
     * @return 表示修改密码操作成功的 ServerResponseEntity，若修改成功则返回空的成功响应，若用户不存在、新密码不符合要求等情况则抛出相应异常。
     */
    @PutMapping("/updatePwd")
    @Operation(summary = "修改密码", description = "修改密码")
    public ServerResponseEntity<Void> updatePwd(@Valid @RequestBody UserRegisterParam userPwdUpdateParam) {
        // 通过用户服务层的方法，使用 MyBatis Plus 的 LambdaQueryWrapper 构建查询条件，根据用户名（昵称）从数据库中查询对应的用户信息，若查询不到则抛出异常提示无法获取用户信息。
        User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getNickName, userPwdUpdateParam.getNickName()));
        if (user == null) {
            // 无法获取用户信息
            throw new YamiShopBindException("无法获取用户信息");
        }

        // 通过密码管理类的方法，对用户修改密码参数中传入的新密码进行解密操作（可能是之前加密传输过来的情况），获取解密后的密码字符串。
        String decryptPassword = passwordManager.decryptPassword(userPwdUpdateParam.getPassWord());
        // 如果解密后的新密码为空字符串，则抛出异常提示新密码不能为空。
        if (StrUtil.isBlank(decryptPassword)) {
            // 新密码不能为空
            throw new YamiShopBindException("新密码不能为空");
        }

        // 使用密码编码器对解密后的新密码进行加密处理，得到加密后的密码字符串。
        String password = passwordEncoder.encode(decryptPassword);
        // 如果加密后的新密码与用户原密码相同，则抛出异常提示新密码不能与原密码相同，保证密码修改的合理性。
        if (StrUtil.equals(password, user.getLoginPassword())) {
            // 新密码不能与原密码相同
            throw new YamiShopBindException("新密码不能与原密码相同");
        }

        // 设置用户的修改时间为当前时间，表示此次密码修改操作的时间。
        user.setModifyTime(new Date());
        // 将加密后的新密码设置到用户对象中，更新用户的密码信息到数据库中，完成密码修改操作。
        user.setLoginPassword(password);
        // 调用用户服务层的更新方法，将更新后的用户对象信息保存到数据库中，确保密码修改生效。
        userService.updateById(user);

        return ServerResponseEntity.success();
    }
}