// iCryptoNow Gateway — request signing and webhook verification (Java 11+, JDK only).
//
//   X-Signature = hex(HMAC-SHA256(secret, timestamp + "\n" + METHOD + "\n" + pathWithQuery + "\n" + rawBody))
//   timestamp   = unix milliseconds, within ±5 minutes of our clock
//
// Self-test against the published vectors:  java Gateway.java ../vectors.json

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.Locale;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class Gateway {

    public static String sign(String secret, String timestamp, String method, String pathWithQuery, String body) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        String message = timestamp + "\n" + method.toUpperCase(Locale.ROOT) + "\n" + pathWithQuery + "\n" + body;
        StringBuilder hex = new StringBuilder();
        for (byte b : mac.doFinal(message.getBytes(StandardCharsets.UTF_8))) hex.append(String.format("%02x", b));
        return hex.toString();
    }

    /** Call the gateway. jsonBody is the JSON string you send (sign exactly these bytes), or "" for none. */
    public static HttpResponse<String> request(String baseUrl, String keyId, String secret, String method, String pathWithQuery, String jsonBody) throws Exception {
        String timestamp = String.valueOf(System.currentTimeMillis());
        HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(baseUrl + pathWithQuery))
            .timeout(Duration.ofSeconds(15))
            .header("X-Key-Id", keyId)
            .header("X-Timestamp", timestamp)
            .header("X-Signature", sign(secret, timestamp, method, pathWithQuery, jsonBody));
        if (jsonBody.isEmpty()) {
            b.method(method, HttpRequest.BodyPublishers.noBody());
        } else {
            b.header("Content-Type", "application/json").method(method, HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8));
        }
        return HttpClient.newHttpClient().send(b.build(), HttpResponse.BodyHandlers.ofString());
    }

    /** Verify a callback: rawBody is the exact request body (verify BEFORE parsing); pathWithQuery is your callback path. */
    public static boolean verifyWebhook(String secret, String pathWithQuery, String timestamp, String signature, String rawBody, long nowMs) throws Exception {
        if (timestamp == null || !timestamp.matches("\\d{13}") || signature == null || !signature.matches("[0-9a-f]{64}")) return false;
        if (Math.abs(nowMs - Long.parseLong(timestamp)) > 5 * 60 * 1000) return false;
        byte[] expected = sign(secret, timestamp, "POST", pathWithQuery, rawBody).getBytes(StandardCharsets.US_ASCII);
        return MessageDigest.isEqual(expected, signature.getBytes(StandardCharsets.US_ASCII));
    }

    // ── self-test (tiny JSON reader for the flat vectors file; use your JSON library in real code) ──
    static String field(String json, String section, String key) {
        int s = json.indexOf("\"" + section + "\"");
        int k = json.indexOf("\"" + key + "\"", s);
        int i = json.indexOf('"', json.indexOf(':', k) + 1) + 1;
        StringBuilder out = new StringBuilder();
        for (; json.charAt(i) != '"'; i++) {
            char c = json.charAt(i);
            if (c == '\\') { char n = json.charAt(++i); out.append(n == 'n' ? '\n' : n); } else out.append(c);
        }
        return out.toString();
    }

    static boolean failed = false;

    static void check(String name, boolean ok) {
        System.out.println((ok ? "PASS " : "FAIL ") + name);
        failed |= !ok;
    }

    public static void main(String[] args) throws Exception {
        String json = Files.readString(Path.of(args.length > 0 ? args[0] : "../vectors.json"));
        for (String k : new String[] {"request_post", "request_get"}) {
            check(k, sign(field(json, k, "secret"), field(json, k, "timestamp"), field(json, k, "method"), field(json, k, "path"), field(json, k, "body"))
                .equals(field(json, k, "signature")));
        }
        String sec = field(json, "webhook", "secret"), ts = field(json, "webhook", "timestamp"), p = field(json, "webhook", "path");
        String sig = field(json, "webhook", "signature"), body = field(json, "webhook", "body");
        long now = Long.parseLong(ts);
        check("webhook valid", verifyWebhook(sec, p, ts, sig, body, now));
        check("webhook tampered body rejected", !verifyWebhook(sec, p, ts, sig, body.replace("100000000", "900000000"), now));
        check("webhook stale timestamp rejected", !verifyWebhook(sec, p, ts, sig, body, now + 301000));
        if (failed) System.exit(1);
    }
}
