//! Contract commands.

use std::fs;
use std::path::PathBuf;

use anyhow::Result;

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

/// Handle contract commands.
pub async fn handle(
    client: &RpcClient,
    _config: &CliConfig,
    cmd: ContractCommands,
    format: OutputFormat,
) -> Result<()> {
    match cmd {
        ContractCommands::Deploy {
            wasm,
            deployer,
            args,
            gas,
        } => deploy(client, wasm, &deployer, args.as_deref(), Some(gas), format).await,
        ContractCommands::Call {
            contract_id,
            method,
            caller,
            args,
            value,
            gas,
        } => {
            call(
                client,
                &contract_id,
                &method,
                &caller,
                args.as_deref(),
                value,
                Some(gas),
                format,
            )
            .await
        }
        ContractCommands::Code { contract_id } => code(client, &contract_id, format).await,
        ContractCommands::Storage { contract_id, key } => {
            storage(client, &contract_id, &key, format).await
        }
        ContractCommands::EstimateGas {
            contract_id,
            method,
            caller,
            args,
            value,
        } => {
            estimate_gas(
                client,
                &contract_id,
                &method,
                &caller,
                args.as_deref(),
                value,
                format,
            )
            .await
        }
        ContractCommands::Info { contract_id } => info(client, &contract_id, format).await,
    }
}

/// Deploy a contract.
async fn deploy(
    client: &RpcClient,
    wasm_path: PathBuf,
    deployer: &str,
    args: Option<&str>,
    gas_limit: Option<u64>,
    format: OutputFormat,
) -> Result<()> {
    // Read WASM file
    let wasm_bytes = fs::read(&wasm_path)?;
    let wasm_hex = hex::encode(&wasm_bytes);

    output::print_info(&format!(
        "Deploying contract ({} bytes)...",
        wasm_bytes.len()
    ));

    let args_hex = args.unwrap_or("");

    let spinner = output::create_spinner("Deploying contract...");

    let result = client
        .deploy_contract(&wasm_hex, args_hex, deployer, gas_limit)
        .await?;

    spinner.finish_and_clear();

    if let Some(error) = &result.error {
        match format {
            OutputFormat::Json => {
                println!(
                    "{}",
                    serde_json::to_string_pretty(&serde_json::json!({
                        "success": false,
                        "error": error
                    }))?
                );
            }
            OutputFormat::Text => {
                output::print_error(&format!("Deployment failed: {}", error));
            }
        }
        return Ok(());
    }

    match format {
        OutputFormat::Json => {
            println!(
                "{}",
                serde_json::to_string_pretty(&serde_json::json!({
                    "success": true,
                    "contractId": result.contract_id,
                    "address": result.address,
                    "gasUsed": result.gas_used,
                }))?
            );
        }
        OutputFormat::Text => {
            output::print_success("Contract deployed!");
            output::print_kv("Contract ID", &result.contract_id);
            output::print_kv("Address", &result.address);
            output::print_kv("Gas Used", &result.gas_used.to_string());
        }
    }

    Ok(())
}

/// Call a contract method.
#[allow(clippy::too_many_arguments)]
async fn call(
    client: &RpcClient,
    contract_id: &str,
    method: &str,
    caller: &str,
    args: Option<&str>,
    value: u64,
    gas_limit: Option<u64>,
    format: OutputFormat,
) -> Result<()> {
    let args_hex = args.unwrap_or("");

    let result = client
        .call_contract(contract_id, method, args_hex, caller, value, gas_limit)
        .await?;

    if let Some(error) = &result.error {
        match format {
            OutputFormat::Json => {
                output::print_value(
                    &serde_json::json!({
                        "success": false,
                        "error": error
                    }),
                    format,
                );
            }
            OutputFormat::Text => {
                output::print_error(&format!("Contract call failed: {}", error));
            }
        }
        return Ok(());
    }

    match format {
        OutputFormat::Json => {
            output::print_value(
                &serde_json::json!({
                    "success": result.success,
                    "data": result.data,
                    "gasUsed": result.gas_used,
                    "logs": result.logs
                }),
                format,
            );
        }
        OutputFormat::Text => {
            output::print_header("Contract Call Result");
            output::print_kv("Success", if result.success { "Yes" } else { "No" });
            output::print_kv("Gas Used", &result.gas_used.to_string());

            if !result.data.is_empty() {
                output::print_kv("Return Data", &result.data);
            }

            if !result.logs.is_empty() {
                println!("\nLogs:");
                for (i, log) in result.logs.iter().enumerate() {
                    println!("  [{}] Contract: {}", i, log.contract_id);
                    for topic in &log.topics {
                        println!("      Topic: {}", topic);
                    }
                    if !log.data.is_empty() {
                        println!("      Data: {}", log.data);
                    }
                }
            }
        }
    }

    Ok(())
}

/// Get contract code.
async fn code(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Result<()> {
    let result = client.get_contract_code(contract_id).await?;

    if let Some(error) = &result.error {
        match format {
            OutputFormat::Json => {
                println!(
                    "{}",
                    serde_json::to_string_pretty(&serde_json::json!({
                        "error": error
                    }))?
                );
            }
            OutputFormat::Text => {
                output::print_error(&format!("Failed to get code: {}", error));
            }
        }
        return Ok(());
    }

    let code_hex = result.code.unwrap_or_default();

    match format {
        OutputFormat::Json => {
            let result = serde_json::json!({
                "contractId": contract_id,
                "code": code_hex,
                "size": code_hex.len() / 2,
            });
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
        OutputFormat::Text => {
            output::print_kv("Contract ID", contract_id);
            output::print_kv("Size", &format!("{} bytes", code_hex.len() / 2));

            if code_hex.len() <= 256 {
                println!("\nCode: {}", code_hex);
            } else {
                println!("\nCode (truncated): {}...", &code_hex[..256]);
            }
        }
    }

    Ok(())
}

/// Get contract storage.
async fn storage(
    client: &RpcClient,
    contract_id: &str,
    key: &str,
    format: OutputFormat,
) -> Result<()> {
    let result = client.get_contract_storage(contract_id, key).await?;

    if let Some(error) = &result.error {
        match format {
            OutputFormat::Json => {
                println!(
                    "{}",
                    serde_json::to_string_pretty(&serde_json::json!({
                        "error": error
                    }))?
                );
            }
            OutputFormat::Text => {
                output::print_error(&format!("Failed to get storage: {}", error));
            }
        }
        return Ok(());
    }

    let value = result.value.unwrap_or_else(|| "null".to_string());

    match format {
        OutputFormat::Json => {
            let result = serde_json::json!({
                "contractId": contract_id,
                "key": key,
                "value": value,
            });
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
        OutputFormat::Text => {
            output::print_kv("Contract ID", contract_id);
            output::print_kv("Key", key);
            output::print_kv("Value", &value);
        }
    }

    Ok(())
}

/// Estimate gas for a call.
async fn estimate_gas(
    client: &RpcClient,
    contract_id: &str,
    method: &str,
    caller: &str,
    args: Option<&str>,
    value: u64,
    format: OutputFormat,
) -> Result<()> {
    let args_hex = args.unwrap_or("");

    let result = client
        .estimate_gas(contract_id, method, args_hex, caller, value)
        .await?;

    if let Some(error) = &result.error {
        match format {
            OutputFormat::Json => {
                println!(
                    "{}",
                    serde_json::to_string_pretty(&serde_json::json!({
                        "error": error
                    }))?
                );
            }
            OutputFormat::Text => {
                output::print_error(&format!("Failed to estimate gas: {}", error));
            }
        }
        return Ok(());
    }

    match format {
        OutputFormat::Json => {
            let result = serde_json::json!({
                "contractId": contract_id,
                "method": method,
                "estimatedGas": result.estimated_gas,
            });
            println!("{}", serde_json::to_string_pretty(&result)?);
        }
        OutputFormat::Text => {
            output::print_kv("Contract ID", contract_id);
            output::print_kv("Method", method);
            output::print_kv("Estimated Gas", &result.estimated_gas.to_string());
        }
    }

    Ok(())
}

/// Get contract metadata.
async fn info(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Result<()> {
    let result = client.get_contract(contract_id).await?;

    if let Some(error) = &result.error {
        match format {
            OutputFormat::Json => {
                println!(
                    "{}",
                    serde_json::to_string_pretty(&serde_json::json!({
                        "error": error
                    }))?
                );
            }
            OutputFormat::Text => {
                output::print_error(&format!("Failed to get contract info: {}", error));
            }
        }
        return Ok(());
    }

    match format {
        OutputFormat::Json => {
            println!(
                "{}",
                serde_json::to_string_pretty(&serde_json::json!({
                    "contractId": contract_id,
                    "codeHash": result.code_hash,
                    "deployer": result.deployer,
                    "deployedAt": result.deployed_at,
                    "deployedHeight": result.deployed_height,
                }))?
            );
        }
        OutputFormat::Text => {
            output::print_header("Contract Info");
            output::print_kv("Contract ID", contract_id);
            if let Some(hash) = &result.code_hash {
                output::print_kv("Code Hash", hash);
            }
            if let Some(deployer) = &result.deployer {
                output::print_kv("Deployer", deployer);
            }
            if let Some(time) = result.deployed_at {
                output::print_kv("Deployed At", &format!("{}", time));
            }
            if let Some(height) = result.deployed_height {
                output::print_kv("Deployed Height", &format!("{}", height));
            }
        }
    }

    Ok(())
}
