// Package main demonstrates the Synor ZK SDK for Go
//
// This example covers Zero-Knowledge proof operations:
// - Circuit compilation
// - Proof generation and verification
// - Groth16, PLONK, and STARK proving systems
// - Recursive proofs
// - On-chain verification
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/synor/sdk-go/zk"
)

func main() {
	// Initialize client
	config := zk.Config{
		APIKey:               getEnv("SYNOR_API_KEY", "your-api-key"),
		Endpoint:             "https://zk.synor.io/v1",
		Timeout:              120 * time.Second, // ZK ops can be slow
		Retries:              3,
		Debug:                false,
		DefaultProvingSystem: zk.ProvingSystemGroth16,
	}

	client, err := zk.NewClient(config)
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}
	defer client.Close()

	ctx := context.Background()

	// Check service health
	healthy, err := client.HealthCheck(ctx)
	if err != nil {
		log.Printf("Health check failed: %v", err)
	} else {
		fmt.Printf("Service healthy: %v\n\n", healthy)
	}

	// Run examples
	circuitExample(ctx, client)
	proofExample(ctx, client)
	provingSystemsExample(ctx, client)
	recursiveProofExample(ctx, client)
	onChainVerificationExample(ctx, client)
	setupExample(ctx, client)
}

func circuitExample(ctx context.Context, client *zk.Client) {
	fmt.Println("=== Circuit Compilation ===")

	// Circom circuit example: prove knowledge of preimage
	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
	fmt.Println("Compiling Circom circuit...")
	compiled, err := client.Circuits.Compile(ctx, &zk.CompileRequest{
		Code:          circomCircuit,
		Format:        zk.CircuitFormatCircom,
		Name:          "hash_preimage",
		ProvingSystem: zk.ProvingSystemGroth16,
	})
	if err != nil {
		log.Printf("Failed to compile circuit: %v", err)
		return
	}

	fmt.Println("Circuit compiled:")
	fmt.Printf("  Circuit ID: %s\n", compiled.CircuitID)
	fmt.Printf("  Constraints: %d\n", compiled.ConstraintCount)
	fmt.Printf("  Public inputs: %d\n", compiled.PublicInputCount)
	fmt.Printf("  Private inputs: %d\n", compiled.PrivateInputCount)
	fmt.Printf("  Proving key size: %d bytes\n", compiled.ProvingKeySize)
	fmt.Printf("  Verification key size: %d bytes\n", compiled.VerificationKeySize)

	// List circuits
	circuits, err := client.Circuits.List(ctx)
	if err != nil {
		log.Printf("Failed to list circuits: %v", err)
		return
	}
	fmt.Printf("\nYour circuits: %d\n", len(circuits))
	for _, circuit := range circuits {
		fmt.Printf("  %s (%s)\n", circuit.Name, circuit.CircuitID)
	}

	// Get circuit details
	details, err := client.Circuits.Get(ctx, compiled.CircuitID)
	if err != nil {
		log.Printf("Failed to get circuit details: %v", err)
		return
	}
	fmt.Println("\nCircuit details:")
	fmt.Printf("  Format: %s\n", details.Format)
	fmt.Printf("  Proving system: %s\n", details.ProvingSystem)
	fmt.Printf("  Created: %s\n", details.CreatedAt)

	// Download proving key
	provingKey, err := client.Circuits.GetProvingKey(ctx, compiled.CircuitID)
	if err != nil {
		log.Printf("Failed to get proving key: %v", err)
		return
	}
	fmt.Printf("\nProving key downloaded: %d bytes\n", len(provingKey))

	// Download verification key
	verificationKey, err := client.Circuits.GetVerificationKey(ctx, compiled.CircuitID)
	if err != nil {
		log.Printf("Failed to get verification key: %v", err)
		return
	}
	fmt.Printf("Verification key downloaded: %d bytes\n", len(verificationKey))

	fmt.Println()
}

func proofExample(ctx context.Context, client *zk.Client) {
	fmt.Println("=== Proof Generation ===")

	// First, compile a simple circuit
	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();
    `

	compiled, err := client.Circuits.Compile(ctx, &zk.CompileRequest{
		Code:          circuit,
		Format:        zk.CircuitFormatCircom,
		Name:          "multiplier",
		ProvingSystem: zk.ProvingSystemGroth16,
	})
	if err != nil {
		log.Printf("Failed to compile circuit: %v", err)
		return
	}

	// Generate proof
	fmt.Println("Generating proof...")
	startTime := time.Now()

	proof, err := client.Proofs.Generate(ctx, &zk.GenerateProofRequest{
		CircuitID: compiled.CircuitID,
		Inputs:    map[string]string{"a": "3", "b": "7"},
	})
	if err != nil {
		log.Printf("Failed to generate proof: %v", err)
		return
	}

	proofTime := time.Since(startTime)
	fmt.Printf("Proof generated in %v\n", proofTime)
	fmt.Printf("  Proof ID: %s\n", proof.ProofID)
	fmt.Printf("  Proof size: %d bytes\n", len(proof.Proof))
	fmt.Printf("  Public signals: %v\n", proof.PublicSignals)

	// Verify proof
	fmt.Println("\nVerifying proof...")
	verifyStart := time.Now()

	isValid, err := client.Proofs.Verify(ctx, &zk.VerifyRequest{
		CircuitID:     compiled.CircuitID,
		Proof:         proof.Proof,
		PublicSignals: proof.PublicSignals,
	})
	if err != nil {
		log.Printf("Failed to verify proof: %v", err)
		return
	}

	verifyTime := time.Since(verifyStart)
	fmt.Printf("Verification completed in %v\n", verifyTime)
	fmt.Printf("  Valid: %v\n", isValid)

	// Verify with wrong public signals (should fail)
	fmt.Println("\nVerifying with wrong signals...")
	invalidResult, err := client.Proofs.Verify(ctx, &zk.VerifyRequest{
		CircuitID:     compiled.CircuitID,
		Proof:         proof.Proof,
		PublicSignals: []string{"42"}, // Wrong answer
	})
	if err != nil {
		log.Printf("Failed to verify with wrong signals: %v", err)
		return
	}
	fmt.Printf("  Valid: %v (expected false)\n", invalidResult)

	// Get proof status
	status, err := client.Proofs.GetStatus(ctx, proof.ProofID)
	if err != nil {
		log.Printf("Failed to get proof status: %v", err)
		return
	}
	fmt.Println("\nProof status:")
	fmt.Printf("  State: %s\n", status.State)
	fmt.Printf("  Verified: %v\n", status.Verified)
	fmt.Printf("  Created: %s\n", status.CreatedAt)

	// List proofs
	proofs, err := client.Proofs.List(ctx, compiled.CircuitID)
	if err != nil {
		log.Printf("Failed to list proofs: %v", err)
		return
	}
	fmt.Printf("\nProofs for circuit: %d\n", len(proofs))

	fmt.Println()
}

func provingSystemsExample(ctx context.Context, client *zk.Client) {
	fmt.Println("=== Proving Systems Comparison ===")

	// Simple circuit for comparison
	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();
    `

	systems := []struct {
		system zk.ProvingSystem
		name   string
	}{
		{zk.ProvingSystemGroth16, "GROTH16"},
		{zk.ProvingSystemPLONK, "PLONK"},
		{zk.ProvingSystemSTARK, "STARK"},
	}

	fmt.Println("Comparing proving systems:\n")

	for _, s := range systems {
		fmt.Printf("%s:\n", s.name)

		// Compile for this system
		compiled, err := client.Circuits.Compile(ctx, &zk.CompileRequest{
			Code:          circuit,
			Format:        zk.CircuitFormatCircom,
			Name:          fmt.Sprintf("comparison_%s", s.name),
			ProvingSystem: s.system,
		})
		if err != nil {
			log.Printf("Failed to compile for %s: %v", s.name, err)
			continue
		}

		// Generate proof
		proofStart := time.Now()
		proof, err := client.Proofs.Generate(ctx, &zk.GenerateProofRequest{
			CircuitID: compiled.CircuitID,
			Inputs:    map[string]string{"x": "10", "y": "20"},
		})
		if err != nil {
			log.Printf("Failed to generate proof for %s: %v", s.name, err)
			continue
		}
		proofTime := time.Since(proofStart)

		// Verify proof
		verifyStart := time.Now()
		_, err = client.Proofs.Verify(ctx, &zk.VerifyRequest{
			CircuitID:     compiled.CircuitID,
			Proof:         proof.Proof,
			PublicSignals: proof.PublicSignals,
		})
		if err != nil {
			log.Printf("Failed to verify proof for %s: %v", s.name, err)
			continue
		}
		verifyTime := time.Since(verifyStart)

		fmt.Printf("  Setup: %dms\n", compiled.SetupTime)
		fmt.Printf("  Proof time: %v\n", proofTime)
		fmt.Printf("  Verify time: %v\n", verifyTime)
		fmt.Printf("  Proof size: %d bytes\n", len(proof.Proof))
		fmt.Printf("  Verification key: %d bytes\n", compiled.VerificationKeySize)
		fmt.Println()
	}

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

	fmt.Println()
}

func recursiveProofExample(ctx context.Context, client *zk.Client) {
	fmt.Println("=== Recursive Proofs ===")

	// Inner circuit
	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
	inner, err := client.Circuits.Compile(ctx, &zk.CompileRequest{
		Code:          innerCircuit,
		Format:        zk.CircuitFormatCircom,
		Name:          "inner_circuit",
		ProvingSystem: zk.ProvingSystemGroth16,
	})
	if err != nil {
		log.Printf("Failed to compile inner circuit: %v", err)
		return
	}

	// Generate multiple proofs to aggregate
	fmt.Println("Generating proofs to aggregate...")
	var proofsToAggregate []zk.ProofData
	for i := 1; i <= 4; i++ {
		proof, err := client.Proofs.Generate(ctx, &zk.GenerateProofRequest{
			CircuitID: inner.CircuitID,
			Inputs:    map[string]string{"x": fmt.Sprintf("%d", i)},
		})
		if err != nil {
			log.Printf("Failed to generate proof %d: %v", i, err)
			continue
		}
		proofsToAggregate = append(proofsToAggregate, zk.ProofData{
			Proof:         proof.Proof,
			PublicSignals: proof.PublicSignals,
		})
		fmt.Printf("  Proof %d: y = %s\n", i, proof.PublicSignals[0])
	}

	// Aggregate proofs recursively
	fmt.Println("\nAggregating proofs...")
	aggregated, err := client.Proofs.Aggregate(ctx, &zk.AggregateRequest{
		CircuitID:       inner.CircuitID,
		Proofs:          proofsToAggregate,
		AggregationType: "recursive",
	})
	if err != nil {
		log.Printf("Failed to aggregate proofs: %v", err)
		return
	}

	originalSize := 0
	for _, p := range proofsToAggregate {
		originalSize += len(p.Proof)
	}

	fmt.Println("Aggregated proof:")
	fmt.Printf("  Proof ID: %s\n", aggregated.ProofID)
	fmt.Printf("  Aggregated count: %d\n", aggregated.AggregatedCount)
	fmt.Printf("  Proof size: %d bytes\n", len(aggregated.Proof))
	fmt.Printf("  Size reduction: %.1f%%\n", (1-float64(len(aggregated.Proof))/float64(originalSize))*100)

	// Verify aggregated proof
	publicSignalsList := make([][]string, len(proofsToAggregate))
	for i, p := range proofsToAggregate {
		publicSignalsList[i] = p.PublicSignals
	}

	isValid, err := client.Proofs.VerifyAggregated(ctx, &zk.VerifyAggregatedRequest{
		CircuitID:         inner.CircuitID,
		Proof:             aggregated.Proof,
		PublicSignalsList: publicSignalsList,
	})
	if err != nil {
		log.Printf("Failed to verify aggregated proof: %v", err)
		return
	}
	fmt.Printf("\nAggregated proof valid: %v\n", isValid)

	// Batch verification (verify multiple proofs in one operation)
	fmt.Println("\nBatch verification...")
	batchResult, err := client.Proofs.BatchVerify(ctx, &zk.BatchVerifyRequest{
		CircuitID: inner.CircuitID,
		Proofs:    proofsToAggregate,
	})
	if err != nil {
		log.Printf("Failed to batch verify: %v", err)
		return
	}
	fmt.Printf("  All valid: %v\n", batchResult.AllValid)
	fmt.Printf("  Results: %v\n", batchResult.Results)

	fmt.Println()
}

func onChainVerificationExample(ctx context.Context, client *zk.Client) {
	fmt.Println("=== On-Chain Verification ===")

	// Compile circuit
	circuit := `
    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();
    `

	compiled, err := client.Circuits.Compile(ctx, &zk.CompileRequest{
		Code:          circuit,
		Format:        zk.CircuitFormatCircom,
		Name:          "vote_commitment",
		ProvingSystem: zk.ProvingSystemGroth16,
	})
	if err != nil {
		log.Printf("Failed to compile circuit: %v", err)
		return
	}

	// Generate Solidity verifier
	fmt.Println("Generating Solidity verifier...")
	solidityVerifier, err := client.Contracts.GenerateVerifier(ctx, &zk.GenerateVerifierRequest{
		CircuitID: compiled.CircuitID,
		Language:  "solidity",
		Optimized: true,
	})
	if err != nil {
		log.Printf("Failed to generate verifier: %v", err)
		return
	}
	fmt.Printf("Solidity verifier generated: %d bytes\n", len(solidityVerifier.Code))
	fmt.Printf("  Contract name: %s\n", solidityVerifier.ContractName)
	fmt.Printf("  Gas estimate: %d\n", solidityVerifier.GasEstimate)

	// Generate proof
	proof, err := client.Proofs.Generate(ctx, &zk.GenerateProofRequest{
		CircuitID: compiled.CircuitID,
		Inputs: map[string]string{
			"vote":       "1",     // Vote YES (1) or NO (0)
			"nullifier":  "12345",
			"commitment": "12345", // 1 * 12345
		},
	})
	if err != nil {
		log.Printf("Failed to generate proof: %v", err)
		return
	}

	// Format proof for on-chain verification
	fmt.Println("\nFormatting proof for on-chain...")
	onChainProof, err := client.Contracts.FormatProof(ctx, &zk.FormatProofRequest{
		CircuitID:     compiled.CircuitID,
		Proof:         proof.Proof,
		PublicSignals: proof.PublicSignals,
		Format:        "calldata",
	})
	if err != nil {
		log.Printf("Failed to format proof: %v", err)
		return
	}
	calldataPreview := onChainProof.Calldata
	if len(calldataPreview) > 100 {
		calldataPreview = calldataPreview[:100]
	}
	fmt.Printf("  Calldata: %s...\n", calldataPreview)
	fmt.Printf("  Estimated gas: %d\n", onChainProof.GasEstimate)

	// Deploy verifier contract (simulation)
	fmt.Println("\nDeploying verifier contract...")
	deployment, err := client.Contracts.DeployVerifier(ctx, &zk.DeployRequest{
		CircuitID: compiled.CircuitID,
		Network:   "synor-testnet",
	})
	if err != nil {
		log.Printf("Failed to deploy verifier: %v", err)
		return
	}
	fmt.Printf("  Contract address: %s\n", deployment.Address)
	fmt.Printf("  TX hash: %s\n", deployment.TxHash)
	fmt.Printf("  Gas used: %d\n", deployment.GasUsed)

	// Verify on-chain
	fmt.Println("\nVerifying on-chain...")
	onChainResult, err := client.Contracts.VerifyOnChain(ctx, &zk.OnChainVerifyRequest{
		ContractAddress: deployment.Address,
		Proof:           proof.Proof,
		PublicSignals:   proof.PublicSignals,
		Network:         "synor-testnet",
	})
	if err != nil {
		log.Printf("Failed to verify on-chain: %v", err)
		return
	}
	fmt.Printf("  TX hash: %s\n", onChainResult.TxHash)
	fmt.Printf("  Verified: %v\n", onChainResult.Verified)
	fmt.Printf("  Gas used: %d\n", onChainResult.GasUsed)

	// Generate verifier for other targets
	fmt.Println("\nGenerating verifiers for other targets:")
	targets := []string{"cairo", "noir", "ink"}
	for _, target := range targets {
		verifier, err := client.Contracts.GenerateVerifier(ctx, &zk.GenerateVerifierRequest{
			CircuitID: compiled.CircuitID,
			Language:  target,
		})
		if err != nil {
			log.Printf("Failed to generate %s verifier: %v", target, err)
			continue
		}
		fmt.Printf("  %s: %d bytes\n", target, len(verifier.Code))
	}

	fmt.Println()
}

func setupExample(ctx context.Context, client *zk.Client) {
	fmt.Println("=== Trusted Setup ===")

	// Get available ceremonies
	ceremonies, err := client.Setup.ListCeremonies(ctx)
	if err != nil {
		log.Printf("Failed to list ceremonies: %v", err)
		return
	}
	fmt.Printf("Active ceremonies: %d\n", len(ceremonies))
	for _, ceremony := range ceremonies {
		fmt.Printf("  %s:\n", ceremony.Name)
		fmt.Printf("    Status: %s\n", ceremony.Status)
		fmt.Printf("    Participants: %d\n", ceremony.ParticipantCount)
		fmt.Printf("    Current round: %d\n", ceremony.CurrentRound)
	}

	// Create a new circuit setup
	circuit := `
    pragma circom 2.1.0;

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

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

	fmt.Println("\nInitializing setup for new circuit...")
	setup, err := client.Setup.Initialize(ctx, &zk.SetupInitRequest{
		Circuit:       circuit,
		Format:        zk.CircuitFormatCircom,
		Name:          "new_circuit_setup",
		ProvingSystem: zk.ProvingSystemGroth16,
		CeremonyType:  "powers_of_tau", // or 'phase2'
	})
	if err != nil {
		log.Printf("Failed to initialize setup: %v", err)
		return
	}
	fmt.Println("Setup initialized:")
	fmt.Printf("  Ceremony ID: %s\n", setup.CeremonyID)
	fmt.Printf("  Powers of Tau required: %d\n", setup.PowersRequired)
	fmt.Printf("  Current phase: %s\n", setup.Phase)

	// Contribute to ceremony (in practice, generates random entropy)
	fmt.Println("\nContributing to ceremony...")
	contribution, err := client.Setup.Contribute(ctx, &zk.ContributeRequest{
		CeremonyID: setup.CeremonyID,
		Entropy:    fmt.Sprintf("%x", []byte("random-entropy-from-user")),
	})
	if err != nil {
		log.Printf("Failed to contribute: %v", err)
		return
	}
	fmt.Println("Contribution submitted:")
	fmt.Printf("  Participant: %s\n", contribution.ParticipantID)
	fmt.Printf("  Contribution hash: %s\n", contribution.Hash)
	fmt.Printf("  Verification status: %v\n", contribution.Verified)

	// Get ceremony status
	status, err := client.Setup.GetStatus(ctx, setup.CeremonyID)
	if err != nil {
		log.Printf("Failed to get ceremony status: %v", err)
		return
	}
	fmt.Println("\nCeremony status:")
	fmt.Printf("  Phase: %s\n", status.Phase)
	fmt.Printf("  Total contributions: %d\n", status.TotalContributions)
	fmt.Printf("  Verified contributions: %d\n", status.VerifiedContributions)
	fmt.Printf("  Ready for finalization: %v\n", status.ReadyForFinalization)

	// Finalize setup (when enough contributions)
	if status.ReadyForFinalization {
		fmt.Println("\nFinalizing setup...")
		finalized, err := client.Setup.Finalize(ctx, setup.CeremonyID)
		if err != nil {
			log.Printf("Failed to finalize setup: %v", err)
			return
		}
		fmt.Println("Setup finalized:")
		fmt.Printf("  Proving key hash: %s\n", finalized.ProvingKeyHash)
		fmt.Printf("  Verification key hash: %s\n", finalized.VerificationKeyHash)
		fmt.Printf("  Contribution transcript: %s\n", finalized.TranscriptCID)
	}

	// Download ceremony transcript
	transcript, err := client.Setup.GetTranscript(ctx, setup.CeremonyID)
	if err != nil {
		log.Printf("Failed to get transcript: %v", err)
		return
	}
	fmt.Printf("\nCeremony transcript: %d bytes\n", len(transcript))

	fmt.Println()
}

func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}
