package io.synor.examples;

import io.synor.zk.*;
import io.synor.zk.types.*;

import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
 * Synor ZK SDK Examples for Java
 *
 * Demonstrates Zero-Knowledge proof operations:
 * - Circuit compilation
 * - Proof generation and verification
 * - Groth16, PLONK, and STARK proving systems
 * - Recursive proofs
 * - On-chain verification
 */
public class ZkExample {

    public static void main(String[] args) throws Exception {
        // Initialize client
        ZkConfig config = ZkConfig.builder()
            .apiKey(System.getenv("SYNOR_API_KEY") != null ?
                    System.getenv("SYNOR_API_KEY") : "your-api-key")
            .endpoint("https://zk.synor.io/v1")
            .timeout(120000) // ZK ops can be slow
            .retries(3)
            .debug(false)
            .defaultProvingSystem(ProvingSystem.GROTH16)
            .build();

        SynorZk zk = new SynorZk(config);

        try {
            // Check service health
            boolean healthy = zk.healthCheck().get();
            System.out.println("Service healthy: " + healthy + "\n");

            // Run examples
            circuitExample(zk);
            proofExample(zk);
            provingSystemsExample(zk);
            recursiveProofExample(zk);
            onChainVerificationExample(zk);
            setupExample(zk);
        } finally {
            zk.close();
        }
    }

    static void circuitExample(SynorZk zk) throws Exception {
        System.out.println("=== Circuit Compilation ===");

        // Circom circuit example: prove knowledge of preimage
        String circomCircuit = """
            pragma circom 2.1.0;

            template HashPreimage() {
                signal input preimage;
                signal input hash;

                // Simplified hash computation (in reality, use proper hash)
                signal preimageSquared;
                preimageSquared <== preimage * preimage;

                // Constrain that hash matches
                hash === preimageSquared;
            }

            component main {public [hash]} = HashPreimage();
            """;

        // Compile circuit
        System.out.println("Compiling Circom circuit...");
        CompiledCircuit compiled = zk.circuits().compile(CompileRequest.builder()
            .code(circomCircuit)
            .format(CircuitFormat.CIRCOM)
            .name("hash_preimage")
            .provingSystem(ProvingSystem.GROTH16)
            .build()).get();

        System.out.println("Circuit compiled:");
        System.out.println("  Circuit ID: " + compiled.getCircuitId());
        System.out.println("  Constraints: " + compiled.getConstraintCount());
        System.out.println("  Public inputs: " + compiled.getPublicInputCount());
        System.out.println("  Private inputs: " + compiled.getPrivateInputCount());
        System.out.println("  Proving key size: " + compiled.getProvingKeySize() + " bytes");
        System.out.println("  Verification key size: " + compiled.getVerificationKeySize() + " bytes");

        // List circuits
        List<Circuit> circuits = zk.circuits().list().get();
        System.out.println("\nYour circuits: " + circuits.size());
        for (Circuit circuit : circuits) {
            System.out.println("  " + circuit.getName() + " (" + circuit.getCircuitId() + ")");
        }

        // Get circuit details
        CircuitDetails details = zk.circuits().get(compiled.getCircuitId()).get();
        System.out.println("\nCircuit details:");
        System.out.println("  Format: " + details.getFormat());
        System.out.println("  Proving system: " + details.getProvingSystem());
        System.out.println("  Created: " + details.getCreatedAt());

        // Download proving key
        byte[] provingKey = zk.circuits().getProvingKey(compiled.getCircuitId()).get();
        System.out.println("\nProving key downloaded: " + provingKey.length + " bytes");

        // Download verification key
        byte[] verificationKey = zk.circuits().getVerificationKey(compiled.getCircuitId()).get();
        System.out.println("Verification key downloaded: " + verificationKey.length + " bytes");

        System.out.println();
    }

    static void proofExample(SynorZk zk) throws Exception {
        System.out.println("=== Proof Generation ===");

        // First, compile a simple circuit
        String circuit = """
            pragma circom 2.1.0;

            template Multiplier() {
                signal input a;
                signal input b;
                signal output c;

                c <== a * b;
            }

            component main {public [c]} = Multiplier();
            """;

        CompiledCircuit compiled = zk.circuits().compile(CompileRequest.builder()
            .code(circuit)
            .format(CircuitFormat.CIRCOM)
            .name("multiplier")
            .provingSystem(ProvingSystem.GROTH16)
            .build()).get();

        // Generate proof
        System.out.println("Generating proof...");
        long startTime = System.currentTimeMillis();

        Map<String, String> inputs = new HashMap<>();
        inputs.put("a", "3");
        inputs.put("b", "7");

        Proof proof = zk.proofs().generate(GenerateProofRequest.builder()
            .circuitId(compiled.getCircuitId())
            .inputs(inputs)
            .build()).get();

        long proofTime = System.currentTimeMillis() - startTime;
        System.out.println("Proof generated in " + proofTime + "ms");
        System.out.println("  Proof ID: " + proof.getProofId());
        System.out.println("  Proof size: " + proof.getProof().length() + " bytes");
        System.out.println("  Public signals: " + proof.getPublicSignals());

        // Verify proof
        System.out.println("\nVerifying proof...");
        long verifyStart = System.currentTimeMillis();

        boolean isValid = zk.proofs().verify(VerifyRequest.builder()
            .circuitId(compiled.getCircuitId())
            .proof(proof.getProof())
            .publicSignals(proof.getPublicSignals())
            .build()).get();

        long verifyTime = System.currentTimeMillis() - verifyStart;
        System.out.println("Verification completed in " + verifyTime + "ms");
        System.out.println("  Valid: " + isValid);

        // Verify with wrong public signals (should fail)
        System.out.println("\nVerifying with wrong signals...");
        boolean invalidResult = zk.proofs().verify(VerifyRequest.builder()
            .circuitId(compiled.getCircuitId())
            .proof(proof.getProof())
            .publicSignals(List.of("42")) // Wrong answer
            .build()).get();
        System.out.println("  Valid: " + invalidResult + " (expected false)");

        // Get proof status
        ProofStatus status = zk.proofs().getStatus(proof.getProofId()).get();
        System.out.println("\nProof status:");
        System.out.println("  State: " + status.getState());
        System.out.println("  Verified: " + status.isVerified());
        System.out.println("  Created: " + status.getCreatedAt());

        // List proofs
        List<ProofInfo> proofs = zk.proofs().list(compiled.getCircuitId()).get();
        System.out.println("\nProofs for circuit: " + proofs.size());

        System.out.println();
    }

    static void provingSystemsExample(SynorZk zk) throws Exception {
        System.out.println("=== Proving Systems Comparison ===");

        // Simple circuit for comparison
        String circuit = """
            pragma circom 2.1.0;

            template Comparison() {
                signal input x;
                signal input y;
                signal output sum;

                sum <== x + y;
            }

            component main {public [sum]} = Comparison();
            """;

        ProvingSystem[] systems = {
            ProvingSystem.GROTH16,
            ProvingSystem.PLONK,
            ProvingSystem.STARK
        };

        System.out.println("Comparing proving systems:\n");

        for (ProvingSystem system : systems) {
            System.out.println(system + ":");

            // Compile for this system
            CompiledCircuit compiled = zk.circuits().compile(CompileRequest.builder()
                .code(circuit)
                .format(CircuitFormat.CIRCOM)
                .name("comparison_" + system.toString().toLowerCase())
                .provingSystem(system)
                .build()).get();

            // Generate proof
            long proofStart = System.currentTimeMillis();
            Map<String, String> inputs = new HashMap<>();
            inputs.put("x", "10");
            inputs.put("y", "20");

            Proof proof = zk.proofs().generate(GenerateProofRequest.builder()
                .circuitId(compiled.getCircuitId())
                .inputs(inputs)
                .build()).get();
            long proofTime = System.currentTimeMillis() - proofStart;

            // Verify proof
            long verifyStart = System.currentTimeMillis();
            zk.proofs().verify(VerifyRequest.builder()
                .circuitId(compiled.getCircuitId())
                .proof(proof.getProof())
                .publicSignals(proof.getPublicSignals())
                .build()).get();
            long verifyTime = System.currentTimeMillis() - verifyStart;

            System.out.println("  Setup: " + compiled.getSetupTime() + "ms");
            System.out.println("  Proof time: " + proofTime + "ms");
            System.out.println("  Verify time: " + verifyTime + "ms");
            System.out.println("  Proof size: " + proof.getProof().length() + " bytes");
            System.out.println("  Verification key: " + compiled.getVerificationKeySize() + " bytes");
            System.out.println();
        }

        System.out.println("Summary:");
        System.out.println("  Groth16: Smallest proofs, fast verification, trusted setup required");
        System.out.println("  PLONK: Universal setup, flexible, moderate proof size");
        System.out.println("  STARK: No trusted setup, largest proofs, quantum resistant");

        System.out.println();
    }

    static void recursiveProofExample(SynorZk zk) throws Exception {
        System.out.println("=== Recursive Proofs ===");

        // Inner circuit
        String innerCircuit = """
            pragma circom 2.1.0;

            template Inner() {
                signal input x;
                signal output y;
                y <== x * x;
            }

            component main {public [y]} = Inner();
            """;

        // Compile inner circuit
        CompiledCircuit inner = zk.circuits().compile(CompileRequest.builder()
            .code(innerCircuit)
            .format(CircuitFormat.CIRCOM)
            .name("inner_circuit")
            .provingSystem(ProvingSystem.GROTH16)
            .build()).get();

        // Generate multiple proofs to aggregate
        System.out.println("Generating proofs to aggregate...");
        List<ProofData> proofsToAggregate = new ArrayList<>();
        for (int i = 1; i <= 4; i++) {
            Map<String, String> inputs = new HashMap<>();
            inputs.put("x", String.valueOf(i));

            Proof proof = zk.proofs().generate(GenerateProofRequest.builder()
                .circuitId(inner.getCircuitId())
                .inputs(inputs)
                .build()).get();

            proofsToAggregate.add(ProofData.builder()
                .proof(proof.getProof())
                .publicSignals(proof.getPublicSignals())
                .build());
            System.out.println("  Proof " + i + ": y = " + proof.getPublicSignals().get(0));
        }

        // Aggregate proofs recursively
        System.out.println("\nAggregating proofs...");
        AggregatedProof aggregated = zk.proofs().aggregate(AggregateRequest.builder()
            .circuitId(inner.getCircuitId())
            .proofs(proofsToAggregate)
            .aggregationType("recursive")
            .build()).get();

        int originalSize = proofsToAggregate.stream()
            .mapToInt(p -> p.getProof().length())
            .sum();

        System.out.println("Aggregated proof:");
        System.out.println("  Proof ID: " + aggregated.getProofId());
        System.out.println("  Aggregated count: " + aggregated.getAggregatedCount());
        System.out.println("  Proof size: " + aggregated.getProof().length() + " bytes");
        System.out.println("  Size reduction: " +
            String.format("%.1f", (1 - (double) aggregated.getProof().length() / originalSize) * 100) + "%");

        // Verify aggregated proof
        List<List<String>> publicSignalsList = proofsToAggregate.stream()
            .map(ProofData::getPublicSignals)
            .toList();

        boolean isValid = zk.proofs().verifyAggregated(VerifyAggregatedRequest.builder()
            .circuitId(inner.getCircuitId())
            .proof(aggregated.getProof())
            .publicSignalsList(publicSignalsList)
            .build()).get();
        System.out.println("\nAggregated proof valid: " + isValid);

        // Batch verification
        System.out.println("\nBatch verification...");
        BatchVerifyResult batchResult = zk.proofs().batchVerify(BatchVerifyRequest.builder()
            .circuitId(inner.getCircuitId())
            .proofs(proofsToAggregate)
            .build()).get();
        System.out.println("  All valid: " + batchResult.isAllValid());
        System.out.println("  Results: " + batchResult.getResults());

        System.out.println();
    }

    static void onChainVerificationExample(SynorZk zk) throws Exception {
        System.out.println("=== On-Chain Verification ===");

        // Compile circuit
        String circuit = """
            pragma circom 2.1.0;

            template VoteCommitment() {
                signal input vote;
                signal input nullifier;
                signal input commitment;

                signal computed;
                computed <== vote * nullifier;
                commitment === computed;
            }

            component main {public [commitment]} = VoteCommitment();
            """;

        CompiledCircuit compiled = zk.circuits().compile(CompileRequest.builder()
            .code(circuit)
            .format(CircuitFormat.CIRCOM)
            .name("vote_commitment")
            .provingSystem(ProvingSystem.GROTH16)
            .build()).get();

        // Generate Solidity verifier
        System.out.println("Generating Solidity verifier...");
        GeneratedVerifier solidityVerifier = zk.contracts().generateVerifier(GenerateVerifierRequest.builder()
            .circuitId(compiled.getCircuitId())
            .language("solidity")
            .optimized(true)
            .build()).get();
        System.out.println("Solidity verifier generated: " + solidityVerifier.getCode().length() + " bytes");
        System.out.println("  Contract name: " + solidityVerifier.getContractName());
        System.out.println("  Gas estimate: " + solidityVerifier.getGasEstimate());

        // Generate proof
        Map<String, String> inputs = new HashMap<>();
        inputs.put("vote", "1");
        inputs.put("nullifier", "12345");
        inputs.put("commitment", "12345");

        Proof proof = zk.proofs().generate(GenerateProofRequest.builder()
            .circuitId(compiled.getCircuitId())
            .inputs(inputs)
            .build()).get();

        // Format proof for on-chain verification
        System.out.println("\nFormatting proof for on-chain...");
        FormattedProof onChainProof = zk.contracts().formatProof(FormatProofRequest.builder()
            .circuitId(compiled.getCircuitId())
            .proof(proof.getProof())
            .publicSignals(proof.getPublicSignals())
            .format("calldata")
            .build()).get();
        String calldataPreview = onChainProof.getCalldata().substring(0,
            Math.min(100, onChainProof.getCalldata().length()));
        System.out.println("  Calldata: " + calldataPreview + "...");
        System.out.println("  Estimated gas: " + onChainProof.getGasEstimate());

        // Deploy verifier contract
        System.out.println("\nDeploying verifier contract...");
        Deployment deployment = zk.contracts().deployVerifier(DeployRequest.builder()
            .circuitId(compiled.getCircuitId())
            .network("synor-testnet")
            .build()).get();
        System.out.println("  Contract address: " + deployment.getAddress());
        System.out.println("  TX hash: " + deployment.getTxHash());
        System.out.println("  Gas used: " + deployment.getGasUsed());

        // Verify on-chain
        System.out.println("\nVerifying on-chain...");
        OnChainVerifyResult onChainResult = zk.contracts().verifyOnChain(OnChainVerifyRequest.builder()
            .contractAddress(deployment.getAddress())
            .proof(proof.getProof())
            .publicSignals(proof.getPublicSignals())
            .network("synor-testnet")
            .build()).get();
        System.out.println("  TX hash: " + onChainResult.getTxHash());
        System.out.println("  Verified: " + onChainResult.isVerified());
        System.out.println("  Gas used: " + onChainResult.getGasUsed());

        // Generate verifier for other targets
        System.out.println("\nGenerating verifiers for other targets:");
        String[] targets = {"cairo", "noir", "ink"};
        for (String target : targets) {
            GeneratedVerifier verifier = zk.contracts().generateVerifier(GenerateVerifierRequest.builder()
                .circuitId(compiled.getCircuitId())
                .language(target)
                .build()).get();
            System.out.println("  " + target + ": " + verifier.getCode().length() + " bytes");
        }

        System.out.println();
    }

    static void setupExample(SynorZk zk) throws Exception {
        System.out.println("=== Trusted Setup ===");

        // Get available ceremonies
        List<Ceremony> ceremonies = zk.setup().listCeremonies().get();
        System.out.println("Active ceremonies: " + ceremonies.size());
        for (Ceremony ceremony : ceremonies) {
            System.out.println("  " + ceremony.getName() + ":");
            System.out.println("    Status: " + ceremony.getStatus());
            System.out.println("    Participants: " + ceremony.getParticipantCount());
            System.out.println("    Current round: " + ceremony.getCurrentRound());
        }

        // Create a new circuit setup
        String circuit = """
            pragma circom 2.1.0;

            template NewCircuit() {
                signal input a;
                signal output b;
                b <== a + 1;
            }

            component main {public [b]} = NewCircuit();
            """;

        System.out.println("\nInitializing setup for new circuit...");
        SetupResult setup = zk.setup().initialize(SetupInitRequest.builder()
            .circuit(circuit)
            .format(CircuitFormat.CIRCOM)
            .name("new_circuit_setup")
            .provingSystem(ProvingSystem.GROTH16)
            .ceremonyType("powers_of_tau")
            .build()).get();
        System.out.println("Setup initialized:");
        System.out.println("  Ceremony ID: " + setup.getCeremonyId());
        System.out.println("  Powers of Tau required: " + setup.getPowersRequired());
        System.out.println("  Current phase: " + setup.getPhase());

        // Contribute to ceremony
        System.out.println("\nContributing to ceremony...");
        Contribution contribution = zk.setup().contribute(ContributeRequest.builder()
            .ceremonyId(setup.getCeremonyId())
            .entropy(bytesToHex("random-entropy-from-user".getBytes()))
            .build()).get();
        System.out.println("Contribution submitted:");
        System.out.println("  Participant: " + contribution.getParticipantId());
        System.out.println("  Contribution hash: " + contribution.getHash());
        System.out.println("  Verification status: " + contribution.isVerified());

        // Get ceremony status
        CeremonyStatus status = zk.setup().getStatus(setup.getCeremonyId()).get();
        System.out.println("\nCeremony status:");
        System.out.println("  Phase: " + status.getPhase());
        System.out.println("  Total contributions: " + status.getTotalContributions());
        System.out.println("  Verified contributions: " + status.getVerifiedContributions());
        System.out.println("  Ready for finalization: " + status.isReadyForFinalization());

        // Finalize setup (when enough contributions)
        if (status.isReadyForFinalization()) {
            System.out.println("\nFinalizing setup...");
            FinalizedSetup finalized = zk.setup().finalize(setup.getCeremonyId()).get();
            System.out.println("Setup finalized:");
            System.out.println("  Proving key hash: " + finalized.getProvingKeyHash());
            System.out.println("  Verification key hash: " + finalized.getVerificationKeyHash());
            System.out.println("  Contribution transcript: " + finalized.getTranscriptCid());
        }

        // Download ceremony transcript
        byte[] transcript = zk.setup().getTranscript(setup.getCeremonyId()).get();
        System.out.println("\nCeremony transcript: " + transcript.length + " bytes");

        System.out.println();
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
