// SPDX-License-Identifier: MIT
//
// Vaultfire Belief Attestation — Boundless Integration Host
// ===========================================================
//
// This binary is the host-side program that:
//   1. Accepts belief attestation parameters from the command line.
//   2. Constructs the guest input.
//   3. Submits a proof request to the Boundless decentralised proving
//      marketplace.
//   4. Waits for the proof to be fulfilled.
//   5. Outputs the seal and journal for on-chain submission.
//
// Usage:
//   cargo run --release -- \
//     --belief-message "I believe in decentralised identity" \
//     --attester 0xYourAddress \
//     --epoch 42 \
//     --module-id 1 \
//     --loyalty-score 9000
//
// Environment variables (required):
//   RPC_URL       — Ethereum RPC endpoint (Base Mainnet or Sepolia).
//   PRIVATE_KEY   — Wallet private key for paying proof fees.
//   PINATA_JWT    — (Optional) Pinata JWT for IPFS storage of the program.

use std::time::Duration;

use alloy_primitives::Address;
use anyhow::{Context, Result};
use boundless_market::client::Client;
use boundless_market::storage::storage_provider_from_env;
use clap::Parser;
use risc0_zkvm::sha::Digestible;
use serde::Serialize;

// ---------------------------------------------------------------------------
//  CLI Arguments
// ---------------------------------------------------------------------------

#[derive(Parser, Debug)]
#[command(
    name = "vaultfire-boundless",
    about = "Submit Vaultfire belief attestation proofs to Boundless"
)]
struct Args {
    /// Ethereum RPC URL (Base Mainnet or Sepolia).
    #[arg(long, env = "RPC_URL")]
    rpc_url: String,

    /// Private key for the requesting wallet.
    #[arg(long, env = "PRIVATE_KEY")]
    private_key: String,

    /// The raw belief message (will be hashed inside the guest).
    #[arg(long)]
    belief_message: String,

    /// Attester Ethereum address (0x-prefixed).
    #[arg(long)]
    attester: String,

    /// Campaign / era identifier.
    #[arg(long, default_value_t = 0)]
    epoch: u32,

    /// Vaultfire module identifier.
    #[arg(long, default_value_t = 1)]
    module_id: u32,

    /// Loyalty / alignment score in basis points (0–10000).
    #[arg(long, default_value_t = 9000)]
    loyalty_score: u32,
}

// ---------------------------------------------------------------------------
//  Guest Input (must match the guest program's AttestationInput)
// ---------------------------------------------------------------------------

#[derive(Serialize)]
struct AttestationInput {
    belief_message: Vec<u8>,
    attester: [u8; 20],
    epoch: u32,
    module_id: u32,
    loyalty_score: u32,
    timestamp: u64,
}

// ---------------------------------------------------------------------------
//  Guest ELF and Image ID
// ---------------------------------------------------------------------------
//
//  In a monorepo setup, risc0-build generates these constants at compile time.
//  For a standalone build, you would set these from the guest build output.
//
//  Example (after building the guest):
//    const GUEST_ELF: &[u8] = include_bytes!("../../guest/target/.../guest.elf");
//    const GUEST_IMAGE_ID: [u32; 8] = [ ... ];
//
//  For this integration we use placeholder paths — see build.rs and the
//  README for instructions on wiring up the actual ELF.

// These will be generated by risc0-build if the guest is in the same workspace.
// Otherwise, set them manually from the guest build output.
//
// Uncomment one of the following approaches:
//
// Approach A: Monorepo (guest in same Cargo workspace)
// include!(concat!(env!("OUT_DIR"), "/methods.rs"));
//
// Approach B: Standalone (pre-built ELF)
// const GUEST_ELF: &[u8] = include_bytes!("path/to/vaultfire-belief-attestation-guest");

/// Placeholder — replace with actual ELF bytes after building the guest.
const GUEST_ELF: &[u8] = &[];

// ---------------------------------------------------------------------------
//  Main
// ---------------------------------------------------------------------------

#[tokio::main]
async fn main() -> Result<()> {
    // Initialise tracing
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "info".into()),
        )
        .init();

    let args = Args::parse();

    // Parse the attester address
    let attester_addr: Address = args
        .attester
        .parse()
        .context("Invalid attester address")?;
    let attester_bytes: [u8; 20] = attester_addr.into();

    // Get current timestamp
    let timestamp = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)?
        .as_secs();

    // Build the guest input
    let input = AttestationInput {
        belief_message: args.belief_message.as_bytes().to_vec(),
        attester: attester_bytes,
        epoch: args.epoch,
        module_id: args.module_id,
        loyalty_score: args.loyalty_score,
        timestamp,
    };

    let input_bytes =
        bincode::serialize(&input).context("Failed to serialize input")?;

    tracing::info!(
        attester = %args.attester,
        epoch = args.epoch,
        module_id = args.module_id,
        loyalty_score = args.loyalty_score,
        "Submitting belief attestation proof request to Boundless"
    );

    // Build the Boundless client
    let storage = storage_provider_from_env().ok();
    let client = Client::builder()
        .with_rpc_url(args.rpc_url.parse()?)
        .with_private_key(args.private_key.parse()?)
        .with_storage_provider(storage)
        .build()
        .await
        .context("Failed to build Boundless client")?;

    // Create and submit the proof request
    let request = client
        .new_request()
        .with_program(GUEST_ELF)
        .with_stdin(&input_bytes);

    let (request_id, expires_at) = client
        .submit(request)
        .await
        .context("Failed to submit proof request")?;

    tracing::info!(
        request_id = format!("{:x}", request_id),
        "Proof request submitted — waiting for fulfilment"
    );

    // Wait for the proof to be generated
    let fulfillment = client
        .wait_for_request_fulfillment(
            request_id,
            Duration::from_secs(5), // poll interval
            expires_at,
        )
        .await
        .context("Proof request was not fulfilled in time")?;

    tracing::info!(
        request_id = format!("{:x}", request_id),
        "Proof request fulfilled!"
    );

    // Extract the seal and journal from the fulfilment
    let seal = fulfillment.seal();
    let journal = fulfillment.journal();

    // Output the results
    println!("\n=== Proof Generation Complete ===\n");
    println!("Seal (hex):    0x{}", hex::encode(seal));
    println!("Journal (hex): 0x{}", hex::encode(journal));
    println!(
        "\nJournal digest (sha256): 0x{}",
        hex::encode(risc0_zkvm::sha::Impl::hash_bytes(journal).as_bytes())
    );

    // Save to files for easy consumption by deployment scripts
    std::fs::write("proof_seal.bin", seal)
        .context("Failed to write seal")?;
    std::fs::write("proof_journal.bin", journal)
        .context("Failed to write journal")?;
    std::fs::write("proof_seal.hex", format!("0x{}", hex::encode(seal)))
        .context("Failed to write seal hex")?;
    std::fs::write(
        "proof_journal.hex",
        format!("0x{}", hex::encode(journal)),
    )
    .context("Failed to write journal hex")?;

    println!("\nFiles written:");
    println!("  proof_seal.bin      — Raw seal bytes");
    println!("  proof_seal.hex      — Hex-encoded seal");
    println!("  proof_journal.bin   — Raw journal bytes");
    println!("  proof_journal.hex   — Hex-encoded journal");
    println!("\nUse these with the ProductionBeliefAttestationVerifier:");
    println!(
        "  verifier.verifyAttestation(seal, journal)"
    );

    Ok(())
}
