/*
 * CoinSwitch PRO — Futures (perpetual) trading client (Java).
 *
 * Single-file class. Every Futures REST endpoint is a method.
 *
 * Dependencies (Gradle):
 *   implementation 'org.bouncycastle:bcprov-jdk18on:1.78'
 *   implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'
 */
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 CoinswitchFutures {

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

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

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

    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(BASE_URL + 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;
            case "DELETE": req.method("DELETE", BodyPublishers.ofString(bodyJson)); break;
            case "PUT":    req.PUT(BodyPublishers.ofString(bodyJson)); break;
            default: throw new IllegalArgumentException("Unsupported method: " + 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());
    }

    // ---- Account ----
    public String walletBalance() throws Exception {
        return send("GET", "/trade/api/v2/futures/wallet_balance", null, null).body();
    }
    public String transactions(Map<String, String> filters) throws Exception {
        Map<String, String> p = new HashMap<>();
        p.put("exchange", EXCHANGE);
        if (filters != null) p.putAll(filters);
        return send("GET", "/trade/api/v2/futures/transactions", p, null).body();
    }

    // ---- Reference data ----
    public String instrumentInfo(String symbolOrNull) throws Exception {
        Map<String, String> p = new HashMap<>();
        p.put("exchange", EXCHANGE);
        if (symbolOrNull != null) p.put("symbol", symbolOrNull);
        return send("GET", "/trade/api/v2/futures/instrument_info", p, null).body();
    }
    public String orderBook(String symbol) throws Exception {
        return send("GET", "/trade/api/v2/futures/order_book",
            Map.of("exchange", EXCHANGE, "symbol", symbol), null).body();
    }
    public String ticker(String symbol) throws Exception {
        return send("GET", "/trade/api/v2/futures/ticker",
            Map.of("exchange", EXCHANGE, "symbol", symbol), null).body();
    }
    public String allPairsTicker() throws Exception {
        return send("GET", "/trade/api/v2/futures/all-pairs/ticker",
            Map.of("exchange", EXCHANGE), null).body();
    }
    public String klines(String symbol, String interval,
                         long startTime, long endTime) throws Exception {
        return send("GET", "/trade/api/v2/futures/klines", Map.of(
            "exchange", EXCHANGE, "symbol", symbol,
            "interval", interval,
            "start_time", String.valueOf(startTime),
            "end_time", String.valueOf(endTime)
        ), null).body();
    }
    public String marketTrades(String symbol) throws Exception {
        return send("GET", "/trade/api/v2/futures/trades",
            Map.of("exchange", EXCHANGE, "symbol", symbol), null).body();
    }

    // ---- Orders ----
    /**
     * Body must include symbol, side, order_type, quantity (and price for
     * LIMIT, trigger_price+reduce_only=true for TP/SL). We auto-mint a
     * client_order_id (idempotency key) if the caller doesn't provide one.
     */
    public String placeOrder(Map<String, Object> body) throws Exception {
        Map<String, Object> b = new HashMap<>(body);
        b.putIfAbsent("exchange", EXCHANGE);
        b.putIfAbsent("client_order_id", UUID.randomUUID().toString());
        return send("POST", "/trade/api/v2/futures/order", null, b).body();
    }
    public String cancelOrder(String orderId) throws Exception {
        return send("DELETE", "/trade/api/v2/futures/order", null,
            Map.of("exchange", EXCHANGE, "order_id", orderId)).body();
    }
    public String cancelAllOpenOrders(String symbolOrNull) throws Exception {
        Map<String, Object> b = new HashMap<>();
        b.put("exchange", EXCHANGE);
        if (symbolOrNull != null) b.put("symbol", symbolOrNull);
        return send("POST", "/trade/api/v2/futures/cancel-all", null, b).body();
    }
    public String getOrderStatus(String orderId) throws Exception {
        return send("GET", "/trade/api/v2/futures/order",
            Map.of("exchange", EXCHANGE, "order_id", orderId), null).body();
    }
    public String openOrders(String symbolOrNull) throws Exception {
        Map<String, Object> body = new HashMap<>();
        body.put("exchange", EXCHANGE);
        if (symbolOrNull != null) body.put("symbol", symbolOrNull);
        return send("POST", "/trade/api/v2/futures/orders/open", null, body).body();
    }
    public String closedOrders(String symbolOrNull) throws Exception {
        Map<String, Object> body = new HashMap<>();
        body.put("exchange", EXCHANGE);
        if (symbolOrNull != null) body.put("symbol", symbolOrNull);
        return send("POST", "/trade/api/v2/futures/orders/closed", null, body).body();
    }

    // ---- Positions, leverage, margin ----
    public String positions(String symbolOrNull) throws Exception {
        Map<String, String> p = new HashMap<>();
        p.put("exchange", EXCHANGE);
        if (symbolOrNull != null) p.put("symbol", symbolOrNull);
        return send("GET", "/trade/api/v2/futures/positions", p, null).body();
    }
    public String getLeverage(String symbol) throws Exception {
        return send("GET", "/trade/api/v2/futures/leverage",
            Map.of("exchange", EXCHANGE, "symbol", symbol), null).body();
    }
    public String updateLeverage(String symbol, int leverage) throws Exception {
        return send("POST", "/trade/api/v2/futures/leverage", null,
            Map.of("exchange", EXCHANGE, "symbol", symbol,
                   "leverage", leverage)).body();
    }
    public String addMargin(String symbol, double margin) throws Exception {
        return send("POST", "/trade/api/v2/futures/add_margin", null,
            Map.of("exchange", EXCHANGE, "symbol", symbol,
                   "margin", margin)).body();
    }
}
