use eyre::Report as Error;
use ibc_relayer::chain::handle::{ChainHandle, CountingAndCachingChainHandle};
use ibc_relayer::config::Config;
use ibc_relayer::error::ErrorDetail as RelayerErrorDetail;
use ibc_relayer::foreign_client::{
    extract_client_id, CreateOptions as ClientOptions, ForeignClient,
};
use ibc_relayer::keyring::errors::ErrorDetail as KeyringErrorDetail;
use ibc_relayer::registry::SharedRegistry;
use ibc_relayer_types::core::ics24_host::identifier::ClientId;
use std::fs;
use std::path::Path;
use tracing::{debug, info};
use crate::relayer::driver::RelayerDriver;
use crate::types::binary::chains::ConnectedChains;
use crate::types::binary::foreign_client::ForeignClientPair;
use crate::types::config::TestConfig;
use crate::types::single::node::FullNode;
use crate::types::tagged::*;
use crate::types::wallet::{TestWallets, Wallet};
use crate::util::random::random_u64_range;
#[derive(Default)]
pub struct BootstrapClientOptions {
    pub client_options_a_to_b: ClientOptions,
    pub client_options_b_to_a: ClientOptions,
    pub pad_client_id_a_to_b: u64,
    pub pad_client_id_b_to_a: u64,
}
pub fn bootstrap_chains_with_full_nodes(
    test_config: &TestConfig,
    node_a: FullNode,
    node_b: FullNode,
    options: BootstrapClientOptions,
    config_modifier: impl FnOnce(&mut Config),
) -> Result<
    (
        RelayerDriver,
        ConnectedChains<impl ChainHandle, impl ChainHandle>,
    ),
    Error,
> {
    let mut config = Config::default();
    add_chain_config(&mut config, &node_a, test_config)?;
    add_chain_config(&mut config, &node_b, test_config)?;
    config_modifier(&mut config);
    let config_path = test_config.chain_store_dir.join("relayer-config.toml");
    save_relayer_config(&config, &config_path)?;
    let registry = new_registry(config.clone());
    let handle_a = spawn_chain_handle(|| {}, ®istry, &node_a)?;
    let handle_b = spawn_chain_handle(|| {}, ®istry, &node_b)?;
    pad_client_ids(&handle_a, &handle_b, options.pad_client_id_a_to_b)?;
    pad_client_ids(&handle_b, &handle_a, options.pad_client_id_b_to_a)?;
    let foreign_clients = bootstrap_foreign_client_pair(&handle_a, &handle_b, options)?;
    let relayer = RelayerDriver {
        config_path,
        config,
        registry,
        hang_on_fail: test_config.hang_on_fail,
    };
    let chains = ConnectedChains::new(
        handle_a,
        handle_b,
        MonoTagged::new(node_a),
        MonoTagged::new(node_b),
        foreign_clients,
    );
    Ok((relayer, chains))
}
pub fn bootstrap_foreign_client_pair<ChainA: ChainHandle, ChainB: ChainHandle>(
    chain_a: &ChainA,
    chain_b: &ChainB,
    options: BootstrapClientOptions,
) -> Result<ForeignClientPair<ChainA, ChainB>, Error> {
    let client_a_to_b = bootstrap_foreign_client(chain_a, chain_b, options.client_options_a_to_b)?;
    let client_b_to_a = bootstrap_foreign_client(chain_b, chain_a, options.client_options_b_to_a)?;
    Ok(ForeignClientPair::new(client_a_to_b, client_b_to_a))
}
pub fn bootstrap_foreign_client<ChainA: ChainHandle, ChainB: ChainHandle>(
    chain_a: &ChainA,
    chain_b: &ChainB,
    client_options: ClientOptions,
) -> Result<ForeignClient<ChainB, ChainA>, Error> {
    let foreign_client =
        ForeignClient::restore(ClientId::default(), chain_b.clone(), chain_a.clone());
    let event = foreign_client.build_create_client_and_send(client_options)?;
    let client_id = extract_client_id(&event.event)?.clone();
    info!(
        "created foreign client from chain {} to chain {} with client id {} on chain {}",
        chain_a.id(),
        chain_b.id(),
        client_id,
        chain_b.id()
    );
    Ok(ForeignClient::restore(
        client_id,
        chain_b.clone(),
        chain_a.clone(),
    ))
}
pub fn pad_client_ids<ChainA: ChainHandle, ChainB: ChainHandle>(
    chain_a: &ChainA,
    chain_b: &ChainB,
    pad_count: u64,
) -> Result<(), Error> {
    let foreign_client =
        ForeignClient::restore(ClientId::default(), chain_b.clone(), chain_a.clone());
    for i in 0..pad_count {
        debug!("creating new client id {} on chain {}", i + 1, chain_b.id());
        foreign_client.build_create_client_and_send(Default::default())?;
    }
    Ok(())
}
pub fn spawn_chain_handle<Seed>(
    _: Seed,
    registry: &SharedRegistry<impl ChainHandle>,
    node: &FullNode,
) -> Result<impl ChainHandle, Error> {
    let chain_id = &node.chain_driver.chain_id;
    let handle = registry.get_or_spawn(chain_id)?;
    add_keys_to_chain_handle(&handle, &node.wallets)?;
    Ok(handle)
}
pub fn add_key_to_chain_handle<Chain: ChainHandle>(
    chain: &Chain,
    wallet: &Wallet,
) -> Result<(), Error> {
    let res = chain.add_key(wallet.id.0.clone(), wallet.key.clone().into());
    match res {
        Err(e) => match e.detail() {
            RelayerErrorDetail::KeyBase(e2) => match e2.source {
                KeyringErrorDetail::KeyAlreadyExist(_) => Ok(()),
                _ => Err(e.into()),
            },
            _ => Err(e.into()),
        },
        Ok(()) => Ok(()),
    }
}
pub fn add_keys_to_chain_handle<Chain: ChainHandle>(
    chain: &Chain,
    wallets: &TestWallets,
) -> Result<(), Error> {
    add_key_to_chain_handle(chain, &wallets.relayer)?;
    add_key_to_chain_handle(chain, &wallets.user1)?;
    add_key_to_chain_handle(chain, &wallets.user2)?;
    Ok(())
}
pub fn new_registry(config: Config) -> SharedRegistry<CountingAndCachingChainHandle> {
    <SharedRegistry<CountingAndCachingChainHandle>>::new(config)
}
pub fn add_chain_config(
    config: &mut Config,
    running_node: &FullNode,
    test_config: &TestConfig,
) -> Result<(), Error> {
    let chain_config =
        running_node.generate_chain_config(&running_node.chain_driver.chain_type, test_config)?;
    config.chains.push(chain_config);
    Ok(())
}
pub fn save_relayer_config(config: &Config, config_path: &Path) -> Result<(), Error> {
    let config_str = toml::to_string_pretty(&config)?;
    fs::write(config_path, &config_str)?;
    info!(
        "written hermes config.toml to {}:\n{}",
        config_path.display(),
        config_str
    );
    Ok(())
}
impl BootstrapClientOptions {
    pub fn client_options_a_to_b(mut self, options: ClientOptions) -> Self {
        self.client_options_a_to_b = options;
        self
    }
    pub fn client_options_b_to_a(mut self, options: ClientOptions) -> Self {
        self.client_options_b_to_a = options;
        self
    }
    pub fn bootstrap_with_random_ids(mut self, bootstrap_with_random_ids: bool) -> Self {
        if bootstrap_with_random_ids {
            self.pad_client_id_b_to_a = random_u64_range(1, 6);
            self.pad_client_id_a_to_b = random_u64_range(1, 6);
        } else {
            self.pad_client_id_b_to_a = 0;
            self.pad_client_id_a_to_b = 1;
        }
        self
    }
}