/*
 * CoinSwitch PRO — HFT (low-latency futures + options) client (Java).
 *
 * Single-file class. Every HFT REST endpoint is a method.
 *
 * HFT base URL: https://dma.coinswitch.co/
 * Response envelope: {retCode, retMsg, result, retExtInfo, time}.
 * Always check retCode == 0 before reading result.
 */
package com.example.coinswitch;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.util.encoders.Hex;

import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class CoinswitchHFT {

    public static final String BASE_URL = "https://dma.coinswitch.co";

    private final String apiKey;
    private final String secretKey;
    private final String baseUrl;
    private final HttpClient http = HttpClient.newHttpClient();
    private final ObjectMapper json = new ObjectMapper();

    public CoinswitchHFT(String apiKey, String secretKey) {
        this(apiKey, secretKey, BASE_URL);
    }

    public CoinswitchHFT(String apiKey, String secretKey, String baseUrl) {
        this.apiKey = apiKey;
        this.secretKey = secretKey;
        this.baseUrl = baseUrl;
    }

    public HttpResponse<String> send(
            String method, String path,
            Map<String, String> params, Map<String, Object> body) throws Exception {
        method = method.toUpperCase();
        if (params != null && !params.isEmpty()) {
            StringBuilder qs = new StringBuilder();
            for (Map.Entry<String, String> e : params.entrySet()) {
                if (qs.length() > 0) qs.append("&");
                qs.append(URLEncoder.encode(e.getKey(), "UTF-8"));
                qs.append("=").append(URLEncoder.encode(e.getValue(), "UTF-8"));
            }
            path += (path.contains("?") ? "&" : "?") + qs;
        }
        String decodedPath = URLDecoder.decode(path, "UTF-8");
        String epoch = String.valueOf(System.currentTimeMillis());
        String message = method + decodedPath + epoch;
        String signature = signEd25519(secretKey, message);

        HttpRequest.Builder req = HttpRequest.newBuilder()
            .uri(new URI(baseUrl + decodedPath))
            .header("Content-Type", "application/json")
            .header("X-AUTH-APIKEY", apiKey)
            .header("X-AUTH-SIGNATURE", signature)
            .header("X-AUTH-EPOCH", epoch);

        String bodyJson = (body == null) ? "" : json.writeValueAsString(body);
        switch (method) {
            case "GET":  req.GET(); break;
            case "POST": req.POST(BodyPublishers.ofString(bodyJson)); break;
            default: throw new IllegalArgumentException("Unsupported: " + method);
        }
        return http.send(req.build(), HttpResponse.BodyHandlers.ofString());
    }

    private static String signEd25519(String secretKeyHex, String message) {
        byte[] secretBytes = Hex.decode(secretKeyHex);
        Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(secretBytes, 0);
        Ed25519Signer signer = new Ed25519Signer();
        signer.init(true, privateKey);
        byte[] msg = message.getBytes(StandardCharsets.UTF_8);
        signer.update(msg, 0, msg.length);
        return Hex.toHexString(signer.generateSignature());
    }

    // ---- Market data ----
    public String serverTime() throws Exception {
        return send("GET", "/v5/market/time", null, null).body();
    }
    public String instrumentsInfo(String category, String symbol, Integer limit) throws Exception {
        Map<String, String> p = new HashMap<>();
        p.put("category", category == null ? "linear" : category);
        if (symbol != null) p.put("symbol", symbol);
        if (limit  != null) p.put("limit",  String.valueOf(limit));
        return send("GET", "/v5/market/instruments-info", p, null).body();
    }
    public String tickers(String category, String symbol) throws Exception {
        Map<String, String> p = new HashMap<>();
        p.put("category", category == null ? "linear" : category);
        if (symbol != null) p.put("symbol", symbol);
        return send("GET", "/v5/market/tickers", p, null).body();
    }
    public String orderBook(String category, String symbol, Integer limit) throws Exception {
        Map<String, String> p = new HashMap<>();
        p.put("category", category == null ? "linear" : category);
        p.put("symbol", symbol);
        if (limit != null) p.put("limit", String.valueOf(limit));
        return send("GET", "/v5/market/orderbook", p, null).body();
    }

    // ---- Orders ----
    /**
     * Body: category, symbol, side ("Buy"/"Sell"), orderType ("Limit"/"Market"),
     * qty (string), price (string for Limit), positionIdx (0/1/2), timeInForce
     * (default "GTC"). We auto-mint orderLinkId if missing (idempotency key).
     */
    public String placeOrder(Map<String, Object> body) throws Exception {
        Map<String, Object> b = new HashMap<>(body);
        b.putIfAbsent("category", "linear");
        b.putIfAbsent("timeInForce", "GTC");
        b.putIfAbsent("orderLinkId", UUID.randomUUID().toString());
        return send("POST", "/v5/order/create", null, b).body();
    }
    public String orderRealtime(Map<String, String> filters) throws Exception {
        Map<String, String> p = new HashMap<>();
        p.put("category", "linear");
        if (filters != null) p.putAll(filters);
        return send("GET", "/v5/order/realtime", p, null).body();
    }
    public String executionList(Map<String, String> filters) throws Exception {
        Map<String, String> p = new HashMap<>();
        p.put("category", "linear");
        if (filters != null) p.putAll(filters);
        return send("GET", "/v5/execution/list", p, null).body();
    }

    // ---- Positions ----
    public String positions(String symbolOrNull, String cursorOrNull) throws Exception {
        Map<String, String> p = new HashMap<>();
        p.put("category", "linear");
        if (symbolOrNull != null) p.put("symbol", symbolOrNull);
        if (cursorOrNull != null) p.put("cursor", cursorOrNull);
        return send("GET", "/v5/position/list", p, null).body();
    }
    public String setLeverage(String symbol, double buyLeverage, double sellLeverage) throws Exception {
        return send("POST", "/v5/position/set-leverage", null, Map.of(
            "category", "linear",
            "symbol", symbol,
            "buyLeverage", String.valueOf(buyLeverage),
            "sellLeverage", String.valueOf(sellLeverage)
        )).body();
    }
    /** mode: 0 = one-way, 3 = hedge. */
    public String switchPositionMode(int mode, String coin) throws Exception {
        return send("POST", "/v5/position/switch-mode", null, Map.of(
            "category", "linear",
            "coin", coin == null ? "USDT" : coin,
            "mode", mode
        )).body();
    }

    // ---- Account ----
    /**
     * direction: "IN" or "OUT". quoteAsset: "USDT" or "INR" (locked on
     * first transfer). clientTxnId: pass a UUID as your idempotency key.
     */
    public String transferFunds(String direction, double amount,
                                String quoteAsset, String clientTxnId) throws Exception {
        return send("POST", "/dma/api/v1/funds/transfer", null, Map.of(
            "direction", direction,
            "amount", amount,
            "quote_asset", quoteAsset == null ? "USDT" : quoteAsset,
            "client_txn_id", clientTxnId == null ? UUID.randomUUID().toString() : clientTxnId
        )).body();
    }

    // ---- WebSocket signature for HFT private NATS stream ----
    public String socketSignature(int expiresInSeconds) throws Exception {
        return send("GET", "/dma/api/v1/socket/signature",
            Map.of("expires_in_seconds", String.valueOf(expiresInSeconds)),
            null).body();
    }
}
