// Vérifier la signature d'un webhook Tauraco (Standard Webhooks) — Java 17+, sans dépendance.
// Verify a Tauraco webhook signature (Standard Webhooks) — Java 17+, no dependency.
// Vecteur de référence (page « Guide des webhooks ») :
//   secret            whsec_dGF1cmFjby1leGVtcGxlLWRlLXZlY3RldXItMjAyNiE=
//   webhook-id        evt_exemple_0001
//   webhook-timestamp 1793750400
//   corps             {"id":"evt_exemple_0001","type":"webhook.test","data":{"message":"ok"}}
//   signature         v1,z25573sJuRUxUk3JkjiZN3OW2KihQFtLvIGhZTHUnuM=
// Un secret vide ou illisible est refusé ; une signature candidate illisible est ignorée
// (on essaie la suivante : l'en-tête peut en porter plusieurs).
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public final class VerifierWebhook {
    private static final long TOLERANCE_SECONDS = 300;

    private VerifierWebhook() {}

    /** {@code headers} : noms en minuscules ; {@code body} : corps BRUT reçu. */
    public static boolean verifySignature(String secret, Map<String, String> headers, byte[] body, long now) {
        String id = headers.get("webhook-id");
        String rawTimestamp = headers.get("webhook-timestamp");
        String signatures = headers.get("webhook-signature");
        if (id == null || rawTimestamp == null || signatures == null) return false;
        long timestamp;
        try {
            timestamp = Long.parseLong(rawTimestamp.trim());
        } catch (NumberFormatException e) {
            return false;
        }
        if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false;
        byte[] key;
        try {
            key = Base64.getDecoder().decode(secret.startsWith("whsec_") ? secret.substring(6) : secret);
        } catch (IllegalArgumentException e) {
            return false; // secret illisible
        }
        if (key.length == 0) return false; // clé vide : n'importe qui pourrait signer
        byte[] expected;
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key, "HmacSHA256"));
            mac.update((id + "." + timestamp + ".").getBytes(StandardCharsets.UTF_8));
            expected = mac.doFinal(body);
        } catch (java.security.GeneralSecurityException e) {
            return false;
        }
        for (String candidate : signatures.split(" ")) {
            String[] parts = candidate.split(",", 2);
            if (parts.length != 2 || !parts[0].equals("v1")) continue;
            byte[] received;
            try {
                received = Base64.getDecoder().decode(parts[1]);
            } catch (IllegalArgumentException e) {
                continue; // candidate illisible : essayer la suivante
            }
            if (MessageDigest.isEqual(received, expected)) return true;
        }
        return false;
    }

    public static boolean verifySignature(String secret, Map<String, String> headers, byte[] body) {
        return verifySignature(secret, headers, body, Instant.now().getEpochSecond());
    }
}
