package io.synor.crypto;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Synor Crypto SDK for Java
 *
 * Quantum-resistant cryptographic primitives for the Synor blockchain.
 *
 * <pre>{@code
 * CryptoConfig config = new CryptoConfig("your-api-key");
 * SynorCrypto crypto = new SynorCrypto(config);
 *
 * // Generate a mnemonic
 * Mnemonic mnemonic = crypto.mnemonic().generate(24).join();
 * System.out.println("Backup words: " + mnemonic.getPhrase());
 *
 * // Create keypair from mnemonic
 * HybridKeypair keypair = crypto.keypairs().fromMnemonic(mnemonic.getPhrase(), "").join();
 * String address = keypair.getAddress(Network.MAINNET);
 *
 * // Sign a message
 * HybridSignature signature = crypto.signing().sign(keypair, "Hello!".getBytes()).join();
 * }</pre>
 */
public class SynorCrypto {
    private final CryptoConfig config;
    private final HttpClient httpClient;
    private final Gson gson;
    private final AtomicBoolean closed = new AtomicBoolean(false);

    private final MnemonicClient mnemonicClient;
    private final KeypairClient keypairClient;
    private final SigningClient signingClient;
    private final FalconClient falconClient;
    private final SphincsClient sphincsClient;
    private final KdfClient kdfClient;
    private final HashClient hashClient;

    public SynorCrypto(CryptoConfig config) {
        this.config = config;
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofMillis(config.getTimeout()))
                .build();
        this.gson = new GsonBuilder()
                .setFieldNamingPolicy(com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                .create();

        this.mnemonicClient = new MnemonicClient(this);
        this.keypairClient = new KeypairClient(this);
        this.signingClient = new SigningClient(this);
        this.falconClient = new FalconClient(this);
        this.sphincsClient = new SphincsClient(this);
        this.kdfClient = new KdfClient(this);
        this.hashClient = new HashClient(this);
    }

    public Network getDefaultNetwork() {
        return config.getDefaultNetwork();
    }

    public MnemonicClient mnemonic() { return mnemonicClient; }
    public KeypairClient keypairs() { return keypairClient; }
    public SigningClient signing() { return signingClient; }
    public FalconClient falcon() { return falconClient; }
    public SphincsClient sphincs() { return sphincsClient; }
    public KdfClient kdf() { return kdfClient; }
    public HashClient hash() { return hashClient; }

    public CompletableFuture<Boolean> healthCheck() {
        return get("/health", new TypeToken<Map<String, Object>>() {})
                .thenApply(result -> "healthy".equals(result.get("status")))
                .exceptionally(e -> false);
    }

    public CompletableFuture<Map<String, Object>> getInfo() {
        return get("/info", new TypeToken<Map<String, Object>>() {});
    }

    public void close() {
        closed.set(true);
    }

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

    <T> CompletableFuture<T> get(String path, TypeToken<T> type) {
        checkClosed();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(config.getEndpoint() + path))
                .header("Authorization", "Bearer " + config.getApiKey())
                .header("X-SDK-Version", "java/0.1.0")
                .GET()
                .build();

        return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(response -> handleResponse(response, type));
    }

    <T> CompletableFuture<T> post(String path, Object body, TypeToken<T> type) {
        checkClosed();
        String jsonBody = body != null ? gson.toJson(body) : "{}";

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(config.getEndpoint() + path))
                .header("Authorization", "Bearer " + config.getApiKey())
                .header("Content-Type", "application/json")
                .header("X-SDK-Version", "java/0.1.0")
                .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
                .build();

        return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(response -> handleResponse(response, type));
    }

    private <T> T handleResponse(HttpResponse<String> response, TypeToken<T> type) {
        if (response.statusCode() >= 400) {
            Map<String, Object> error = gson.fromJson(response.body(), new TypeToken<Map<String, Object>>() {}.getType());
            String message = error != null && error.get("message") != null
                    ? (String) error.get("message")
                    : "HTTP " + response.statusCode();
            String code = error != null ? (String) error.get("code") : null;
            throw new CryptoException(message, code);
        }
        return gson.fromJson(response.body(), type.getType());
    }

    private void checkClosed() {
        if (closed.get()) {
            throw new CryptoException("Client has been closed", "CLIENT_CLOSED");
        }
    }

    // ========================================================================
    // Sub-Clients
    // ========================================================================

    public static class MnemonicClient {
        private final SynorCrypto crypto;
        MnemonicClient(SynorCrypto crypto) { this.crypto = crypto; }

        public CompletableFuture<Mnemonic> generate(int wordCount) {
            Map<String, Object> body = Map.of("word_count", wordCount);
            return crypto.post("/mnemonic/generate", body, new TypeToken<Mnemonic>() {});
        }

        public CompletableFuture<Mnemonic> fromPhrase(String phrase) {
            Map<String, Object> body = Map.of("phrase", phrase);
            return crypto.post("/mnemonic/from-phrase", body, new TypeToken<Mnemonic>() {});
        }

        public CompletableFuture<MnemonicValidation> validate(String phrase) {
            Map<String, Object> body = Map.of("phrase", phrase);
            return crypto.post("/mnemonic/validate", body, new TypeToken<MnemonicValidation>() {});
        }

        public CompletableFuture<byte[]> toSeed(String phrase, String passphrase) {
            Map<String, Object> body = Map.of("phrase", phrase, "passphrase", passphrase);
            return crypto.post("/mnemonic/to-seed", body, new TypeToken<Map<String, String>>() {})
                    .thenApply(result -> Base64.getDecoder().decode(result.get("seed")));
        }

        public CompletableFuture<List<String>> suggestWords(String partial, int limit) {
            Map<String, Object> body = Map.of("partial", partial, "limit", limit);
            return crypto.post("/mnemonic/suggest", body, new TypeToken<Map<String, List<String>>>() {})
                    .thenApply(result -> result.get("suggestions"));
        }
    }

    public static class KeypairClient {
        private final SynorCrypto crypto;
        KeypairClient(SynorCrypto crypto) { this.crypto = crypto; }

        public CompletableFuture<HybridKeypair> generate() {
            return crypto.post("/keypair/generate", null, new TypeToken<HybridKeypair>() {});
        }

        public CompletableFuture<HybridKeypair> fromMnemonic(String phrase, String passphrase) {
            Map<String, Object> body = Map.of("phrase", phrase, "passphrase", passphrase);
            return crypto.post("/keypair/from-mnemonic", body, new TypeToken<HybridKeypair>() {});
        }

        public CompletableFuture<HybridKeypair> fromSeed(byte[] seed) {
            Map<String, Object> body = Map.of("seed", Base64.getEncoder().encodeToString(seed));
            return crypto.post("/keypair/from-seed", body, new TypeToken<HybridKeypair>() {});
        }

        public CompletableFuture<Address> getAddress(HybridPublicKey publicKey, Network network) {
            Map<String, Object> body = Map.of(
                    "public_key", publicKey.toMap(),
                    "network", network.getValue()
            );
            return crypto.post("/keypair/address", body, new TypeToken<Address>() {});
        }
    }

    public static class SigningClient {
        private final SynorCrypto crypto;
        SigningClient(SynorCrypto crypto) { this.crypto = crypto; }

        public CompletableFuture<HybridSignature> sign(HybridKeypair keypair, byte[] message) {
            Map<String, Object> body = Map.of(
                    "secret_key", keypair.getSecretKey().toMap(),
                    "message", Base64.getEncoder().encodeToString(message)
            );
            return crypto.post("/sign/hybrid", body, new TypeToken<HybridSignature>() {});
        }

        public CompletableFuture<Boolean> verify(HybridPublicKey publicKey, byte[] message, HybridSignature signature) {
            Map<String, Object> body = Map.of(
                    "public_key", publicKey.toMap(),
                    "message", Base64.getEncoder().encodeToString(message),
                    "signature", signature.toMap()
            );
            return crypto.post("/sign/verify", body, new TypeToken<Map<String, Boolean>>() {})
                    .thenApply(result -> result.get("valid"));
        }

        public CompletableFuture<byte[]> signEd25519(byte[] secretKey, byte[] message) {
            Map<String, Object> body = Map.of(
                    "secret_key", Base64.getEncoder().encodeToString(secretKey),
                    "message", Base64.getEncoder().encodeToString(message)
            );
            return crypto.post("/sign/ed25519", body, new TypeToken<Map<String, String>>() {})
                    .thenApply(result -> Base64.getDecoder().decode(result.get("signature")));
        }
    }

    public static class FalconClient {
        private final SynorCrypto crypto;
        FalconClient(SynorCrypto crypto) { this.crypto = crypto; }

        public CompletableFuture<FalconKeypair> generate(FalconVariant variant) {
            Map<String, Object> body = Map.of("variant", variant.getValue());
            return crypto.post("/falcon/generate", body, new TypeToken<FalconKeypair>() {});
        }

        public CompletableFuture<FalconSignature> sign(FalconKeypair keypair, byte[] message) {
            Map<String, Object> body = Map.of(
                    "variant", keypair.getVariant().getValue(),
                    "secret_key", Base64.getEncoder().encodeToString(keypair.getSecretKey().getBytes()),
                    "message", Base64.getEncoder().encodeToString(message)
            );
            return crypto.post("/falcon/sign", body, new TypeToken<FalconSignature>() {});
        }

        public CompletableFuture<Boolean> verify(byte[] publicKey, byte[] message, FalconSignature signature) {
            Map<String, Object> body = Map.of(
                    "variant", signature.getVariant().getValue(),
                    "public_key", Base64.getEncoder().encodeToString(publicKey),
                    "message", Base64.getEncoder().encodeToString(message),
                    "signature", Base64.getEncoder().encodeToString(signature.getBytes())
            );
            return crypto.post("/falcon/verify", body, new TypeToken<Map<String, Boolean>>() {})
                    .thenApply(result -> result.get("valid"));
        }
    }

    public static class SphincsClient {
        private final SynorCrypto crypto;
        SphincsClient(SynorCrypto crypto) { this.crypto = crypto; }

        public CompletableFuture<SphincsKeypair> generate(SphincsVariant variant) {
            Map<String, Object> body = Map.of("variant", variant.getValue());
            return crypto.post("/sphincs/generate", body, new TypeToken<SphincsKeypair>() {});
        }

        public CompletableFuture<SphincsSignature> sign(SphincsKeypair keypair, byte[] message) {
            Map<String, Object> body = Map.of(
                    "variant", keypair.getVariant().getValue(),
                    "secret_key", Base64.getEncoder().encodeToString(keypair.getSecretKey().getBytes()),
                    "message", Base64.getEncoder().encodeToString(message)
            );
            return crypto.post("/sphincs/sign", body, new TypeToken<SphincsSignature>() {});
        }

        public CompletableFuture<Boolean> verify(byte[] publicKey, byte[] message, SphincsSignature signature) {
            Map<String, Object> body = Map.of(
                    "variant", signature.getVariant().getValue(),
                    "public_key", Base64.getEncoder().encodeToString(publicKey),
                    "message", Base64.getEncoder().encodeToString(message),
                    "signature", Base64.getEncoder().encodeToString(signature.getBytes())
            );
            return crypto.post("/sphincs/verify", body, new TypeToken<Map<String, Boolean>>() {})
                    .thenApply(result -> result.get("valid"));
        }
    }

    public static class KdfClient {
        private final SynorCrypto crypto;
        KdfClient(SynorCrypto crypto) { this.crypto = crypto; }

        public CompletableFuture<byte[]> deriveKey(byte[] seed, DerivationConfig config) {
            Map<String, Object> body = new HashMap<>();
            body.put("seed", Base64.getEncoder().encodeToString(seed));
            body.put("output_length", config.getOutputLength());
            if (config.getSalt() != null) {
                body.put("salt", Base64.getEncoder().encodeToString(config.getSalt()));
            }
            if (config.getInfo() != null) {
                body.put("info", Base64.getEncoder().encodeToString(config.getInfo()));
            }
            return crypto.post("/kdf/hkdf", body, new TypeToken<Map<String, String>>() {})
                    .thenApply(result -> Base64.getDecoder().decode(result.get("key")));
        }

        public CompletableFuture<byte[]> deriveFromPassword(byte[] password, PasswordDerivationConfig config) {
            Map<String, Object> body = Map.of(
                    "password", Base64.getEncoder().encodeToString(password),
                    "salt", Base64.getEncoder().encodeToString(config.getSalt()),
                    "iterations", config.getIterations(),
                    "output_length", config.getOutputLength()
            );
            return crypto.post("/kdf/pbkdf2", body, new TypeToken<Map<String, String>>() {})
                    .thenApply(result -> Base64.getDecoder().decode(result.get("key")));
        }
    }

    public static class HashClient {
        private final SynorCrypto crypto;
        HashClient(SynorCrypto crypto) { this.crypto = crypto; }

        public CompletableFuture<Hash256> sha3_256(byte[] data) {
            Map<String, Object> body = Map.of("data", Base64.getEncoder().encodeToString(data));
            return crypto.post("/hash/sha3-256", body, new TypeToken<Hash256>() {});
        }

        public CompletableFuture<Hash256> blake3(byte[] data) {
            Map<String, Object> body = Map.of("data", Base64.getEncoder().encodeToString(data));
            return crypto.post("/hash/blake3", body, new TypeToken<Hash256>() {});
        }

        public CompletableFuture<Hash256> keccak256(byte[] data) {
            Map<String, Object> body = Map.of("data", Base64.getEncoder().encodeToString(data));
            return crypto.post("/hash/keccak256", body, new TypeToken<Hash256>() {});
        }
    }
}
