package io.cameleer.server.app.web;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Duration;
import java.util.Base64;
import java.util.UUID;

/**
 * HMAC-SHA256 signed URL tokens for artifact downloads. Key derivation is
 * deterministic from the JWT signing secret (HMAC-SHA256(jwtSecret,
 * "cameleer-artifact-token-v1")) so server restarts don't invalidate fresh
 * tokens. The loader container does NOT carry the JWT or the bootstrap token —
 * it only carries the signed URL, which is scoped to one appVersionId and one
 * short TTL.
 */
public class ArtifactDownloadTokenSigner {

    private static final String DERIVATION = "cameleer-artifact-token-v1";

    public record SignedToken(long exp, String sig) {}

    private final byte[] key;
    private final Clock clock;

    public ArtifactDownloadTokenSigner(String jwtSecret, Clock clock) {
        if (jwtSecret == null || jwtSecret.isBlank()) {
            throw new IllegalArgumentException("jwtSecret must not be null or blank");
        }
        this.key = deriveKey(jwtSecret);
        this.clock = clock;
    }

    private static byte[] deriveKey(String jwtSecret) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(jwtSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            return mac.doFinal(DERIVATION.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new IllegalStateException("HMAC init failed", e);
        }
    }

    public SignedToken sign(UUID appVersionId, Duration ttl) {
        long exp = clock.instant().plus(ttl).getEpochSecond();
        return new SignedToken(exp, signRaw(appVersionId, exp));
    }

    public String signRaw(UUID appVersionId, long exp) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key, "HmacSHA256"));
            String payload = appVersionId + ":" + exp;
            byte[] tag = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
            return Base64.getUrlEncoder().withoutPadding().encodeToString(tag);
        } catch (Exception e) {
            throw new IllegalStateException("sign failed", e);
        }
    }

    public boolean verify(UUID appVersionId, long exp, String sig) {
        if (sig == null || sig.isBlank()) return false;
        if (clock.instant().getEpochSecond() > exp) return false;
        String expected = signRaw(appVersionId, exp);
        // constant-time compare
        return java.security.MessageDigest.isEqual(
                expected.getBytes(StandardCharsets.UTF_8),
                sig.getBytes(StandardCharsets.UTF_8));
    }
}
