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

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
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
@RequiredArgsConstructor
public class CredentialServiceImpl implements ICredentialService {

    private static final long CREDENTIAL_EXPIRE_HOURS = 24L;
    private static final long LOCK_WAIT_SECONDS = 3L;
    private static final long LOCK_LEASE_SECONDS = 5L;
    private static final String SYSTEM_KEY_PREFIX = "credential:";
    private static final String LOCK_KEY_PREFIX = "credential:lock:";

    private String credentialSecretKey = "88888888888888888888888888888888"; // 32 瀛楄妭

    private final ISystemSecretService systemSecretService;
    private final StringRedisTemplate stringRedisTemplate;
    private final RedissonClient redissonClient;

    @Override
    public CredentialRespDTO generateCredential(CredentialReqDTO reqDTO) {
        String systemName = reqDTO.getSystemName();

        boolean validatedSecretRes = validateSecret(systemName, reqDTO.getSecretKey());
        if (!validatedSecretRes) {
            throw new ServiceException(ServiceErrorCodeRange.VALIDATE_SYSTEM_SECRET_FAIL);
        }

        LocalDateTime expiresTime = LocalDateTime.now().plusHours(CREDENTIAL_EXPIRE_HOURS);
        String credential = encryptCredential(systemName, expiresTime);

        RLock lock = redissonClient.getLock(formatLockKey(systemName));
        boolean locked = false;
        try {
            locked = lock.tryLock(LOCK_WAIT_SECONDS, LOCK_LEASE_SECONDS, TimeUnit.SECONDS);
            if (!locked) {
                log.warn("[generateCredential][systemName({}) failed to acquire lock within {} seconds]",
                        systemName, LOCK_WAIT_SECONDS);
                throw new ServiceException(ServiceErrorCodeRange.GENERATE_CREDENTIAL_FAIL);
            }

            deleteCredential(systemName);
            CredentialInfo credentialInfo = new CredentialInfo(systemName, expiresTime, credential);
            cacheCredential(systemName, credentialInfo);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("[generateCredential][systemName({}) interrupted while waiting for lock]", systemName, e);
            throw new ServiceException(ServiceErrorCodeRange.GENERATE_CREDENTIAL_FAIL);
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

        return new CredentialRespDTO()
                .setCredentialToken(systemName + StrUtil.COLON + credential)
                .setSystemName(systemName)
                .setExpiresTime(expiresTime);
    }

    /**
     * 验证凭证
     *
     * @param credentialToken 凭证 credentialToken
     * @return
     */
    @Override
    public CredentialInfo verifyCredential(String credentialToken) {
        if (StrUtil.isEmpty(credentialToken)) {
            return null;
        }
        String[] split = credentialToken.split(StrUtil.COLON);
        if (split.length != 2) {
            return null;
        }

        String systemName = split[0];
        CredentialInfo credentialInfo = getCredentialFromRedis(systemName);
        if (ObjectUtil.isNull(credentialInfo)) {
            return null;
        }

        if (credentialInfo.getExpiresTime().isBefore(LocalDateTime.now()) || !credentialInfo.getCredential().equals(split[1])) {
            return null;
        }

        return credentialInfo;
    }


    /**
     * 校验系统secretKey
     *
     * @param systemName
     * @param secretKey
     * @return
     */
    private boolean validateSecret(String systemName, String secretKey) {
        SystemSecretDO systemSecretDO = systemSecretService.getBySystemName(systemName);
        return systemSecretDO != null && StrUtil.equals(systemSecretDO.getSecret(), secretKey);
    }

    /**
     * 获取加密后的凭证credential
     *
     * @param systemName  系统名称
     * @param expiresTime 过期时间
     * @return
     */
    private String encryptCredential(String systemName, LocalDateTime expiresTime) {
        // 生成随机 IV
        byte[] iv = RandomUtil.randomBytes(16);

        // 创建 AES 实例
        AES aes = new AES(
                Mode.CBC,
                Padding.PKCS5Padding,
                credentialSecretKey.getBytes(StandardCharsets.UTF_8),
                iv
        );
        String origin = systemName + StrUtil.C_COLON + expiresTime.getNano();
        byte[] encrypted = aes.encrypt(origin.getBytes(StandardCharsets.UTF_8));
        return Base64.encode(encrypted);
    }

    /**
     * 缓存系统凭证credential
     *
     * @param systemName
     * @param info
     */
    private void cacheCredential(String systemName, CredentialInfo info) {
        String credentialKey = formatCredentialKey(systemName);
        String jsonValue = JsonUtils.toJsonString(info);

        long remainingSeconds = Duration.between(LocalDateTime.now(), info.getExpiresTime()).getSeconds();
        if (remainingSeconds > 0) {
            stringRedisTemplate.opsForValue().set(credentialKey, jsonValue, remainingSeconds, TimeUnit.SECONDS);
        }
    }

    /**
     * 从redis中获取系统凭证信息
     *
     * @param systemName
     * @return
     */
    private CredentialInfo getCredentialFromRedis(String systemName) {
        String credentialKey = formatCredentialKey(systemName);
        String jsonValue = stringRedisTemplate.opsForValue().get(credentialKey);
        if (StrUtil.isEmpty(jsonValue)) {
            return null;
        }
        return JsonUtils.parseObject(jsonValue, CredentialInfo.class);
    }


    private void deleteCredential(String systemName) {
        String credentialKey = formatCredentialKey(systemName);
        String oldToken = stringRedisTemplate.opsForValue().get(credentialKey);
        if (StrUtil.isNotEmpty(oldToken)) {
            stringRedisTemplate.delete(credentialKey);
        }
    }

    private String formatCredentialKey(String systemName) {
        return SYSTEM_KEY_PREFIX + systemName;
    }

    /**
     * 获取锁的key（用于针对同一时刻多个请求，保证多个请求只对同一个key进行加锁）
     *
     * @param systemName
     * @return
     */
    private String formatLockKey(String systemName) {
        return LOCK_KEY_PREFIX + systemName;
    }

    @Data
    @AllArgsConstructor
    public static class CredentialInfo {
        private String systemName;
        private LocalDateTime expiresTime;
        private String credential;
    }
}
