package cn.qihangerp.security.external;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Clock;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * In-memory anti-replay (nonce) + idempotency caches.
 *
 * <p>首期按“单体优先 + 同步接口”落地，这里使用内存缓存，不引入额外依赖、不改库。
 * 后续如需多实例/重启不丢，可替换为 Redis/DB。</p>
 */
public class ExternalRequestCaches {
    private final Clock clock;
    private final Map<String, Long> nonceExpireAtMs = new ConcurrentHashMap<>();
    private final Map<String, CachedResponse> idempotency = new ConcurrentHashMap<>();

    public ExternalRequestCaches(Clock clock) {
        this.clock = Objects.requireNonNull(clock, "clock");
    }

    public boolean tryUseNonce(String nonce, long nowMs, long ttlMs) {
        cleanupNonce(nowMs);
        long expireAt = nowMs + ttlMs;
        return nonceExpireAtMs.putIfAbsent(nonce, expireAt) == null;
    }

    public CachedResponse getIdempotency(String key, long nowMs) {
        cleanupIdempotency(nowMs);
        CachedResponse r = idempotency.get(key);
        if (r == null) return null;
        return r.expireAtMs > nowMs ? r : null;
    }

    public void putIdempotency(String key, CachedResponse resp) {
        idempotency.put(key, resp);
    }

    public long nowMs() {
        return clock.millis();
    }

    private void cleanupNonce(long nowMs) {
        if (nonceExpireAtMs.size() < 10_000) return;
        nonceExpireAtMs.entrySet().removeIf(e -> e.getValue() <= nowMs);
    }

    private void cleanupIdempotency(long nowMs) {
        if (idempotency.size() < 10_000) return;
        idempotency.entrySet().removeIf(e -> e.getValue().expireAtMs <= nowMs);
    }

    public static String sha256Hex(byte[] bytes) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] digest = md.digest(bytes);
            StringBuilder sb = new StringBuilder(digest.length * 2);
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new IllegalStateException("sha256 failed", e);
        }
    }

    public static String hmacSha256Base64(String secret, String message) {
        try {
            javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
            mac.init(new javax.crypto.spec.SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            byte[] sig = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(sig);
        } catch (Exception e) {
            throw new IllegalStateException("hmac failed", e);
        }
    }

    public record CachedResponse(int httpStatus, String contentType, byte[] body, long expireAtMs) {
    }
}

