package cn.skcks.matrix.v2.services.auth;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import cn.skcks.matrix.v2.config.JwtConfig;
import cn.skcks.matrix.v2.model.auth.dto.UserLoginDto;
import cn.skcks.matrix.v2.model.auth.dto.UserRegisterDto;
import cn.skcks.matrix.v2.model.auth.vo.UserLoginVo;
import cn.skcks.matrix.v2.model.auth.vo.UserRegisterVo;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
import cn.skcks.matrix.v2.services.jwt.JwtService;
import cn.skcks.matrix.v2.services.user.UserService;
import cn.skcks.matrix.v2.utils.redis.RedisUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;


@Slf4j
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
    private final JwtConfig jwtConfig;
    private final JwtService jwtService;

    private final UserService userService;

    private final static String DEFAULT_PREFIX = "AUTH";
    private final static String SEPARATOR = ":";

    private final static String TOKEN_PREFIX = "TOKEN";

    @Override
    public boolean validateToken(String token) {
        return jwtService.verifyToken(token);
    }

    @Override
    public String generateToken(User user) {
        Claims claims = new Claims();
        claims.setUserName(user.getUserName());
        claims.setUserId(String.valueOf(user.getId()));
        return jwtService.generateToken(claims);
    }

    @Override
    public String refreshToken(String refreshToken) {
        return null;
    }


    @Data
    @AllArgsConstructor
    protected static class Credentials {
        private String passwordHex;
        private String keyHex;
    }

    protected Credentials createCredentials(String password, byte[] key) {
        // aes 加密
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
        // 加密后的 hex
        String passwordHex = aes.encryptHex(password);
        // 密钥 (hex)
        String keyHex = HexUtil.encodeHexStr(key);
        return new Credentials(passwordHex, keyHex);
    }

    @Override
    public UserRegisterVo register(UserRegisterDto dto) {
        if (userService.exist(dto.getUserName(), dto.getEmail())) {
            return UserRegisterVo.builder()
                    .status(false)
                    .exception("用户名 或 邮箱已被注册")
                    .build();
        }

        // 计算 密码的 hash
        String passwordHash = SecureUtil.sha256(dto.getPassword());

        // 随机生成密钥
        final byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();
        final Credentials credentials = createCredentials(passwordHash, key);

        User user = new User();
        final long id = IdUtil.getSnowflakeNextId();
        user.setId(id);
        user.setUserName(dto.getUserName());
        user.setPassword(credentials.passwordHex);
        user.setSalt(credentials.keyHex);
        user.setEmail(dto.getEmail());
        user.setDescription("");
        user.setPhoneNumber("");
        user.setCreateTime(DateUtil.date());
        user.setUpdateTime(DateUtil.date());

        final boolean status = userService.add(user);

        final UserRegisterVo.UserRegisterVoBuilder builder = UserRegisterVo.builder().status(status);

        if (status) {
            builder.userId(String.valueOf(id));
        } else {
            builder.exception("注册失败");
        }

        return builder.build();
    }

    @Override
    public UserLoginVo login(UserLoginDto dto) {
        UserLoginVo.UserLoginVoBuilder builder = UserLoginVo.builder().status(false);
        final String exception = "用户不存在 或 密码错误";
        if (!userService.exist(dto.getAccount(), dto.getAccount())) {
            return builder.exception(exception).build();
        }

        User user = userService.getUser(dto.getAccount());
        if (user == null) {
            return builder.exception(exception).build();
        }

        final String passwordHash = SecureUtil.sha256(dto.getPassword());
        final byte[] keyHex = HexUtil.decodeHex(user.getSalt());

        Credentials credentials = createCredentials(passwordHash, keyHex);
        if (!user.getPassword().equals(credentials.getPasswordHex())) {
            return builder.exception(exception).build();
        }

        String token = generateToken(user);
        saveToken(user, token);

        user.setUpdateTime(DateUtil.date());
        userService.update(user);

        return builder.status(true)
                .id(String.valueOf(user.getId()))
                .token(token)
                .build();
    }

    private String generateRedisKey(Long id, String token) {
        return generateRedisKey(String.valueOf(id), token);
    }

    private String generateRedisKey(String id, String token) {
        String tokenMd5 = SecureUtil.md5(token);
        return DEFAULT_PREFIX + SEPARATOR + TOKEN_PREFIX
                + SEPARATOR + id
                + SEPARATOR + tokenMd5;
    }

    @Override
    public boolean validateToken(Claims claims, String token) {
        String redisKey = generateRedisKey(claims.getUserId(), token);
        String cacheToken = RedisUtil.StringOps.get(redisKey);
        if (StringUtils.isEmpty(cacheToken)) {
            return false;
        }
        return StringUtils.equals(cacheToken, token);
    }

    @Override
    public void saveToken(User user, String token) {
        String redisKey = generateRedisKey(user.getId(), token);
        RedisUtil.StringOps.set(redisKey, token);
        RedisUtil.KeyOps.expire(redisKey, jwtConfig.getExpire().getSeconds(), TimeUnit.SECONDS);
    }
}
