package io.synor.privacy;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import okhttp3.*;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Synor Privacy SDK client for Java.
 * Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
 */
public class PrivacyClient implements AutoCloseable {
    private static final String SDK_VERSION = "0.1.0";
    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

    private final PrivacyConfig config;
    private final OkHttpClient httpClient;
    private final Gson gson;
    private final AtomicBoolean closed = new AtomicBoolean(false);

    public PrivacyClient(PrivacyConfig config) {
        this.config = config;
        this.gson = new GsonBuilder().create();
        this.httpClient = new OkHttpClient.Builder()
                .connectTimeout(config.getTimeoutMs(), TimeUnit.MILLISECONDS)
                .readTimeout(config.getTimeoutMs(), TimeUnit.MILLISECONDS)
                .writeTimeout(config.getTimeoutMs(), TimeUnit.MILLISECONDS)
                .build();
    }

    // ==================== Confidential Transactions ====================

    public CompletableFuture<ConfidentialTransaction> createConfidentialTx(ConfidentialTxInput[] inputs, ConfidentialTxOutput[] outputs) {
        JsonObject body = new JsonObject();
        body.add("inputs", gson.toJsonTree(inputs));
        body.add("outputs", gson.toJsonTree(outputs));
        return post("/privacy/confidential/create", body, ConfidentialTransaction.class);
    }

    public CompletableFuture<Boolean> verifyConfidentialTx(ConfidentialTransaction tx) {
        JsonObject body = new JsonObject();
        body.add("transaction", gson.toJsonTree(tx));
        return post("/privacy/confidential/verify", body, JsonObject.class)
                .thenApply(response -> response.get("valid").getAsBoolean());
    }

    public CompletableFuture<Commitment> createCommitment(String value, String blindingFactor) {
        JsonObject body = new JsonObject();
        body.addProperty("value", value);
        body.addProperty("blinding_factor", blindingFactor);
        return post("/privacy/commitment/create", body, Commitment.class);
    }

    public CompletableFuture<Boolean> verifyCommitment(String commitment, String value, String blindingFactor) {
        JsonObject body = new JsonObject();
        body.addProperty("commitment", commitment);
        body.addProperty("value", value);
        body.addProperty("blinding_factor", blindingFactor);
        return post("/privacy/commitment/verify", body, JsonObject.class)
                .thenApply(response -> response.get("valid").getAsBoolean());
    }

    public CompletableFuture<RangeProof> createRangeProof(String value, String blindingFactor, long minValue, long maxValue) {
        JsonObject body = new JsonObject();
        body.addProperty("value", value);
        body.addProperty("blinding_factor", blindingFactor);
        body.addProperty("min_value", minValue);
        body.addProperty("max_value", maxValue);
        return post("/privacy/range-proof/create", body, RangeProof.class);
    }

    public CompletableFuture<Boolean> verifyRangeProof(RangeProof proof) {
        JsonObject body = new JsonObject();
        body.add("proof", gson.toJsonTree(proof));
        return post("/privacy/range-proof/verify", body, JsonObject.class)
                .thenApply(response -> response.get("valid").getAsBoolean());
    }

    // ==================== Ring Signatures ====================

    public CompletableFuture<RingSignature> createRingSignature(String message, String[] ring, int signerIndex, String privateKey) {
        JsonObject body = new JsonObject();
        body.addProperty("message", message);
        body.add("ring", gson.toJsonTree(ring));
        body.addProperty("signer_index", signerIndex);
        body.addProperty("private_key", privateKey);
        return post("/privacy/ring/sign", body, RingSignature.class);
    }

    public CompletableFuture<Boolean> verifyRingSignature(RingSignature signature, String message) {
        JsonObject body = new JsonObject();
        body.add("signature", gson.toJsonTree(signature));
        body.addProperty("message", message);
        return post("/privacy/ring/verify", body, JsonObject.class)
                .thenApply(response -> response.get("valid").getAsBoolean());
    }

    public CompletableFuture<String[]> generateDecoys(int count, String excludeKey) {
        String path = "/privacy/ring/decoys?count=" + count;
        if (excludeKey != null) {
            path += "&exclude=" + excludeKey;
        }
        return get(path, new TypeToken<String[]>(){}.getType());
    }

    public CompletableFuture<Boolean> checkKeyImage(String keyImage) {
        return get("/privacy/ring/key-image/" + keyImage, JsonObject.class)
                .thenApply(response -> response.get("spent").getAsBoolean());
    }

    // ==================== Stealth Addresses ====================

    public CompletableFuture<StealthKeyPair> generateStealthKeyPair() {
        return post("/privacy/stealth/generate", new JsonObject(), StealthKeyPair.class);
    }

    public CompletableFuture<StealthAddress> deriveStealthAddress(String spendPublicKey, String viewPublicKey) {
        JsonObject body = new JsonObject();
        body.addProperty("spend_public_key", spendPublicKey);
        body.addProperty("view_public_key", viewPublicKey);
        return post("/privacy/stealth/derive", body, StealthAddress.class);
    }

    public CompletableFuture<String> recoverStealthPrivateKey(String stealthAddress, String viewPrivateKey, String spendPrivateKey) {
        JsonObject body = new JsonObject();
        body.addProperty("stealth_address", stealthAddress);
        body.addProperty("view_private_key", viewPrivateKey);
        body.addProperty("spend_private_key", spendPrivateKey);
        return post("/privacy/stealth/recover", body, JsonObject.class)
                .thenApply(response -> response.get("private_key").getAsString());
    }

    public CompletableFuture<StealthOutput[]> scanOutputs(String viewPrivateKey, String spendPublicKey, long fromBlock, Long toBlock) {
        JsonObject body = new JsonObject();
        body.addProperty("view_private_key", viewPrivateKey);
        body.addProperty("spend_public_key", spendPublicKey);
        body.addProperty("from_block", fromBlock);
        if (toBlock != null) {
            body.addProperty("to_block", toBlock);
        }
        return post("/privacy/stealth/scan", body, StealthOutput[].class);
    }

    // ==================== Blinding ====================

    public CompletableFuture<String> generateBlindingFactor() {
        return post("/privacy/blinding/generate", new JsonObject(), JsonObject.class)
                .thenApply(response -> response.get("blinding_factor").getAsString());
    }

    public CompletableFuture<String> blindValue(String value, String blindingFactor) {
        JsonObject body = new JsonObject();
        body.addProperty("value", value);
        body.addProperty("blinding_factor", blindingFactor);
        return post("/privacy/blinding/blind", body, JsonObject.class)
                .thenApply(response -> response.get("blinded_value").getAsString());
    }

    public CompletableFuture<String> unblindValue(String blindedValue, String blindingFactor) {
        JsonObject body = new JsonObject();
        body.addProperty("blinded_value", blindedValue);
        body.addProperty("blinding_factor", blindingFactor);
        return post("/privacy/blinding/unblind", body, JsonObject.class)
                .thenApply(response -> response.get("value").getAsString());
    }

    // ==================== Lifecycle ====================

    public CompletableFuture<Boolean> healthCheck() {
        if (closed.get()) {
            return CompletableFuture.completedFuture(false);
        }
        return get("/health", JsonObject.class)
                .thenApply(response -> "healthy".equals(response.get("status").getAsString()))
                .exceptionally(e -> false);
    }

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

    public boolean isClosed() {
        return closed.get();
    }

    // ==================== HTTP Helpers ====================

    private <T> CompletableFuture<T> get(String path, Type type) {
        return executeRequest(path, null, type);
    }

    private <T> CompletableFuture<T> post(String path, JsonObject body, Type type) {
        return executeRequest(path, body, type);
    }

    private <T> CompletableFuture<T> executeRequest(String path, JsonObject body, Type type) {
        if (closed.get()) {
            CompletableFuture<T> future = new CompletableFuture<>();
            future.completeExceptionally(new PrivacyException("Client has been closed"));
            return future;
        }

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

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

        if (body != null) {
            requestBuilder.post(RequestBody.create(gson.toJson(body), JSON));
        } else {
            requestBuilder.get();
        }

        executeWithRetry(requestBuilder.build(), config.getRetries(), 0, future, type);
        return future;
    }

    private <T> void executeWithRetry(Request request, int remainingRetries, int attempt, CompletableFuture<T> future, Type type) {
        httpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                if (remainingRetries > 1) {
                    scheduleRetry(request, remainingRetries - 1, attempt + 1, future, type);
                } else {
                    future.completeExceptionally(new PrivacyException("Request failed: " + e.getMessage(), e));
                }
            }

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

                    if (response.isSuccessful()) {
                        T result = gson.fromJson(bodyString, type);
                        future.complete(result);
                    } else {
                        JsonObject errorObj = gson.fromJson(bodyString, JsonObject.class);
                        String message = errorObj.has("message") ? errorObj.get("message").getAsString() : "Unknown error";
                        String code = errorObj.has("code") ? errorObj.get("code").getAsString() : null;

                        if (remainingRetries > 1 && response.code() >= 500) {
                            scheduleRetry(request, remainingRetries - 1, attempt + 1, future, type);
                        } else {
                            future.completeExceptionally(new PrivacyException(message, response.code(), code));
                        }
                    }
                }
            }
        });
    }

    private <T> void scheduleRetry(Request request, int remainingRetries, int attempt, CompletableFuture<T> future, Type type) {
        long delay = (long) Math.pow(2, attempt) * 1000;
        CompletableFuture.delayedExecutor(delay, TimeUnit.MILLISECONDS)
                .execute(() -> executeWithRetry(request, remainingRetries, attempt, future, type));
    }
}
