package cn.novalon.manage.gateway.service.impl;

import cn.novalon.manage.gateway.service.SignatureService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Service;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 请求签名服务实现
 * 
 * 文件定义：实现API请求签名生成和验证功能
 * 涉及业务：API安全防护，防止请求篡改和重放攻击
 * 算法：HMAC-SHA256签名算法
 * 
 * 签名算法：
 * 1. 构造签名字符串：METHOD + "\n" + PATH + "\n" + QUERY + "\n" + BODY + "\n" + TIMESTAMP + "\n" + NONCE
 * 2. 使用HMAC-SHA256算法对签名字符串进行签名
 * 3. 将签名结果进行Base64编码
 * 
 * 防重放攻击：
 * 1. 检查时间戳是否在有效期内（默认5分钟）
 * 2. 检查nonce是否已使用（使用ConcurrentHashMap存储）
 * 3. 定期清理过期的nonce记录
 * 
 * @author 张翔
 * @date 2026-03-26
 */
@Service
public class SignatureServiceImpl implements SignatureService {

    private static final Logger logger = LoggerFactory.getLogger(SignatureServiceImpl.class);
    private static final String HMAC_SHA256 = "HmacSHA256";
    private static final String SIGNATURE_HEADER = "X-Signature";
    private static final String TIMESTAMP_HEADER = "X-Timestamp";
    private static final String NONCE_HEADER = "X-Nonce";

    @Value("${signature.enabled:true}")
    private boolean signatureEnabled;

    @Value("${signature.max-age-minutes:5}")
    private int maxAgeMinutes;

    @Value("${signature.nonce-cache-size:10000}")
    private int nonceCacheSize;

    private final ConcurrentHashMap<String, Long> nonceCache = new ConcurrentHashMap<>();

    @Override
    public String generateSignature(
            String method, 
            String path, 
            String query, 
            String body, 
            long timestamp, 
            String nonce, 
            String secret) {
        try {
            String stringToSign = buildStringToSign(method, path, query, body, timestamp, nonce);
            logger.debug("String to sign: {}", stringToSign);

            Mac mac = Mac.getInstance(HMAC_SHA256);
            SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA256);
            mac.init(secretKeySpec);

            byte[] signatureBytes = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
            String signature = Base64.getEncoder().encodeToString(signatureBytes);

            logger.debug("Generated signature: {}", signature);
            return signature;

        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            logger.error("Failed to generate signature", e);
            throw new RuntimeException("Signature generation failed", e);
        }
    }

    @Override
    public boolean verifySignature(ServerHttpRequest request, String secret) {
        if (!signatureEnabled) {
            logger.debug("Signature verification is disabled");
            return true;
        }

        String signature = request.getHeaders().getFirst(SIGNATURE_HEADER);
        String timestampStr = request.getHeaders().getFirst(TIMESTAMP_HEADER);
        String nonce = request.getHeaders().getFirst(NONCE_HEADER);

        if (signature == null || timestampStr == null || nonce == null) {
            logger.warn("Missing signature headers - Signature: {}, Timestamp: {}, Nonce: {}", 
                    signature, timestampStr, nonce);
            return false;
        }

        try {
            long timestamp = Long.parseLong(timestampStr);

            if (!isTimestampValid(timestamp, maxAgeMinutes)) {
                logger.warn("Timestamp is invalid or expired: {}", timestamp);
                return false;
            }

            if (isNonceUsed(nonce)) {
                logger.warn("Nonce has been used: {}", nonce);
                return false;
            }

            String method = request.getMethod().name();
            String path = request.getPath().value();
            String query = request.getURI().getQuery() != null ? request.getURI().getQuery() : "";
            String body = ""; // 在WebFlux中，请求体需要特殊处理

            String expectedSignature = generateSignature(method, path, query, body, timestamp, nonce, secret);

            boolean isValid = signature.equals(expectedSignature);
            
            if (isValid) {
                recordNonce(nonce);
                logger.debug("Signature verification passed for request: {} {}", method, path);
            } else {
                logger.warn("Signature verification failed - Expected: {}, Actual: {}", expectedSignature, signature);
            }

            return isValid;

        } catch (NumberFormatException e) {
            logger.error("Invalid timestamp format: {}", timestampStr, e);
            return false;
        }
    }

    @Override
    public boolean isTimestampValid(long timestamp, int maxAgeMinutes) {
        long currentTime = System.currentTimeMillis();
        long timeDifference = Math.abs(currentTime - timestamp);
        long maxAgeMillis = TimeUnit.MINUTES.toMillis(maxAgeMinutes);

        boolean isValid = timeDifference <= maxAgeMillis;
        
        if (!isValid) {
            logger.debug("Timestamp validation failed - Current: {}, Request: {}, Difference: {}ms, Max: {}ms",
                    currentTime, timestamp, timeDifference, maxAgeMillis);
        }

        return isValid;
    }

    @Override
    public boolean isNonceUsed(String nonce) {
        return nonceCache.containsKey(nonce);
    }

    @Override
    public void recordNonce(String nonce) {
        nonceCache.put(nonce, System.currentTimeMillis());
        logger.debug("Recorded nonce: {}", nonce);

        if (nonceCache.size() > nonceCacheSize) {
            cleanupExpiredNonces();
        }
    }

    @Override
    public void cleanupExpiredNonces() {
        long currentTime = System.currentTimeMillis();
        long expirationTime = TimeUnit.MINUTES.toMillis(maxAgeMinutes * 2);

        int initialSize = nonceCache.size();
        
        nonceCache.entrySet().removeIf(entry -> 
                (currentTime - entry.getValue()) > expirationTime);

        int removedCount = initialSize - nonceCache.size();
        
        if (removedCount > 0) {
            logger.info("Cleaned up {} expired nonces. Current cache size: {}", 
                    removedCount, nonceCache.size());
        }
    }

    private String buildStringToSign(
            String method, 
            String path, 
            String query, 
            String body, 
            long timestamp, 
            String nonce) {
        StringBuilder sb = new StringBuilder();
        sb.append(method).append("\n");
        sb.append(path).append("\n");
        sb.append(query != null ? query : "").append("\n");
        sb.append(body != null ? body : "").append("\n");
        sb.append(timestamp).append("\n");
        sb.append(nonce);
        return sb.toString();
    }

    public int getNonceCacheSize() {
        return nonceCache.size();
    }
}
