//! Block commands.

use anyhow::Result;

use crate::client::RpcClient;
use crate::output::{self, OutputFormat};

/// Get block by hash or blue score.
///
/// In a DAG blockchain, there is no strict height→hash mapping.
/// When a number is provided, it's interpreted as a blue score.
pub async fn get_block(client: &RpcClient, id: &str, format: OutputFormat) -> Result<()> {
    // Try to parse as blue score first
    let block = if id.chars().all(|c| c.is_ascii_digit()) {
        let blue_score: u64 = id.parse()?;
        let blocks = client.get_blocks_by_blue_score(blue_score, true).await?;
        if blocks.is_empty() {
            anyhow::bail!(
                "No block found at blue score {}. Note: In a DAG, use block hashes for precise lookups.",
                blue_score
            );
        }
        // Return the first block at this blue score
        // (there could be multiple in case of parallel blocks)
        blocks.into_iter().next().unwrap()
    } else {
        client.get_block(id, true).await?
    };

    match format {
        OutputFormat::Json => {
            output::print_value(&block, format);
        }
        OutputFormat::Text => {
            output::print_header("Block");
            output::print_kv("Hash", &block.hash);
            output::print_kv("Version", &block.header.version.to_string());
            output::print_kv(
                "Timestamp",
                &output::format_timestamp(block.header.timestamp),
            );
            output::print_kv("Blue Score", &block.header.blue_score.to_string());
            output::print_kv("Bits", &format!("0x{:08x}", block.header.bits));
            output::print_kv("Nonce", &block.header.nonce.to_string());
            output::print_kv("Parents", &block.header.parents.len().to_string());
            output::print_kv("Transactions", &block.transactions.len().to_string());

            if !block.header.parents.is_empty() {
                println!("\nParents:");
                for parent in &block.header.parents {
                    println!("  {}", parent);
                }
            }

            if !block.transactions.is_empty() {
                println!("\nTransactions:");
                for tx in &block.transactions {
                    println!(
                        "  {} - {} SYNOR",
                        output::format_hash(&tx.hash),
                        output::format_synor(tx.fee)
                    );
                }
            }
        }
    }

    Ok(())
}

/// Get latest blocks.
pub async fn get_blocks(client: &RpcClient, count: usize, format: OutputFormat) -> Result<()> {
    // Get tips first
    let tips = client.get_tips().await?;

    // Get blocks starting from tips
    let mut blocks = Vec::new();
    for tip in tips.iter().take(count.min(tips.len())) {
        if let Ok(block) = client.get_block(tip, false).await {
            blocks.push(block);
        }
    }

    match format {
        OutputFormat::Json => {
            output::print_value(&blocks, format);
        }
        OutputFormat::Text => {
            output::print_header(&format!("Latest Blocks ({})", blocks.len()));

            let headers = vec!["Hash", "Time", "Blue Score", "Txs"];
            let rows: Vec<Vec<String>> = blocks
                .iter()
                .map(|b| {
                    vec![
                        output::format_hash(&b.hash),
                        output::format_timestamp(b.header.timestamp),
                        b.header.blue_score.to_string(),
                        b.transactions.len().to_string(),
                    ]
                })
                .collect();

            output::print_table(headers, rows);
        }
    }

    Ok(())
}

/// Get current tips.
pub async fn get_tips(client: &RpcClient, format: OutputFormat) -> Result<()> {
    let tips = client.get_tips().await?;

    match format {
        OutputFormat::Json => {
            output::print_value(&tips, format);
        }
        OutputFormat::Text => {
            output::print_header(&format!("DAG Tips ({})", tips.len()));
            for tip in &tips {
                println!("  {}", tip);
            }
        }
    }

    Ok(())
}

/// Get block count.
pub async fn get_block_count(client: &RpcClient, format: OutputFormat) -> Result<()> {
    let count = client.get_block_count().await?;
    let blue_score = client.get_blue_score().await?;

    match format {
        OutputFormat::Json => {
            let result = serde_json::json!({
                "block_count": count,
                "blue_score": blue_score,
            });
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
        OutputFormat::Text => {
            output::print_kv("Block Count", &count.to_string());
            output::print_kv("Blue Score", &blue_score.to_string());
        }
    }

    Ok(())
}
