//! Synor ZK SDK Examples for Rust
//!
//! Demonstrates Zero-Knowledge proof operations:
//! - Circuit compilation
//! - Proof generation and verification
//! - Groth16, PLONK, and STARK proving systems
//! - Recursive proofs
//! - On-chain verification

use std::collections::HashMap;
use std::env;
use std::time::Instant;
use synor_zk::{
    AggregateRequest, BatchVerifyRequest, CircuitFormat, CompileRequest, ContributeRequest,
    DeployRequest, FormatProofRequest, GenerateProofRequest, GenerateVerifierRequest,
    OnChainVerifyRequest, ProofData, ProvingSystem, SetupInitRequest, SynorZk, VerifyAggregatedRequest,
    VerifyRequest, ZkConfig,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize client
    let config = ZkConfig {
        api_key: env::var("SYNOR_API_KEY").unwrap_or_else(|_| "your-api-key".to_string()),
        endpoint: "https://zk.synor.io/v1".to_string(),
        timeout: 120000, // ZK ops can be slow
        retries: 3,
        debug: false,
        default_proving_system: ProvingSystem::Groth16,
    };

    let zk = SynorZk::new(config)?;

    // Check service health
    let healthy = zk.health_check().await?;
    println!("Service healthy: {}\n", healthy);

    // Run examples
    circuit_example(&zk).await?;
    proof_example(&zk).await?;
    proving_systems_example(&zk).await?;
    recursive_proof_example(&zk).await?;
    on_chain_verification_example(&zk).await?;
    setup_example(&zk).await?;

    zk.close().await?;
    Ok(())
}

async fn circuit_example(zk: &SynorZk) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Circuit Compilation ===");

    // Circom circuit example: prove knowledge of preimage
    let circom_circuit = r#"
    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
    println!("Compiling Circom circuit...");
    let compiled = zk
        .circuits
        .compile(CompileRequest {
            code: circom_circuit.to_string(),
            format: CircuitFormat::Circom,
            name: "hash_preimage".to_string(),
            proving_system: ProvingSystem::Groth16,
        })
        .await?;

    println!("Circuit compiled:");
    println!("  Circuit ID: {}", compiled.circuit_id);
    println!("  Constraints: {}", compiled.constraint_count);
    println!("  Public inputs: {}", compiled.public_input_count);
    println!("  Private inputs: {}", compiled.private_input_count);
    println!("  Proving key size: {} bytes", compiled.proving_key_size);
    println!("  Verification key size: {} bytes", compiled.verification_key_size);

    // List circuits
    let circuits = zk.circuits.list().await?;
    println!("\nYour circuits: {}", circuits.len());
    for circuit in &circuits {
        println!("  {} ({})", circuit.name, circuit.circuit_id);
    }

    // Get circuit details
    let details = zk.circuits.get(&compiled.circuit_id).await?;
    println!("\nCircuit details:");
    println!("  Format: {:?}", details.format);
    println!("  Proving system: {:?}", details.proving_system);
    println!("  Created: {}", details.created_at);

    // Download proving key
    let proving_key = zk.circuits.get_proving_key(&compiled.circuit_id).await?;
    println!("\nProving key downloaded: {} bytes", proving_key.len());

    // Download verification key
    let verification_key = zk.circuits.get_verification_key(&compiled.circuit_id).await?;
    println!("Verification key downloaded: {} bytes", verification_key.len());

    println!();
    Ok(())
}

async fn proof_example(zk: &SynorZk) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Proof Generation ===");

    // First, compile a simple circuit
    let circuit = r#"
    pragma circom 2.1.0;

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

        c <== a * b;
    }

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

    let compiled = zk
        .circuits
        .compile(CompileRequest {
            code: circuit.to_string(),
            format: CircuitFormat::Circom,
            name: "multiplier".to_string(),
            proving_system: ProvingSystem::Groth16,
        })
        .await?;

    // Generate proof
    println!("Generating proof...");
    let start_time = Instant::now();

    let mut inputs = HashMap::new();
    inputs.insert("a".to_string(), "3".to_string());
    inputs.insert("b".to_string(), "7".to_string());

    let proof = zk
        .proofs
        .generate(GenerateProofRequest {
            circuit_id: compiled.circuit_id.clone(),
            inputs,
        })
        .await?;

    let proof_time = start_time.elapsed();
    println!("Proof generated in {:?}", proof_time);
    println!("  Proof ID: {}", proof.proof_id);
    println!("  Proof size: {} bytes", proof.proof.len());
    println!("  Public signals: {:?}", proof.public_signals);

    // Verify proof
    println!("\nVerifying proof...");
    let verify_start = Instant::now();

    let is_valid = zk
        .proofs
        .verify(VerifyRequest {
            circuit_id: compiled.circuit_id.clone(),
            proof: proof.proof.clone(),
            public_signals: proof.public_signals.clone(),
        })
        .await?;

    let verify_time = verify_start.elapsed();
    println!("Verification completed in {:?}", verify_time);
    println!("  Valid: {}", is_valid);

    // Verify with wrong public signals (should fail)
    println!("\nVerifying with wrong signals...");
    let invalid_result = zk
        .proofs
        .verify(VerifyRequest {
            circuit_id: compiled.circuit_id.clone(),
            proof: proof.proof.clone(),
            public_signals: vec!["42".to_string()], // Wrong answer
        })
        .await?;
    println!("  Valid: {} (expected false)", invalid_result);

    // Get proof status
    let status = zk.proofs.get_status(&proof.proof_id).await?;
    println!("\nProof status:");
    println!("  State: {}", status.state);
    println!("  Verified: {}", status.verified);
    println!("  Created: {}", status.created_at);

    // List proofs
    let proofs = zk.proofs.list(&compiled.circuit_id).await?;
    println!("\nProofs for circuit: {}", proofs.len());

    println!();
    Ok(())
}

async fn proving_systems_example(zk: &SynorZk) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Proving Systems Comparison ===");

    // Simple circuit for comparison
    let circuit = r#"
    pragma circom 2.1.0;

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

        sum <== x + y;
    }

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

    let systems = [
        (ProvingSystem::Groth16, "GROTH16"),
        (ProvingSystem::PLONK, "PLONK"),
        (ProvingSystem::STARK, "STARK"),
    ];

    println!("Comparing proving systems:\n");

    for (system, name) in systems {
        println!("{}:", name);

        // Compile for this system
        let compiled = zk
            .circuits
            .compile(CompileRequest {
                code: circuit.to_string(),
                format: CircuitFormat::Circom,
                name: format!("comparison_{}", name.to_lowercase()),
                proving_system: system,
            })
            .await?;

        // Generate proof
        let proof_start = Instant::now();
        let mut inputs = HashMap::new();
        inputs.insert("x".to_string(), "10".to_string());
        inputs.insert("y".to_string(), "20".to_string());

        let proof = zk
            .proofs
            .generate(GenerateProofRequest {
                circuit_id: compiled.circuit_id.clone(),
                inputs,
            })
            .await?;
        let proof_time = proof_start.elapsed();

        // Verify proof
        let verify_start = Instant::now();
        zk.proofs
            .verify(VerifyRequest {
                circuit_id: compiled.circuit_id.clone(),
                proof: proof.proof.clone(),
                public_signals: proof.public_signals.clone(),
            })
            .await?;
        let verify_time = verify_start.elapsed();

        println!("  Setup: {}ms", compiled.setup_time);
        println!("  Proof time: {:?}", proof_time);
        println!("  Verify time: {:?}", verify_time);
        println!("  Proof size: {} bytes", proof.proof.len());
        println!("  Verification key: {} bytes", compiled.verification_key_size);
        println!();
    }

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

    println!();
    Ok(())
}

async fn recursive_proof_example(zk: &SynorZk) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Recursive Proofs ===");

    // Inner circuit
    let inner_circuit = r#"
    pragma circom 2.1.0;

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

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

    // Compile inner circuit
    let inner = zk
        .circuits
        .compile(CompileRequest {
            code: inner_circuit.to_string(),
            format: CircuitFormat::Circom,
            name: "inner_circuit".to_string(),
            proving_system: ProvingSystem::Groth16,
        })
        .await?;

    // Generate multiple proofs to aggregate
    println!("Generating proofs to aggregate...");
    let mut proofs_to_aggregate = Vec::new();
    for i in 1..=4 {
        let mut inputs = HashMap::new();
        inputs.insert("x".to_string(), i.to_string());

        let proof = zk
            .proofs
            .generate(GenerateProofRequest {
                circuit_id: inner.circuit_id.clone(),
                inputs,
            })
            .await?;

        proofs_to_aggregate.push(ProofData {
            proof: proof.proof,
            public_signals: proof.public_signals.clone(),
        });
        println!("  Proof {}: y = {}", i, proof.public_signals[0]);
    }

    // Aggregate proofs recursively
    println!("\nAggregating proofs...");
    let aggregated = zk
        .proofs
        .aggregate(AggregateRequest {
            circuit_id: inner.circuit_id.clone(),
            proofs: proofs_to_aggregate.clone(),
            aggregation_type: "recursive".to_string(),
        })
        .await?;

    let original_size: usize = proofs_to_aggregate.iter().map(|p| p.proof.len()).sum();

    println!("Aggregated proof:");
    println!("  Proof ID: {}", aggregated.proof_id);
    println!("  Aggregated count: {}", aggregated.aggregated_count);
    println!("  Proof size: {} bytes", aggregated.proof.len());
    println!(
        "  Size reduction: {:.1}%",
        (1.0 - aggregated.proof.len() as f64 / original_size as f64) * 100.0
    );

    // Verify aggregated proof
    let public_signals_list: Vec<Vec<String>> = proofs_to_aggregate
        .iter()
        .map(|p| p.public_signals.clone())
        .collect();

    let is_valid = zk
        .proofs
        .verify_aggregated(VerifyAggregatedRequest {
            circuit_id: inner.circuit_id.clone(),
            proof: aggregated.proof,
            public_signals_list,
        })
        .await?;
    println!("\nAggregated proof valid: {}", is_valid);

    // Batch verification (verify multiple proofs in one operation)
    println!("\nBatch verification...");
    let batch_result = zk
        .proofs
        .batch_verify(BatchVerifyRequest {
            circuit_id: inner.circuit_id.clone(),
            proofs: proofs_to_aggregate,
        })
        .await?;
    println!("  All valid: {}", batch_result.all_valid);
    println!("  Results: {:?}", batch_result.results);

    println!();
    Ok(())
}

async fn on_chain_verification_example(zk: &SynorZk) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== On-Chain Verification ===");

    // Compile circuit
    let circuit = r#"
    pragma circom 2.1.0;

    template VoteCommitment() {
        signal input vote;        // Private: actual vote
        signal input nullifier;   // Private: unique identifier
        signal input commitment;  // Public: commitment to verify

        // Simplified commitment (in practice, use Poseidon hash)
        signal computed;
        computed <== vote * nullifier;
        commitment === computed;
    }

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

    let compiled = zk
        .circuits
        .compile(CompileRequest {
            code: circuit.to_string(),
            format: CircuitFormat::Circom,
            name: "vote_commitment".to_string(),
            proving_system: ProvingSystem::Groth16,
        })
        .await?;

    // Generate Solidity verifier
    println!("Generating Solidity verifier...");
    let solidity_verifier = zk
        .contracts
        .generate_verifier(GenerateVerifierRequest {
            circuit_id: compiled.circuit_id.clone(),
            language: "solidity".to_string(),
            optimized: true,
        })
        .await?;
    println!(
        "Solidity verifier generated: {} bytes",
        solidity_verifier.code.len()
    );
    println!("  Contract name: {}", solidity_verifier.contract_name);
    println!("  Gas estimate: {}", solidity_verifier.gas_estimate);

    // Generate proof
    let mut inputs = HashMap::new();
    inputs.insert("vote".to_string(), "1".to_string()); // Vote YES (1) or NO (0)
    inputs.insert("nullifier".to_string(), "12345".to_string());
    inputs.insert("commitment".to_string(), "12345".to_string()); // 1 * 12345

    let proof = zk
        .proofs
        .generate(GenerateProofRequest {
            circuit_id: compiled.circuit_id.clone(),
            inputs,
        })
        .await?;

    // Format proof for on-chain verification
    println!("\nFormatting proof for on-chain...");
    let on_chain_proof = zk
        .contracts
        .format_proof(FormatProofRequest {
            circuit_id: compiled.circuit_id.clone(),
            proof: proof.proof.clone(),
            public_signals: proof.public_signals.clone(),
            format: "calldata".to_string(),
        })
        .await?;

    let calldata_preview = if on_chain_proof.calldata.len() > 100 {
        &on_chain_proof.calldata[..100]
    } else {
        &on_chain_proof.calldata
    };
    println!("  Calldata: {}...", calldata_preview);
    println!("  Estimated gas: {}", on_chain_proof.gas_estimate);

    // Deploy verifier contract (simulation)
    println!("\nDeploying verifier contract...");
    let deployment = zk
        .contracts
        .deploy_verifier(DeployRequest {
            circuit_id: compiled.circuit_id.clone(),
            network: "synor-testnet".to_string(),
        })
        .await?;
    println!("  Contract address: {}", deployment.address);
    println!("  TX hash: {}", deployment.tx_hash);
    println!("  Gas used: {}", deployment.gas_used);

    // Verify on-chain
    println!("\nVerifying on-chain...");
    let on_chain_result = zk
        .contracts
        .verify_on_chain(OnChainVerifyRequest {
            contract_address: deployment.address,
            proof: proof.proof,
            public_signals: proof.public_signals,
            network: "synor-testnet".to_string(),
        })
        .await?;
    println!("  TX hash: {}", on_chain_result.tx_hash);
    println!("  Verified: {}", on_chain_result.verified);
    println!("  Gas used: {}", on_chain_result.gas_used);

    // Generate verifier for other targets
    println!("\nGenerating verifiers for other targets:");
    let targets = ["cairo", "noir", "ink"];
    for target in targets {
        let verifier = zk
            .contracts
            .generate_verifier(GenerateVerifierRequest {
                circuit_id: compiled.circuit_id.clone(),
                language: target.to_string(),
                optimized: false,
            })
            .await?;
        println!("  {}: {} bytes", target, verifier.code.len());
    }

    println!();
    Ok(())
}

async fn setup_example(zk: &SynorZk) -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Trusted Setup ===");

    // Get available ceremonies
    let ceremonies = zk.setup.list_ceremonies().await?;
    println!("Active ceremonies: {}", ceremonies.len());
    for ceremony in &ceremonies {
        println!("  {}:", ceremony.name);
        println!("    Status: {}", ceremony.status);
        println!("    Participants: {}", ceremony.participant_count);
        println!("    Current round: {}", ceremony.current_round);
    }

    // Create a new circuit setup
    let circuit = r#"
    pragma circom 2.1.0;

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

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

    println!("\nInitializing setup for new circuit...");
    let setup = zk
        .setup
        .initialize(SetupInitRequest {
            circuit: circuit.to_string(),
            format: CircuitFormat::Circom,
            name: "new_circuit_setup".to_string(),
            proving_system: ProvingSystem::Groth16,
            ceremony_type: "powers_of_tau".to_string(), // or 'phase2'
        })
        .await?;
    println!("Setup initialized:");
    println!("  Ceremony ID: {}", setup.ceremony_id);
    println!("  Powers of Tau required: {}", setup.powers_required);
    println!("  Current phase: {}", setup.phase);

    // Contribute to ceremony (in practice, generates random entropy)
    println!("\nContributing to ceremony...");
    let contribution = zk
        .setup
        .contribute(ContributeRequest {
            ceremony_id: setup.ceremony_id.clone(),
            entropy: hex::encode(b"random-entropy-from-user"),
        })
        .await?;
    println!("Contribution submitted:");
    println!("  Participant: {}", contribution.participant_id);
    println!("  Contribution hash: {}", contribution.hash);
    println!("  Verification status: {}", contribution.verified);

    // Get ceremony status
    let status = zk.setup.get_status(&setup.ceremony_id).await?;
    println!("\nCeremony status:");
    println!("  Phase: {}", status.phase);
    println!("  Total contributions: {}", status.total_contributions);
    println!("  Verified contributions: {}", status.verified_contributions);
    println!("  Ready for finalization: {}", status.ready_for_finalization);

    // Finalize setup (when enough contributions)
    if status.ready_for_finalization {
        println!("\nFinalizing setup...");
        let finalized = zk.setup.finalize(&setup.ceremony_id).await?;
        println!("Setup finalized:");
        println!("  Proving key hash: {}", finalized.proving_key_hash);
        println!("  Verification key hash: {}", finalized.verification_key_hash);
        println!("  Contribution transcript: {}", finalized.transcript_cid);
    }

    // Download ceremony transcript
    let transcript = zk.setup.get_transcript(&setup.ceremony_id).await?;
    println!("\nCeremony transcript: {} bytes", transcript.len());

    println!();
    Ok(())
}
