package io.synor.dex;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import okhttp3.*;

import java.io.IOException;
import java.math.BigInteger;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * Synor DEX SDK Client
 *
 * Complete decentralized exchange client with support for:
 * - AMM swaps (constant product, stable, concentrated)
 * - Liquidity provision
 * - Perpetual futures (up to 100x leverage)
 * - Order books (limit orders)
 * - Farming & staking
 */
public class SynorDex implements AutoCloseable {
    private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");

    private final DexConfig config;
    private final OkHttpClient client;
    private final Gson gson;
    private volatile boolean closed = false;

    public final PerpsClient perps;
    public final OrderBookClient orderbook;
    public final FarmsClient farms;

    public SynorDex(DexConfig config) {
        this.config = config;
        this.client = new OkHttpClient.Builder()
            .callTimeout(config.getTimeout(), TimeUnit.MILLISECONDS)
            .build();
        this.gson = new GsonBuilder().create();

        this.perps = new PerpsClient(this);
        this.orderbook = new OrderBookClient(this);
        this.farms = new FarmsClient(this);
    }

    // Token Operations

    public CompletableFuture<Token> getToken(String address) {
        return get("/tokens/" + address, Token.class);
    }

    public CompletableFuture<List<Token>> listTokens() {
        return getList("/tokens", Token.class);
    }

    public CompletableFuture<List<Token>> searchTokens(String query) {
        return getList("/tokens/search?q=" + urlEncode(query), Token.class);
    }

    // Pool Operations

    public CompletableFuture<Pool> getPool(String tokenA, String tokenB) {
        return get("/pools/" + tokenA + "/" + tokenB, Pool.class);
    }

    public CompletableFuture<Pool> getPoolById(String poolId) {
        return get("/pools/" + poolId, Pool.class);
    }

    public CompletableFuture<List<Pool>> listPools(PoolFilter filter) {
        StringBuilder params = new StringBuilder();
        if (filter != null) {
            if (filter.getTokens() != null) {
                params.append("tokens=").append(String.join(",", filter.getTokens()));
            }
            if (filter.getMinTvl() != null) {
                appendParam(params, "min_tvl", filter.getMinTvl().toString());
            }
            if (filter.getMinVolume24h() != null) {
                appendParam(params, "min_volume", filter.getMinVolume24h().toString());
            }
            if (filter.getVerified() != null) {
                appendParam(params, "verified", filter.getVerified().toString());
            }
            if (filter.getLimit() != null) {
                appendParam(params, "limit", filter.getLimit().toString());
            }
            if (filter.getOffset() != null) {
                appendParam(params, "offset", filter.getOffset().toString());
            }
        }
        String path = params.length() > 0 ? "/pools?" + params : "/pools";
        return getList(path, Pool.class);
    }

    // Swap Operations

    public CompletableFuture<Quote> getQuote(QuoteParams params) {
        Map<String, Object> body = new HashMap<>();
        body.put("token_in", params.getTokenIn());
        body.put("token_out", params.getTokenOut());
        body.put("amount_in", params.getAmountIn().toString());
        body.put("slippage", params.getSlippage() != null ? params.getSlippage() : 0.005);
        return post("/swap/quote", body, Quote.class);
    }

    public CompletableFuture<SwapResult> swap(SwapParams params) {
        long deadline = params.getDeadline() != null
            ? params.getDeadline()
            : Instant.now().getEpochSecond() + 1200;

        Map<String, Object> body = new HashMap<>();
        body.put("token_in", params.getTokenIn());
        body.put("token_out", params.getTokenOut());
        body.put("amount_in", params.getAmountIn().toString());
        body.put("min_amount_out", params.getMinAmountOut().toString());
        body.put("deadline", deadline);
        if (params.getRecipient() != null) {
            body.put("recipient", params.getRecipient());
        }
        return post("/swap", body, SwapResult.class);
    }

    // Liquidity Operations

    public CompletableFuture<LiquidityResult> addLiquidity(AddLiquidityParams params) {
        long deadline = params.getDeadline() != null
            ? params.getDeadline()
            : Instant.now().getEpochSecond() + 1200;

        Map<String, Object> body = new HashMap<>();
        body.put("token_a", params.getTokenA());
        body.put("token_b", params.getTokenB());
        body.put("amount_a", params.getAmountA().toString());
        body.put("amount_b", params.getAmountB().toString());
        body.put("deadline", deadline);
        if (params.getMinAmountA() != null) {
            body.put("min_amount_a", params.getMinAmountA().toString());
        }
        if (params.getMinAmountB() != null) {
            body.put("min_amount_b", params.getMinAmountB().toString());
        }
        return post("/liquidity/add", body, LiquidityResult.class);
    }

    public CompletableFuture<LiquidityResult> removeLiquidity(RemoveLiquidityParams params) {
        long deadline = params.getDeadline() != null
            ? params.getDeadline()
            : Instant.now().getEpochSecond() + 1200;

        Map<String, Object> body = new HashMap<>();
        body.put("pool", params.getPool());
        body.put("lp_amount", params.getLpAmount().toString());
        body.put("deadline", deadline);
        if (params.getMinAmountA() != null) {
            body.put("min_amount_a", params.getMinAmountA().toString());
        }
        if (params.getMinAmountB() != null) {
            body.put("min_amount_b", params.getMinAmountB().toString());
        }
        return post("/liquidity/remove", body, LiquidityResult.class);
    }

    public CompletableFuture<List<LPPosition>> getMyPositions() {
        return getList("/liquidity/positions", LPPosition.class);
    }

    // Analytics

    public CompletableFuture<List<OHLCV>> getPriceHistory(String pair, String interval, int limit) {
        return getList("/analytics/candles/" + pair + "?interval=" + interval + "&limit=" + limit, OHLCV.class);
    }

    public CompletableFuture<List<TradeHistory>> getTradeHistory(String pair, int limit) {
        return getList("/analytics/trades/" + pair + "?limit=" + limit, TradeHistory.class);
    }

    public CompletableFuture<VolumeStats> getVolumeStats() {
        return get("/analytics/volume", VolumeStats.class);
    }

    public CompletableFuture<TVLStats> getTVL() {
        return get("/analytics/tvl", TVLStats.class);
    }

    // Lifecycle

    public CompletableFuture<Boolean> healthCheck() {
        return get("/health", HealthResponse.class)
            .thenApply(r -> "healthy".equals(r.status))
            .exceptionally(e -> false);
    }

    @Override
    public void close() {
        closed = true;
        client.dispatcher().executorService().shutdown();
        client.connectionPool().evictAll();
    }

    public boolean isClosed() {
        return closed;
    }

    // Internal methods

    <T> CompletableFuture<T> get(String path, Class<T> responseClass) {
        return request("GET", path, null, responseClass);
    }

    <T> CompletableFuture<List<T>> getList(String path, Class<T> elementClass) {
        return request("GET", path, null, List.class)
            .thenApply(list -> {
                @SuppressWarnings("unchecked")
                List<T> result = (List<T>) list;
                return result;
            });
    }

    <T> CompletableFuture<T> post(String path, Object body, Class<T> responseClass) {
        return request("POST", path, body, responseClass);
    }

    <T> CompletableFuture<T> delete(String path, Class<T> responseClass) {
        return request("DELETE", path, null, responseClass);
    }

    private <T> CompletableFuture<T> request(String method, String path, Object body, Class<T> responseClass) {
        if (closed) {
            return CompletableFuture.failedFuture(new DexException("Client has been closed", "CLIENT_CLOSED", 0));
        }

        CompletableFuture<T> future = new CompletableFuture<>();

        Request.Builder requestBuilder = new Request.Builder()
            .url(config.getEndpoint() + path)
            .header("Content-Type", "application/json")
            .header("Authorization", "Bearer " + config.getApiKey())
            .header("X-SDK-Version", "java/0.1.0");

        RequestBody requestBody = body != null
            ? RequestBody.create(gson.toJson(body), JSON)
            : null;

        switch (method) {
            case "GET":
                requestBuilder.get();
                break;
            case "POST":
                requestBuilder.post(requestBody);
                break;
            case "DELETE":
                requestBuilder.delete(requestBody);
                break;
        }

        client.newCall(requestBuilder.build()).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                future.completeExceptionally(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try (ResponseBody responseBody = response.body()) {
                    String bodyStr = responseBody != null ? responseBody.string() : "";

                    if (!response.isSuccessful()) {
                        ErrorResponse error = gson.fromJson(bodyStr, ErrorResponse.class);
                        future.completeExceptionally(new DexException(
                            error != null && error.message != null ? error.message : "HTTP " + response.code(),
                            error != null ? error.code : null,
                            response.code()
                        ));
                        return;
                    }

                    T result = gson.fromJson(bodyStr, responseClass);
                    future.complete(result);
                }
            }
        });

        return future;
    }

    private void appendParam(StringBuilder params, String key, String value) {
        if (params.length() > 0) {
            params.append("&");
        }
        params.append(key).append("=").append(value);
    }

    private String urlEncode(String value) {
        try {
            return java.net.URLEncoder.encode(value, "UTF-8");
        } catch (Exception e) {
            return value;
        }
    }

    DexConfig getConfig() {
        return config;
    }

    OkHttpClient getClient() {
        return client;
    }

    Gson getGson() {
        return gson;
    }

    private static class HealthResponse {
        String status;
    }

    private static class ErrorResponse {
        String message;
        String code;
    }
}
