//! SPL Token-2022 Confidential Transfers
//!
//! Enables privacy-preserving token transfers on Solana using the Token-2022
//! Confidential Transfer extension. Transfer amounts are encrypted on-chain
//! using twisted ElGamal encryption with ZK proofs for validity.
//!
//! # Supported Operations
//!
//! - Create Token-2022 mints with confidential transfer extension
//! - Configure token accounts for confidential transfers
//! - Deposit tokens to confidential balance (public -> encrypted)
//! - Transfer tokens confidentially (amount hidden on-chain)
//! - Apply pending balance (recipient claims incoming transfers)
//! - Withdraw from confidential balance (encrypted -> public)
//!
//! # Privacy Guarantees
//!
//! - Transfer AMOUNTS are ElGamal-encrypted on-chain
//! - ZK proofs ensure validity without revealing amounts
//! - Sender and recipient addresses remain publicly visible
//! - Uses Solana's native ZK proof verification (on-chain program)
//!
//! # Architecture
//!
//! Uses the `spl-token` CLI (v5.5+) for instruction building and on-chain
//! proof generation. Integrates with Coldstar's Rust ZK engine for signing
//! pipeline integrity.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::Command;
use thiserror::Error;

use coldstar_config::DEFAULT_RPC_URL;

/// Errors from confidential transfer operations.
#[derive(Error, Debug)]
pub enum ConfidentialError {
    /// CLI binary not found.
    #[error("{binary} CLI not found — install with: {install_hint}")]
    BinaryNotFound {
        binary: String,
        install_hint: String,
    },

    /// CLI command failed.
    #[error("CLI command failed: {0}")]
    CommandFailed(String),

    /// I/O error during command execution.
    #[error("I/O error: {0}")]
    IoError(#[from] std::io::Error),

    /// JSON parsing error.
    #[error("JSON error: {0}")]
    JsonError(#[from] serde_json::Error),

    /// A workflow step failed.
    #[error("Step '{step}' failed: {detail}")]
    StepFailed {
        step: String,
        detail: String,
    },
}

/// Lifecycle state of a token account's confidential transfer capability.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConfidentialTransferState {
    /// Account has not been configured for confidential transfers.
    NotConfigured,
    /// Account has been configured with an ElGamal keypair.
    Configured,
    /// Account is approved for confidential transfers.
    Approved,
    /// Account is active and participating in confidential transfers.
    Active,
}

/// Metadata about a Token-2022 mint with confidential transfers enabled.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfidentialMintInfo {
    /// Mint address (base58).
    pub mint_address: String,
    /// Token decimal places.
    pub decimals: u8,
    /// Mint authority address.
    pub authority: String,
    /// Whether the confidential transfer extension is enabled.
    pub confidential_transfer_enabled: bool,
    /// Whether new accounts are auto-approved for confidential transfers.
    pub auto_approve: bool,
}

/// Metadata about a token account configured for confidential transfers.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfidentialAccountInfo {
    /// Token account address (base58).
    pub account_address: String,
    /// Mint address (base58).
    pub mint_address: String,
    /// Owner wallet address (base58).
    pub owner: String,
    /// Visible (non-encrypted) token balance.
    pub public_balance: f64,
    /// Whether confidential transfers are configured.
    pub confidential_configured: bool,
    /// ElGamal public key (if configured).
    pub elgamal_pubkey: Option<String>,
}

/// Result from a CLI operation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CliResult {
    /// Whether the operation succeeded.
    pub success: bool,
    /// Error message (if failed).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
    /// Key-value data from the operation.
    #[serde(flatten)]
    pub data: HashMap<String, serde_json::Value>,
    /// Raw CLI output.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output: Option<String>,
}

impl CliResult {
    fn success(data: HashMap<String, serde_json::Value>, output: String) -> Self {
        Self {
            success: true,
            error: None,
            data,
            output: Some(output),
        }
    }

    fn failure(error: String) -> Self {
        Self {
            success: false,
            error: Some(error),
            data: HashMap::new(),
            output: None,
        }
    }
}

/// Token-2022 program ID.
const TOKEN_2022_PROGRAM_ID: &str = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";

/// Manages SPL Token-2022 Confidential Transfer operations.
///
/// Bridges Coldstar's secure signing architecture with Solana's Token-2022
/// confidential transfer extension. Supports the full lifecycle:
///
/// - `create_confidential_mint()` — new Token-2022 mint
/// - `create_token_account()` — associated token account
/// - `configure_confidential_account()` — enable confidential mode
/// - `mint_tokens()` — mint supply (authority only)
/// - `deposit_to_confidential()` — public -> encrypted balance
/// - `confidential_transfer()` — encrypted transfer (amount hidden)
/// - `apply_pending_balance()` — claim incoming transfers
/// - `withdraw_from_confidential()` — encrypted -> public balance
///
/// Also provides:
/// - `setup_confidential_token()` — full setup in one call
/// - `full_confidential_transfer()` — deposit + transfer in one call
/// - `check_prerequisites()` — verify tools & connectivity
pub struct ConfidentialTransferManager {
    /// Solana RPC endpoint URL.
    rpc_url: String,
    /// Path to the Solana keypair JSON file.
    keypair_path: Option<PathBuf>,
    /// Path to the `spl-token` binary.
    spl_token_bin: PathBuf,
    /// Path to the `solana` CLI binary.
    solana_bin: PathBuf,
}

impl ConfidentialTransferManager {
    /// Create a new manager.
    ///
    /// # Arguments
    ///
    /// * `rpc_url` - Solana RPC endpoint (defaults to devnet)
    /// * `keypair_path` - Path to Solana keypair JSON file for signing
    /// * `spl_token_binary` - Explicit path to `spl-token` CLI binary (auto-detected if `None`)
    ///
    /// # Errors
    ///
    /// Returns [`ConfidentialError::BinaryNotFound`] if required CLI tools
    /// cannot be located.
    pub fn new(
        rpc_url: Option<&str>,
        keypair_path: Option<&Path>,
        spl_token_binary: Option<&Path>,
    ) -> Result<Self, ConfidentialError> {
        let spl_token_bin = match spl_token_binary {
            Some(p) => p.to_path_buf(),
            None => find_binary("spl-token")?,
        };
        let solana_bin = find_binary("solana")?;

        Ok(Self {
            rpc_url: rpc_url.unwrap_or(DEFAULT_RPC_URL).to_string(),
            keypair_path: keypair_path.map(|p| p.to_path_buf()),
            spl_token_bin,
            solana_bin,
        })
    }

    // -- CLI helpers ------------------------------------------------------

    fn run_spl_token(&self, args: &[&str]) -> Result<std::process::Output, ConfidentialError> {
        let mut cmd = Command::new(&self.spl_token_bin);
        cmd.args(args).args(["--url", &self.rpc_url]);

        if let Some(ref kp) = self.keypair_path {
            cmd.args(["--owner", &kp.to_string_lossy()]);
        }

        let output = cmd.output()?;
        Ok(output)
    }

    #[allow(dead_code)]
    fn run_solana(&self, args: &[&str]) -> Result<std::process::Output, ConfidentialError> {
        let mut cmd = Command::new(&self.solana_bin);
        cmd.args(args).args(["--url", &self.rpc_url]);

        if let Some(ref kp) = self.keypair_path {
            cmd.args(["--keypair", &kp.to_string_lossy()]);
        }

        let output = cmd.output()?;
        Ok(output)
    }

    // =====================================================================
    //  MINT OPERATIONS
    // =====================================================================

    /// Create a new Token-2022 mint with confidential transfer extension.
    ///
    /// # Arguments
    ///
    /// * `decimals` - Token decimal places (default: 6, same as USDC)
    /// * `auto_approve` - Auto-approve accounts for confidential transfers
    pub fn create_confidential_mint(
        &self,
        decimals: u8,
        auto_approve: bool,
    ) -> CliResult {
        let approve_policy = if auto_approve { "auto" } else { "manual" };
        let decimals_str = decimals.to_string();

        let output = match self.run_spl_token(&[
            "create-token",
            "--program-id", TOKEN_2022_PROGRAM_ID,
            "--decimals", &decimals_str,
            "--enable-confidential-transfers", approve_policy,
        ]) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let mint_address = parse_address_from_output(&stdout, "Creating token")
            .or_else(|| parse_address_from_output(&stdout, "Address:"))
            .or_else(|| parse_first_address(&stdout));

        let mut data = HashMap::new();
        if let Some(ref addr) = mint_address {
            data.insert("mint".to_string(), serde_json::Value::String(addr.clone()));
        }
        data.insert("decimals".to_string(), serde_json::json!(decimals));
        data.insert("auto_approve".to_string(), serde_json::json!(auto_approve));
        if let Some(sig) = parse_signature(&stdout) {
            data.insert("signature".to_string(), serde_json::Value::String(sig));
        }

        CliResult::success(data, stdout)
    }

    // =====================================================================
    //  ACCOUNT OPERATIONS
    // =====================================================================

    /// Create a token account (Associated Token Account) for the given mint.
    pub fn create_token_account(&self, mint_address: &str) -> CliResult {
        let output = match self.run_spl_token(&[
            "create-account", mint_address,
            "--program-id", TOKEN_2022_PROGRAM_ID,
        ]) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let account_address = parse_address_from_output(&stdout, "Creating account")
            .or_else(|| parse_address_from_output(&stdout, "account"));

        let mut data = HashMap::new();
        if let Some(ref addr) = account_address {
            data.insert("account".to_string(), serde_json::Value::String(addr.clone()));
        }
        data.insert("mint".to_string(), serde_json::Value::String(mint_address.to_string()));
        if let Some(sig) = parse_signature(&stdout) {
            data.insert("signature".to_string(), serde_json::Value::String(sig));
        }

        CliResult::success(data, stdout)
    }

    /// Configure a token account for confidential transfers.
    ///
    /// Generates an ElGamal keypair for the account and submits a ZK proof
    /// of key ownership to the Token-2022 program.
    pub fn configure_confidential_account(
        &self,
        token_account: Option<&str>,
        mint_address: Option<&str>,
    ) -> CliResult {
        let mut args = vec!["configure-confidential-transfer-account"];
        if let Some(acct) = token_account {
            args.extend(["--address", acct]);
        }

        let output = match self.run_spl_token(&args) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let mut data = HashMap::new();
        data.insert("configured".to_string(), serde_json::json!(true));
        if let Some(acct) = token_account {
            data.insert("account".to_string(), serde_json::Value::String(acct.to_string()));
        }
        if let Some(mint) = mint_address {
            data.insert("mint".to_string(), serde_json::Value::String(mint.to_string()));
        }

        CliResult::success(data, stdout)
    }

    // =====================================================================
    //  TOKEN MINTING
    // =====================================================================

    /// Mint tokens to an account (requires mint authority).
    pub fn mint_tokens(
        &self,
        mint_address: &str,
        amount: f64,
        recipient: Option<&str>,
    ) -> CliResult {
        let amount_str = amount.to_string();
        let mut args = vec!["mint", mint_address, &amount_str];
        if let Some(recip) = recipient {
            args.push(recip);
        }
        args.extend(["--program-id", TOKEN_2022_PROGRAM_ID]);

        let output = match self.run_spl_token(&args) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let mut data = HashMap::new();
        data.insert("mint".to_string(), serde_json::Value::String(mint_address.to_string()));
        data.insert("amount".to_string(), serde_json::json!(amount));
        if let Some(recip) = recipient {
            data.insert("recipient".to_string(), serde_json::Value::String(recip.to_string()));
        }

        CliResult::success(data, stdout)
    }

    // =====================================================================
    //  CONFIDENTIAL OPERATIONS
    // =====================================================================

    /// Deposit tokens from public balance to confidential balance.
    ///
    /// Moves tokens from the visible on-chain balance into the
    /// ElGamal-encrypted confidential balance.
    pub fn deposit_to_confidential(
        &self,
        mint_address: &str,
        amount: f64,
    ) -> CliResult {
        let amount_str = amount.to_string();
        let output = match self.run_spl_token(&[
            "deposit-confidential-tokens",
            mint_address,
            &amount_str,
        ]) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let mut data = HashMap::new();
        data.insert("deposited".to_string(), serde_json::json!(amount));
        data.insert("mint".to_string(), serde_json::Value::String(mint_address.to_string()));

        CliResult::success(data, stdout)
    }

    /// Perform a confidential token transfer.
    ///
    /// The transfer amount is encrypted on-chain using twisted ElGamal
    /// encryption. The `spl-token` CLI automatically generates the required
    /// ZK proofs (range, equality, validity). Only the transfer amount is
    /// hidden; sender and recipient addresses remain visible.
    pub fn confidential_transfer(
        &self,
        mint_address: &str,
        amount: f64,
        recipient: &str,
    ) -> CliResult {
        let amount_str = amount.to_string();
        let output = match self.run_spl_token(&[
            "transfer",
            mint_address,
            &amount_str,
            recipient,
            "--confidential",
        ]) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let mut data = HashMap::new();
        data.insert("amount_hidden".to_string(), serde_json::json!(true));
        data.insert("mint".to_string(), serde_json::Value::String(mint_address.to_string()));
        data.insert("recipient".to_string(), serde_json::Value::String(recipient.to_string()));

        CliResult::success(data, stdout)
    }

    /// Apply pending confidential balance to available balance.
    ///
    /// After receiving a confidential transfer, tokens land in a 'pending'
    /// state. This instruction moves them to the available confidential
    /// balance so they can be transferred or withdrawn.
    pub fn apply_pending_balance(&self, token_account: Option<&str>) -> CliResult {
        let mut args = vec!["apply-pending-balance"];
        if let Some(acct) = token_account {
            args.extend(["--address", acct]);
        }

        let output = match self.run_spl_token(&args) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let mut data = HashMap::new();
        data.insert("applied".to_string(), serde_json::json!(true));

        CliResult::success(data, stdout)
    }

    /// Withdraw tokens from confidential balance to public balance.
    ///
    /// Decrypts and moves tokens from the encrypted confidential balance
    /// back to the visible public balance.
    pub fn withdraw_from_confidential(
        &self,
        mint_address: &str,
        amount: f64,
    ) -> CliResult {
        let amount_str = amount.to_string();
        let output = match self.run_spl_token(&[
            "withdraw-confidential-tokens",
            mint_address,
            &amount_str,
        ]) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let mut data = HashMap::new();
        data.insert("withdrawn".to_string(), serde_json::json!(amount));
        data.insert("mint".to_string(), serde_json::Value::String(mint_address.to_string()));

        CliResult::success(data, stdout)
    }

    // =====================================================================
    //  QUERY OPERATIONS
    // =====================================================================

    /// Get detailed info about a token account, including confidential state.
    pub fn get_account_info(&self, token_account: &str) -> CliResult {
        let output = match self.run_spl_token(&["display", token_account]) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let mut data = HashMap::new();
        data.insert(
            "account".to_string(),
            serde_json::Value::String(token_account.to_string()),
        );
        data.insert(
            "confidential_configured".to_string(),
            serde_json::json!(stdout.contains("Confidential transfer")),
        );

        // Parse key fields from display output
        for line in stdout.lines() {
            let trimmed = line.trim();
            if let Some(rest) = trimmed.strip_prefix("Mint:") {
                data.insert(
                    "mint".to_string(),
                    serde_json::Value::String(rest.trim().to_string()),
                );
            } else if let Some(rest) = trimmed.strip_prefix("Owner:") {
                data.insert(
                    "owner".to_string(),
                    serde_json::Value::String(rest.trim().to_string()),
                );
            } else if trimmed.to_lowercase().contains("balance:")
                && !trimmed.to_lowercase().contains("confidential")
            {
                if let Some(val_str) = trimmed.split(':').last() {
                    if let Ok(balance) = val_str.trim().parse::<f64>() {
                        data.insert(
                            "public_balance".to_string(),
                            serde_json::json!(balance),
                        );
                    }
                }
            }
        }

        CliResult::success(data, stdout)
    }

    /// Get the public balance of a token account.
    pub fn get_token_balance(&self, token_account: &str) -> CliResult {
        let output = match self.run_spl_token(&["balance", "--address", token_account]) {
            Ok(o) => o,
            Err(e) => return CliResult::failure(e.to_string()),
        };

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return CliResult::failure(stderr.trim().to_string());
        }

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let balance: f64 = stdout.trim().parse().unwrap_or(0.0);

        let mut data = HashMap::new();
        data.insert("balance".to_string(), serde_json::json!(balance));

        CliResult::success(data, stdout)
    }

    // =====================================================================
    //  COMPOUND WORKFLOWS
    // =====================================================================

    /// Complete setup: create mint -> create account -> configure -> mint tokens.
    ///
    /// # Arguments
    ///
    /// * `decimals` - Token decimal places
    /// * `initial_supply` - Amount to mint after setup (0.0 = skip minting)
    pub fn setup_confidential_token(
        &self,
        decimals: u8,
        initial_supply: f64,
    ) -> CliResult {
        let mut steps: Vec<serde_json::Value> = Vec::new();

        // 1. Create mint
        let mint_result = self.create_confidential_mint(decimals, true);
        steps.push(serde_json::json!({"step": "create_mint", "result": &mint_result}));
        if !mint_result.success {
            return CliResult::failure(format!(
                "Failed to create mint: {}",
                mint_result.error.unwrap_or_default()
            ));
        }
        let mint_address = mint_result
            .data
            .get("mint")
            .and_then(|v| v.as_str())
            .unwrap_or("")
            .to_string();

        if mint_address.is_empty() {
            return CliResult::failure("Failed to parse mint address from output".to_string());
        }

        // 2. Create token account
        let account_result = self.create_token_account(&mint_address);
        steps.push(serde_json::json!({"step": "create_account", "result": &account_result}));
        if !account_result.success {
            return CliResult::failure(format!(
                "Failed to create account: {}",
                account_result.error.unwrap_or_default()
            ));
        }
        let token_account = account_result
            .data
            .get("account")
            .and_then(|v| v.as_str())
            .unwrap_or("")
            .to_string();

        // 3. Configure for confidential transfers
        let config_result = self.configure_confidential_account(
            if token_account.is_empty() { None } else { Some(&token_account) },
            Some(&mint_address),
        );
        steps.push(serde_json::json!({"step": "configure_confidential", "result": &config_result}));
        if !config_result.success {
            return CliResult::failure(format!(
                "Failed to configure confidential transfers: {}",
                config_result.error.unwrap_or_default()
            ));
        }

        // 4. Mint initial tokens (if requested)
        if initial_supply > 0.0 {
            let mint_result2 = self.mint_tokens(&mint_address, initial_supply, None);
            steps.push(serde_json::json!({"step": "mint_tokens", "result": &mint_result2}));
            if !mint_result2.success {
                return CliResult::failure(format!(
                    "Failed to mint tokens: {}",
                    mint_result2.error.unwrap_or_default()
                ));
            }
        }

        let mut data = HashMap::new();
        data.insert("mint".to_string(), serde_json::Value::String(mint_address));
        data.insert("account".to_string(), serde_json::Value::String(token_account));
        data.insert("decimals".to_string(), serde_json::json!(decimals));
        data.insert("initial_supply".to_string(), serde_json::json!(initial_supply));
        data.insert("steps".to_string(), serde_json::json!(steps));

        CliResult::success(data, "Setup complete".to_string())
    }

    /// Complete confidential transfer: deposit -> transfer.
    ///
    /// # Arguments
    ///
    /// * `mint_address` - Token mint address
    /// * `amount` - Amount to transfer
    /// * `recipient` - Recipient wallet public key
    /// * `deposit_first` - Deposit to confidential balance before transfer
    pub fn full_confidential_transfer(
        &self,
        mint_address: &str,
        amount: f64,
        recipient: &str,
        deposit_first: bool,
    ) -> CliResult {
        let mut steps: Vec<serde_json::Value> = Vec::new();

        // 1. Deposit to confidential balance (if needed)
        if deposit_first {
            let deposit_result = self.deposit_to_confidential(mint_address, amount);
            steps.push(serde_json::json!({"step": "deposit", "result": &deposit_result}));
            if !deposit_result.success {
                return CliResult::failure(format!(
                    "Deposit failed: {}",
                    deposit_result.error.unwrap_or_default()
                ));
            }
        }

        // 2. Confidential transfer
        let transfer_result = self.confidential_transfer(mint_address, amount, recipient);
        steps.push(serde_json::json!({"step": "transfer", "result": &transfer_result}));
        if !transfer_result.success {
            return CliResult::failure(format!(
                "Transfer failed: {}",
                transfer_result.error.unwrap_or_default()
            ));
        }

        let mut data = HashMap::new();
        data.insert("amount_hidden_on_chain".to_string(), serde_json::json!(true));
        data.insert("mint".to_string(), serde_json::Value::String(mint_address.to_string()));
        data.insert("recipient".to_string(), serde_json::Value::String(recipient.to_string()));
        data.insert("steps".to_string(), serde_json::json!(steps));
        data.insert(
            "privacy_note".to_string(),
            serde_json::Value::String(
                "Transfer amount is ElGamal-encrypted on-chain. \
                 Sender and recipient addresses are still visible. \
                 ZK proofs ensure transfer validity without revealing the amount."
                    .to_string(),
            ),
        );

        CliResult::success(data, "Confidential transfer complete".to_string())
    }

    // =====================================================================
    //  PREREQUISITES CHECK
    // =====================================================================

    /// Verify all tools and dependencies are available for confidential transfers.
    pub fn check_prerequisites(&self) -> CliResult {
        let mut checks = HashMap::new();

        // spl-token CLI
        match Command::new(&self.spl_token_bin)
            .arg("--version")
            .output()
        {
            Ok(output) if output.status.success() => {
                checks.insert(
                    "spl_token_cli".to_string(),
                    serde_json::json!({
                        "available": true,
                        "version": String::from_utf8_lossy(&output.stdout).trim().to_string(),
                        "path": self.spl_token_bin.to_string_lossy(),
                    }),
                );
            }
            Ok(_) | Err(_) => {
                checks.insert(
                    "spl_token_cli".to_string(),
                    serde_json::json!({"available": false}),
                );
            }
        }

        // solana CLI
        match Command::new(&self.solana_bin)
            .arg("--version")
            .output()
        {
            Ok(output) if output.status.success() => {
                checks.insert(
                    "solana_cli".to_string(),
                    serde_json::json!({
                        "available": true,
                        "version": String::from_utf8_lossy(&output.stdout).trim().to_string(),
                        "path": self.solana_bin.to_string_lossy(),
                    }),
                );
            }
            Ok(_) | Err(_) => {
                checks.insert(
                    "solana_cli".to_string(),
                    serde_json::json!({"available": false}),
                );
            }
        }

        // Keypair
        let kp_exists = self
            .keypair_path
            .as_ref()
            .map(|p| p.exists())
            .unwrap_or(false);
        checks.insert(
            "keypair".to_string(),
            serde_json::json!({
                "configured": self.keypair_path.is_some(),
                "path": self.keypair_path.as_ref().map(|p| p.to_string_lossy().to_string()),
                "exists": kp_exists,
            }),
        );

        let spl_ok = checks
            .get("spl_token_cli")
            .and_then(|v| v.get("available"))
            .and_then(|v| v.as_bool())
            .unwrap_or(false);
        let sol_ok = checks
            .get("solana_cli")
            .and_then(|v| v.get("available"))
            .and_then(|v| v.as_bool())
            .unwrap_or(false);
        let all_ok = spl_ok && sol_ok && kp_exists;

        let mut data = HashMap::new();
        data.insert("ready".to_string(), serde_json::json!(all_ok));
        data.insert("checks".to_string(), serde_json::json!(checks));

        CliResult::success(data, "Prerequisites check complete".to_string())
    }
}

// -- Helper functions --------------------------------------------------------

/// Locate a CLI binary by name, searching PATH and common install dirs.
fn find_binary(name: &str) -> Result<PathBuf, ConfidentialError> {
    // Check PATH first
    if let Ok(path) = which::which(name) {
        return Ok(path);
    }

    // Common install locations
    let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
    let candidates = [
        home.join(".cargo/bin").join(name),
        home.join(".local/share/solana/install/active_release/bin")
            .join(name),
        PathBuf::from("/opt/homebrew/bin").join(name),
    ];

    for candidate in &candidates {
        if candidate.exists() {
            return Ok(candidate.clone());
        }
    }

    let install_hint = if name == "spl-token" {
        "cargo install spl-token-cli".to_string()
    } else {
        "sh -c \"$(curl -sSfL https://release.anza.xyz/stable/install)\"".to_string()
    };

    Err(ConfidentialError::BinaryNotFound {
        binary: name.to_string(),
        install_hint,
    })
}

/// Extract an address from CLI output by searching for a keyword-containing line.
fn parse_address_from_output(output: &str, keyword: &str) -> Option<String> {
    let keyword_lower = keyword.to_lowercase();
    for line in output.lines() {
        if line.to_lowercase().contains(&keyword_lower) {
            for word in line.split_whitespace() {
                let cleaned = word.trim_matches(|c: char| ".,;:()'\"".contains(c));
                if (32..=44).contains(&cleaned.len()) && cleaned.chars().all(|c| c.is_alphanumeric())
                {
                    return Some(cleaned.to_string());
                }
            }
        }
    }
    None
}

/// Fallback: find the first base58-like address on any line.
fn parse_first_address(output: &str) -> Option<String> {
    for line in output.lines() {
        let trimmed = line.trim();
        if (32..=44).contains(&trimmed.len()) && trimmed.chars().all(|c| c.is_alphanumeric()) {
            return Some(trimmed.to_string());
        }
    }
    None
}

/// Extract a transaction signature from CLI output.
fn parse_signature(output: &str) -> Option<String> {
    for line in output.lines() {
        if line.contains("Signature:") {
            return line.split("Signature:").last().map(|s| s.trim().to_string());
        }
        let trimmed = line.trim();
        if (80..=90).contains(&trimmed.len()) && trimmed.chars().all(|c| c.is_alphanumeric()) {
            return Some(trimmed.to_string());
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_address_from_creating_token() {
        let output = "Creating token AbCdEfGh1234567890AbCdEfGh1234567890Ab1234 under program TokenzQd...";
        let addr = parse_address_from_output(output, "Creating token");
        assert!(addr.is_some());
        let parsed = addr.unwrap();
        assert!((32..=44).contains(&parsed.len()));
    }

    #[test]
    fn parse_address_fallback() {
        let output = "AbCdEfGh1234567890AbCdEfGh1234567890Ab12\nSome other line";
        let addr = parse_first_address(output);
        assert!(addr.is_some());
    }

    #[test]
    fn parse_signature_from_output() {
        let output = "Signature: 5abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567abc890def123ghi456jkl789mno01";
        let sig = parse_signature(output);
        assert!(sig.is_some());
    }

    #[test]
    fn cli_result_success() {
        let mut data = HashMap::new();
        data.insert("key".to_string(), serde_json::json!("value"));
        let result = CliResult::success(data, "ok".to_string());
        assert!(result.success);
        assert!(result.error.is_none());
    }

    #[test]
    fn cli_result_failure() {
        let result = CliResult::failure("something broke".to_string());
        assert!(!result.success);
        assert_eq!(result.error.as_deref(), Some("something broke"));
    }
}
