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

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.signers.AlgorithmUtil;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import cn.skcks.matrix.v2.config.JwtConfig;
import cn.skcks.matrix.v2.model.jwt.convert.ClaimConvertor;
import cn.skcks.matrix.v2.model.jwt.dto.Claims;
import cn.skcks.matrix.v2.utils.base91.Base91;
import cn.skcks.matrix.v2.utils.redis.RedisUtil;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

import javax.annotation.Nullable;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Map;

@Slf4j
@RequiredArgsConstructor
@Data
@Order
@Service
public class JwtServiceImpl implements JwtService, CommandLineRunner {
    private final JwtConfig jwtConfig;

    private String publicCacheKey;
    private String privateCacheKey;

    private KeyPair keyPair;

    private PublicKey publicKey;

    private PrivateKey privateKey;

    private boolean checkKeys() {
        return StringUtils.isNotBlank(RedisUtil.StringOps.get(publicCacheKey))
                && StringUtils.isNotBlank(RedisUtil.StringOps.get(privateCacheKey));
    }

    private boolean hasKeys() {
        return RedisUtil.KeyOps.hasKey(publicCacheKey)
                && RedisUtil.KeyOps.hasKey(privateCacheKey);
    }

    public void init() {
        if (!jwtConfig.isGenerateKeyPairEveryTime()) {
            if (hasKeys() && checkKeys()) {
                return;
            }
        } else {
            log.info("[JwtService] 每次启动重新生成 jwt 密钥对 已启用");
        }

        log.info("[JwtService] 生成 jwt 密钥对");
        final String algorithm = jwtConfig.getAlgorithm();
        final KeyPair keyPair = KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(algorithm));
        RedisUtil.StringOps.set(publicCacheKey, Base91.encode(keyPair.getPublic().getEncoded()));
        RedisUtil.StringOps.set(privateCacheKey, Base91.encode(keyPair.getPrivate().getEncoded()));
    }

    @Override
    public void reload() {
        log.info("[JwtService] 重新加载密钥对");
        run();
    }

    public void loadPublicKey() {
        final byte[] pub = Base91.decode(RedisUtil.StringOps.get(publicCacheKey));
        publicKey = KeyUtil.generatePublicKey(AlgorithmUtil.getAlgorithm(jwtConfig.getAlgorithm()), pub);
    }

    public void loadPrivateKey() {
        final byte[] pri = Base91.decode(RedisUtil.StringOps.get(privateCacheKey));
        privateKey = KeyUtil.generatePrivateKey(AlgorithmUtil.getAlgorithm(jwtConfig.getAlgorithm()), pri);
    }

    public void loadKeyPair() {
        log.info("[JwtService] 载入密钥对信息");
        loadPublicKey();
        loadPrivateKey();
        keyPair = new KeyPair(publicKey, privateKey);
    }

    @Override
    public void run(String... args) {
        publicCacheKey = jwtConfig.getPublicCacheKey();
        privateCacheKey = jwtConfig.getPrivateCacheKey();

        log.info("JwtConfig.publicCacheKey {}", publicCacheKey);
        log.info("JwtConfig.privateCacheKey {}", privateCacheKey);
        log.info("JwtConfig.expire {}", jwtConfig.getExpire());

        init();
        loadKeyPair();
    }

    public JWTSigner getSignSigner() {
        return JWTSignerUtil.createSigner(jwtConfig.getAlgorithm(), privateKey);
    }

    private JWTSigner getVerifySigner() {
        return JWTSignerUtil.createSigner(jwtConfig.getAlgorithm(), keyPair);
    }

    public String generateToken(Claims claims) {
        JWTSigner signer = getSignSigner();
        JWT jwt = JWT.create()
                .setSigner(signer)
                .setIssuedAt(DateUtil.date())
                .setExpiresAt(DateUtil.offset(DateUtil.date(), DateField.SECOND, (int) jwtConfig.getExpire().getSeconds()));
        Map<String, Object> map = ClaimConvertor.INSTANCE.toMap(claims);
        map.forEach(jwt::setPayload);
        return jwt.sign();
    }

    public boolean verifyToken(String token) {
        JWTSigner signer = getVerifySigner();

        try {
            JWT jwt = JWT.of(token).setSigner(signer);
            return jwt.verify() && jwt.validate(0);
        } catch (Exception e) {
            log.warn(e.getMessage());
            return false;
        }
    }

    @Nullable
    @Override
    public Claims parseToken(String token) {
        if (!verifyToken(token)) {
            return null;
        }

        try {
            JWT jwt = JWT.of(token).setSigner(getVerifySigner());
            return ClaimConvertor.INSTANCE.toClaims(jwt.getPayloads().getRaw());
        } catch (Exception e) {
            log.warn("{}", e.getMessage());
            return null;
        }
    }
}
