use core::time::Duration;
use eyre::eyre;
use std::sync::{Arc, RwLock};
use toml;
use tracing::info;
use crate::chain::builder::ChainBuilder;
use crate::chain::config;
use crate::chain::driver::ChainDriver;
use crate::chain::ext::bootstrap::ChainBootstrapMethodsExt;
use crate::error::Error;
use crate::ibc::denom::Denom;
use crate::ibc::token::Token;
use crate::types::single::node::FullNode;
use crate::types::wallet::{TestWallets, Wallet};
use crate::util::random::{random_u128_range, random_u32};
pub fn bootstrap_single_node(
    builder: &ChainBuilder,
    prefix: &str,
    use_random_id: bool,
    config_modifier: impl FnOnce(&mut toml::Value) -> Result<(), Error>,
    genesis_modifier: impl FnOnce(&mut serde_json::Value) -> Result<(), Error>,
    chain_number: usize,
) -> Result<FullNode, Error> {
    let native_token_number = chain_number % builder.native_tokens.len();
    let native_token = &builder.native_tokens[native_token_number];
    let native_denom = Denom::base(native_token);
    let denom = if use_random_id {
        Denom::base(&format!("coin{:x}", random_u32()))
    } else {
        Denom::base("samoleans")
    };
    let initial_amount = random_u128_range(1_000_000_000_000_000_000, 2_000_000_000_000_000_000);
    let initial_native_token = Token::new(native_denom, initial_amount);
    let additional_native_token = initial_native_token
        .clone()
        .checked_add(1_000_000_000_000u64)
        .ok_or(Error::generic(eyre!(
            "error creating initial {} with additional amount",
            native_token
        )))?;
    let initial_coin = Token::new(denom.clone(), initial_amount);
    let chain_driver = builder.new_chain(prefix, use_random_id, chain_number)?;
    chain_driver.initialize()?;
    chain_driver.update_genesis_file("genesis.json", genesis_modifier)?;
    let validator = add_wallet(&chain_driver, "validator", use_random_id)?;
    let relayer = add_wallet(&chain_driver, "relayer", use_random_id)?;
    let user1 = add_wallet(&chain_driver, "user1", use_random_id)?;
    let user2 = add_wallet(&chain_driver, "user2", use_random_id)?;
    chain_driver.add_genesis_account(&validator.address, &[&additional_native_token])?;
    chain_driver.add_genesis_validator(&validator.id, &initial_native_token)?;
    chain_driver.add_genesis_account(&user1.address, &[&initial_native_token, &initial_coin])?;
    chain_driver.add_genesis_account(&user2.address, &[&initial_native_token, &initial_coin])?;
    chain_driver.add_genesis_account(&relayer.address, &[&initial_native_token, &initial_coin])?;
    chain_driver.collect_gen_txs()?;
    let log_level = std::env::var("CHAIN_LOG_LEVEL").unwrap_or_else(|_| "info".to_string());
    chain_driver.update_chain_config("config.toml", |config| {
        config::set_log_level(config, &log_level)?;
        config::set_rpc_port(config, chain_driver.rpc_port)?;
        config::set_p2p_port(config, chain_driver.p2p_port)?;
        config::set_pprof_port(config, chain_driver.pprof_port)?;
        config::set_timeout_commit(config, Duration::from_secs(1))?;
        config::set_timeout_propose(config, Duration::from_secs(1))?;
        config::set_mode(config, "validator")?;
        config::set_indexer(config, "kv")?;
        config_modifier(config)?;
        Ok(())
    })?;
    let minimum_gas = format!("0{}", native_token);
    chain_driver.update_chain_config("app.toml", |config| {
        config::set_grpc_port(config, chain_driver.grpc_port)?;
        config::disable_grpc_web(config)?;
        config::disable_api(config)?;
        config::set_minimum_gas_price(config, &minimum_gas)?;
        Ok(())
    })?;
    let process = chain_driver.start()?;
    chain_driver.assert_eventual_wallet_amount(&relayer.address, &initial_coin)?;
    info!(
        "started new chain {} at with home path {} and RPC address {}.",
        chain_driver.chain_id,
        chain_driver.home_path,
        chain_driver.rpc_address(),
    );
    info!(
        "user wallet for chain {} - id: {}, address: {}",
        chain_driver.chain_id, user1.id.0, user1.address.0,
    );
    info!(
        "you can manually interact with the chain using commands starting with:\n{} --home '{}' --node {}",
        chain_driver.command_path,
        chain_driver.home_path,
        chain_driver.rpc_address(),
    );
    let wallets = TestWallets {
        validator,
        relayer,
        user1,
        user2,
    };
    let node = FullNode {
        chain_driver,
        denom,
        wallets,
        process: Arc::new(RwLock::new(process)),
    };
    Ok(node)
}
fn add_wallet(driver: &ChainDriver, prefix: &str, use_random_id: bool) -> Result<Wallet, Error> {
    if use_random_id {
        let num = random_u32();
        let wallet_id = format!("{prefix}-{num:x}");
        driver.add_wallet(&wallet_id)
    } else {
        driver.add_wallet(prefix)
    }
}