//! Transaction commands.

use anyhow::Result;
use dialoguer::Password;
use synor_types::{
    transaction::{Outpoint, ScriptPubKey, ScriptType, Transaction, TxInput, TxOutput},
    Address, Amount, Hash256,
};

use crate::client::RpcClient;
use crate::config::CliConfig;
use crate::output::{self, OutputFormat};
use crate::wallet::Wallet;

/// Get transaction by hash.
pub async fn get_tx(client: &RpcClient, hash: &str, format: OutputFormat) -> Result<()> {
    let tx = client.get_transaction(hash).await?;

    match format {
        OutputFormat::Json => {
            output::print_value(&tx, format);
        }
        OutputFormat::Text => {
            output::print_header("Transaction");
            output::print_kv("Hash", &tx.hash);
            output::print_kv("Mass", &tx.mass.to_string());
            output::print_kv("Fee", &output::format_synor(tx.fee));
            output::print_kv("Inputs", &tx.inputs.len().to_string());
            output::print_kv("Outputs", &tx.outputs.len().to_string());

            if !tx.inputs.is_empty() {
                println!("\nInputs:");
                for (i, input) in tx.inputs.iter().enumerate() {
                    println!(
                        "  [{}] {}:{}",
                        i,
                        output::format_hash(&input.previous_outpoint.transaction_id),
                        input.previous_outpoint.index
                    );
                }
            }

            if !tx.outputs.is_empty() {
                println!("\nOutputs:");
                for (i, output_item) in tx.outputs.iter().enumerate() {
                    println!(
                        "  [{}] {} -> {}",
                        i,
                        output::format_synor(output_item.value),
                        output::format_hash(&output_item.script_public_key)
                    );
                }
            }
        }
    }

    Ok(())
}

/// Send a transaction.
pub async fn send(
    client: &RpcClient,
    config: &CliConfig,
    to: &str,
    amount: &str,
    fee: Option<&str>,
    format: OutputFormat,
) -> Result<()> {
    // Parse amount
    let amount_sompi = parse_synor(amount)?;

    // Get fee
    let fee_sompi = if let Some(f) = fee {
        parse_synor(f)?
    } else {
        client.estimate_fee("normal").await?
    };

    // Load wallet
    let wallet_path = config.default_wallet_path();
    if !wallet_path.exists() {
        anyhow::bail!("No wallet found. Create one with: synor wallet create");
    }
    let wallet = Wallet::load(&wallet_path)?;

    // Get password for signing
    let password: String = Password::new()
        .with_prompt("Enter wallet password")
        .interact()?;

    // Unlock wallet
    wallet.unlock(&password)?;

    // Get sender address
    let from_addr = wallet
        .default_address()
        .ok_or_else(|| anyhow::anyhow!("No address in wallet"))?;

    // Get UTXOs
    let utxos = client.get_utxos(&from_addr.address).await?;

    // Select UTXOs
    let total_needed = amount_sompi + fee_sompi;
    let mut selected_amount = 0u64;
    let mut selected_utxos = Vec::new();

    for utxo in utxos {
        if selected_amount >= total_needed {
            break;
        }
        selected_amount += utxo.amount;
        selected_utxos.push(utxo);
    }

    if selected_amount < total_needed {
        anyhow::bail!(
            "Insufficient funds: have {}, need {}",
            output::format_synor(selected_amount),
            output::format_synor(total_needed)
        );
    }

    // Build transaction
    let change = selected_amount - total_needed;
    let tx_hex = build_transaction(
        &wallet,
        &selected_utxos,
        to,
        amount_sompi,
        &from_addr.address,
        change,
        &password,
    )?;

    // Submit transaction
    let tx_hash = client.submit_transaction(&tx_hex).await?;

    match format {
        OutputFormat::Json => {
            let result = serde_json::json!({
                "hash": tx_hash,
                "from": from_addr.address,
                "to": to,
                "amount": amount_sompi,
                "fee": fee_sompi,
            });
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
        OutputFormat::Text => {
            output::print_success("Transaction sent!");
            output::print_kv("Hash", &tx_hash);
            output::print_kv("From", &from_addr.address);
            output::print_kv("To", to);
            output::print_kv("Amount", &output::format_synor(amount_sompi));
            output::print_kv("Fee", &output::format_synor(fee_sompi));
        }
    }

    Ok(())
}

/// Get mempool entries.
pub async fn mempool(client: &RpcClient, verbose: bool, format: OutputFormat) -> Result<()> {
    let entries = client.get_mempool().await?;

    match format {
        OutputFormat::Json => {
            output::print_value(&entries, format);
        }
        OutputFormat::Text => {
            if entries.is_empty() {
                output::print_info("Mempool is empty");
                return Ok(());
            }

            output::print_header(&format!("Mempool ({} transactions)", entries.len()));

            if verbose {
                let headers = vec!["Hash", "Fee", "Mass", "Age"];
                let now = std::time::SystemTime::now()
                    .duration_since(std::time::UNIX_EPOCH)
                    .unwrap()
                    .as_millis() as u64;

                let rows: Vec<Vec<String>> = entries
                    .iter()
                    .map(|e| {
                        let age = (now - e.timestamp) / 1000;
                        vec![
                            output::format_hash(&e.hash),
                            output::format_synor(e.fee),
                            e.mass.to_string(),
                            output::format_duration(age),
                        ]
                    })
                    .collect();

                output::print_table(headers, rows);
            } else {
                for entry in &entries {
                    println!("  {}", entry.hash);
                }
            }
        }
    }

    Ok(())
}

// ==================== Helpers ====================

fn parse_synor(s: &str) -> Result<u64> {
    let s = s.trim().to_uppercase();
    let s = s.strip_suffix("SYNOR").unwrap_or(&s).trim();
    let synor: f64 = s.parse()?;
    let sompi = (synor * 100_000_000.0) as u64;
    Ok(sompi)
}

fn build_transaction(
    wallet: &Wallet,
    utxos: &[crate::client::Utxo],
    to: &str,
    amount: u64,
    change_addr: &str,
    change: u64,
    password: &str,
) -> Result<String> {
    // Parse destination address
    let to_address =
        Address::parse(to).map_err(|e| anyhow::anyhow!("Invalid destination address: {}", e))?;

    // Parse change address
    let change_address = Address::parse(change_addr)
        .map_err(|e| anyhow::anyhow!("Invalid change address: {}", e))?;

    // Build inputs (initially with empty signature scripts)
    let mut inputs = Vec::with_capacity(utxos.len());
    for utxo in utxos {
        let txid_bytes = hex::decode(&utxo.outpoint.transaction_id)?;
        if txid_bytes.len() != 32 {
            anyhow::bail!("Invalid transaction ID length: expected 32 bytes");
        }
        let mut txid_array = [0u8; 32];
        txid_array.copy_from_slice(&txid_bytes);

        let outpoint = Outpoint::new(Hash256::from_bytes(txid_array), utxo.outpoint.index);

        // Empty signature script initially - will be filled after signing
        inputs.push(TxInput::new(outpoint, Vec::new()));
    }

    // Build outputs
    let mut outputs = Vec::new();

    // Recipient output - determine script type from address type
    let recipient_script = create_script_pubkey(&to_address);
    outputs.push(TxOutput::new(Amount::from_sompi(amount), recipient_script));

    // Change output (if any)
    if change > 0 {
        let change_script = create_script_pubkey(&change_address);
        outputs.push(TxOutput::new(Amount::from_sompi(change), change_script));
    }

    // Build unsigned transaction
    let mut tx = Transaction::new(inputs, outputs);

    // Compute sighash (hash of transaction with empty signature scripts)
    let sighash = tx.sighash();
    let sighash_bytes = sighash.as_bytes();

    // Sign the sighash with the wallet
    let from_addr = wallet
        .default_address()
        .ok_or_else(|| anyhow::anyhow!("No default address in wallet"))?;

    let signature = wallet.sign_transaction(&from_addr.address, sighash_bytes, password)?;
    let signature_bytes = signature.to_bytes();

    // Put signature in each input's signature_script
    // The signature format is: [ed25519_sig (64 bytes)][dilithium_sig (~2420 bytes)]
    for input in &mut tx.inputs {
        input.signature_script = signature_bytes.clone();
    }

    // Serialize the signed transaction with borsh
    let tx_bytes = borsh::to_vec(&tx)
        .map_err(|e| anyhow::anyhow!("Failed to serialize transaction: {}", e))?;

    Ok(hex::encode(&tx_bytes))
}

/// Creates a ScriptPubKey from an address.
fn create_script_pubkey(address: &Address) -> ScriptPubKey {
    use synor_types::address::AddressType;

    let script_type = match address.addr_type() {
        AddressType::P2PKH => ScriptType::P2PKH,
        AddressType::P2pkhPqc => ScriptType::P2pkhPqc,
        AddressType::P2SH => ScriptType::P2SH,
        AddressType::P2shPqc => ScriptType::P2shPqc,
    };

    ScriptPubKey {
        script_type,
        data: address.payload().to_vec(),
    }
}
