package com.njcn.msgpush.module.push.service.credential;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.njcn.msgpush.framework.common.exception.ServiceException;
import com.njcn.msgpush.framework.common.exception.enums.ServiceErrorCodeRange;
import com.njcn.msgpush.framework.common.util.json.JsonUtils;
import com.njcn.msgpush.module.push.dal.dataobject.credential.SystemSecretDO;
import com.njcn.msgpush.module.push.dal.dataobject.credential.dto.CredentialReqDTO;
import com.njcn.msgpush.module.push.dal.dataobject.credential.dto.CredentialRespDTO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

/**
 * @author caozehui
 * @data 2026-03-31
 */
@Service
@Slf4j
public class CredentialServiceImpl implements ICredentialService {
    /**
     * 凭证加密密钥（生产环境应通过配置中心或环境变量注入）
     */
    private String credentialSecretKey = "88888888888888888888888888888888"; // 32 字节

    @Autowired
    private ISystemSecretService systemSecretService;

    private final StringRedisTemplate stringRedisTemplate;

    public CredentialServiceImpl(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }


    @Override
    public CredentialRespDTO generateCredential(CredentialReqDTO reqDTO) {
        try {
            // 1. 验证系统密钥
            boolean validatedSecretRes = validateSecret(reqDTO.getSystemName(), reqDTO.getSecretKey());
            if (!validatedSecretRes) {
                throw new ServiceException(ServiceErrorCodeRange.VALIDATE_SYSTEM_SECRET_FAIL);
            }

            // 2. 创建凭证信息
            CredentialInfo credentialInfo = new CredentialInfo(
                    reqDTO.getSystemName(),
                    LocalDateTime.now().plusHours(24)
            );

            // 3. 生成凭证 token（加密后的 JSON）
            String token = encryptCredential(credentialInfo);

            // 4. 缓存凭证信息到 Redis
            cacheCredential(token, credentialInfo);

            // 5. 构建响应
            return new CredentialRespDTO()
                    .setCredentialToken(token)
                    .setSystemName(reqDTO.getSystemName())
                    .setExpiresTime(credentialInfo.getExpiresTime());
        } catch (Exception e) {
            throw new ServiceException(ServiceErrorCodeRange.GENERATE_CREDENTIAL_FAIL);
        }
    }

    public CredentialInfo verifyCredential(String token) {
        if (StrUtil.isEmpty(token)) {
            return null;
        }

        try {
            // 1. 从 Redis 获取凭证信息
            CredentialInfo credentialInfo = getCredentialFromRedis(token);
            if (credentialInfo == null) {
                return null;
            }

            // 2. 检查是否过期
            if (credentialInfo.getExpiresTime().isBefore(LocalDateTime.now())) {
                // 删除过期的凭证
                deleteCredential(token);
                return null;
            }

            return credentialInfo;
        } catch (Exception e) {
            log.warn("[verifyCredential] 验证凭证失败，token={}", token, e);
            return null;
        }
    }

    /**
     * 验证系统密钥
     *
     * @param systemName 系统名称
     * @param secretKey  密钥
     */
    private boolean validateSecret(String systemName, String secretKey) {
        SystemSecretDO systemSecretDO = systemSecretService.getBySystemName(systemName);

        String storedSecret = systemSecretDO.getSecret();
        if (!storedSecret.equals(secretKey)) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 加密凭证信息
     *
     * @param info 凭证信息
     * @return 加密后的 token
     */
    private String encryptCredential(CredentialInfo info) {
        AES aes = SecureUtil.aes(credentialSecretKey.getBytes(StandardCharsets.UTF_8));
        String json = JsonUtils.toJsonString(info);
        byte[] encrypted = aes.encrypt(json.getBytes(StandardCharsets.UTF_8));
        return Base64.encode(encrypted);
    }

    /**
     * 解密凭证信息
     *
     * @param token 凭证 token
     * @return 凭证信息
     */
    private CredentialInfo decryptCredential(String token) {
        try {
            AES aes = SecureUtil.aes(credentialSecretKey.getBytes(StandardCharsets.UTF_8));
            byte[] decrypted = aes.decrypt(Base64.decode(token));
            String json = new String(decrypted, StandardCharsets.UTF_8);
            return JsonUtils.parseObject(json, CredentialInfo.class);
        } catch (Exception e) {
            log.error("[decryptCredential] 解密凭证失败", e);
            return null;
        }
    }

    /**
     * 缓存凭证信息到 Redis
     *
     * @param token 凭证 token
     * @param info  凭证信息
     */
    private void cacheCredential(String token, CredentialInfo info) {
        String redisKey = formatRedisKey(token);
        String jsonValue = JsonUtils.toJsonString(info);

        // 计算剩余过期时间（秒）
        long remainingSeconds = Duration.between(LocalDateTime.now(), info.getExpiresTime()).getSeconds();
        if (remainingSeconds > 0) {
            stringRedisTemplate.opsForValue().set(redisKey, jsonValue, remainingSeconds, TimeUnit.SECONDS);
        }
    }

    /**
     * 从 Redis 获取凭证信息
     *
     * @param token 凭证 token
     * @return 凭证信息
     */
    private CredentialInfo getCredentialFromRedis(String token) {
        String redisKey = formatRedisKey(token);
        String jsonValue = stringRedisTemplate.opsForValue().get(redisKey);
        if (StrUtil.isEmpty(jsonValue)) {
            return null;
        }
        return JsonUtils.parseObject(jsonValue, CredentialInfo.class);
    }

    /**
     * 删除凭证
     *
     * @param token 凭证 token
     */
    private void deleteCredential(String token) {
        String redisKey = formatRedisKey(token);
        stringRedisTemplate.delete(redisKey);
    }

    /**
     * 格式化 Redis Key
     *
     * @param token 凭证 token
     * @return Redis Key
     */
    private String formatRedisKey(String token) {
        return "credential:token:" + token;
    }

    /**
     * 凭证信息内部类
     */
    @Data
    @AllArgsConstructor
    public static class CredentialInfo {
        private String systemName;
        private LocalDateTime expiresTime;
    }
}
