package com.luojia_channel.modules.user.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luojia_channel.common.domain.UserDTO;
import com.luojia_channel.common.exception.UserException;
import com.luojia_channel.common.utils.UserContext;
import com.luojia_channel.modules.user.dto.UserLoginDTO;
import com.luojia_channel.modules.user.dto.UserRegisterDTO;
import com.luojia_channel.modules.user.entity.User;
import com.luojia_channel.modules.user.mapper.UserMapper;
import com.luojia_channel.modules.user.service.UserLoginService;
import com.luojia_channel.common.utils.JWTUtil;
import com.luojia_channel.common.utils.RedisUtil;
import com.luojia_channel.modules.user.utils.ValidateParameterUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

import static com.luojia_channel.common.constants.RedisConstant.BLACKLIST_PREFIX;
import static com.luojia_channel.common.constants.RedisConstant.REFRESH_TOKEN_PREFIX;
import static com.luojia_channel.common.utils.JWTUtil.TOKEN_PREFIX;
import static com.luojia_channel.modules.user.constant.UserConstant.*;

@Service
@RequiredArgsConstructor
public class UserLoginServiceImpl extends ServiceImpl<UserMapper, User> implements UserLoginService {

    private final UserMapper userMapper;
    private final RedisUtil redisUtil;
    private final JWTUtil jwtUtil;
    private final ValidateParameterUtil validateParameterUtil;

    /**
     * 根据用户标志获得用户学号，用于登录
     * @param userFlag
     * @return
     */
    private User getUserByFlag(String userFlag) {
        if (StrUtil.isBlank(userFlag)) {
            throw new UserException("用户标识不能为空");
        }
        // 使用正则表达式判断类型，之前直接判断长度，虽然不合法的数据在数据库中仍然查不到
        boolean isEmail = userFlag.contains("@");
        boolean isPhone = !isEmail && userFlag.matches(ValidateParameterUtil.PHONE_REGEX);
        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
        if (isEmail) {
            wrapper.eq(User::getEmail, userFlag);
        } else if (isPhone) {
            wrapper.eq(User::getPhone, userFlag);
        } else {
            // 默认学号登录
            wrapper.eq(User::getStudentId, userFlag);
        }
        User user = userMapper.selectOne(wrapper);
        if (user == null) {
            throw new UserException("用户不存在");
        }
        return user;
    }

    /**
     * 生成并存储双token
     * @param userDTO
     */
    private void generateTokens(UserDTO userDTO){
        String accessToken = jwtUtil.generateAccessToken(userDTO);
        String refreshToken = jwtUtil.generateRefreshToken(userDTO);
        userDTO.setAccessToken(accessToken);
        userDTO.setRefreshToken(refreshToken);
        //存储refreshToken到redis
        String key = "refresh_token:" + userDTO.getUserId();
        redisUtil.set(key, refreshToken, EXPIRE_TIME, TimeUnit.DAYS);
    }

    /**
     * 用户登录，支持学号，手机，邮箱登录
     * 双token登录，支持无感刷新，即时失效
     * @param userLoginDTO
     * @return
     */
    @Override
    public UserDTO login(UserLoginDTO userLoginDTO) {
        String userFlag = userLoginDTO.getUserFlag();
        String password = userLoginDTO.getPassword();
        // TODO 选择密码加密格式
        User user = getUserByFlag(userFlag);
        if(!user.getPassword().equals(password)) {
            throw new UserException("密码错误");
        }
        UserDTO userDTO = UserDTO.builder()
                .userId(user.getId())
                .username(user.getUsername())
                .build();
        generateTokens(userDTO);
        return userDTO;
    }

    /**
     * 检查登录
     * @param accessToken
     * @param refreshToken
     * @return
     */
    @Override
    public UserDTO checkLogin(String accessToken, String refreshToken) {
        return jwtUtil.checkLogin(accessToken, refreshToken);
    }

    /**
     * 登出
     * @param accessToken
     */
    @Override
    public void logout(String accessToken) {
        Long userId = UserContext.getUserId();
        // 删除refreshToken
        String refreshKey = REFRESH_TOKEN_PREFIX + userId;
        redisUtil.delete(refreshKey);
        // 将accessToken加入黑名单
        if (StrUtil.isNotBlank(accessToken)) {
            String rawToken = accessToken.replace(TOKEN_PREFIX, "");
            long expire = jwtUtil.getRemainingTime(rawToken);
            redisUtil.set(BLACKLIST_PREFIX + rawToken, "1", expire, TimeUnit.MILLISECONDS);
        }
    }

    /**
     * 用户注册，支持手机，邮箱，学号三选一注册，注册后分发token直接登录
     * TODO 若未来想实现验证码登录可使用UUID生成六位验证码，然后存入redis，再发送验证码
     * @param userRegisterDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserDTO register(UserRegisterDTO userRegisterDTO) {
        // 校验注册参数
        validateParameterUtil.validateUser(userRegisterDTO);
        User user = BeanUtil.copyProperties(userRegisterDTO, User.class);
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        save(user);
        UserDTO userDTO = UserDTO.builder()
                .userId(user.getId())
                .username(user.getUsername())
                .build();
        generateTokens(userDTO);
        return userDTO;
    }
}




