use core::time::Duration;
use ibc_relayer::chain::handle::ChainHandle;
use tracing::info;
use crate::bootstrap::binary::connection::{bootstrap_connection, BootstrapConnectionOptions};
use crate::error::Error;
use crate::framework::base::HasOverrides;
use crate::framework::base::TestConfigOverride;
use crate::framework::binary::chain::{
    BinaryChainTest, ClientOptionsOverride, RelayerConfigOverride, RunBinaryChainTest,
};
use crate::framework::binary::node::{
    run_binary_node_test, NodeConfigOverride, NodeGenesisOverride,
};
use crate::framework::supervisor::{RunWithSupervisor, SupervisorOverride};
use crate::relayer::driver::RelayerDriver;
use crate::types::binary::chains::ConnectedChains;
use crate::types::binary::connection::ConnectedConnection;
use crate::types::config::TestConfig;
use crate::types::env::write_env;
use crate::util::suspend::hang_on_error;
pub fn run_two_way_binary_connection_test<Test, Overrides>(test: &Test) -> Result<(), Error>
where
    Test: BinaryConnectionTest,
    Test: HasOverrides<Overrides = Overrides>,
    Overrides: TestConfigOverride
        + NodeConfigOverride
        + NodeGenesisOverride
        + RelayerConfigOverride
        + ClientOptionsOverride
        + SupervisorOverride
        + ConnectionDelayOverride,
{
    run_binary_connection_test(&RunTwoWayBinaryConnectionTest::new(test))
}
pub fn run_binary_connection_test<Test, Overrides>(test: &Test) -> Result<(), Error>
where
    Test: BinaryConnectionTest,
    Test: HasOverrides<Overrides = Overrides>,
    Overrides: TestConfigOverride
        + NodeConfigOverride
        + NodeGenesisOverride
        + RelayerConfigOverride
        + ClientOptionsOverride
        + SupervisorOverride
        + ConnectionDelayOverride,
{
    run_binary_node_test(&RunBinaryChainTest::new(&RunBinaryConnectionTest::new(
        &RunWithSupervisor::new(test),
    )))
}
pub trait BinaryConnectionTest {
    fn run<ChainA: ChainHandle, ChainB: ChainHandle>(
        &self,
        config: &TestConfig,
        relayer: RelayerDriver,
        chains: ConnectedChains<ChainA, ChainB>,
        connection: ConnectedConnection<ChainA, ChainB>,
    ) -> Result<(), Error>;
}
pub trait ConnectionDelayOverride {
    fn connection_delay(&self) -> Duration;
}
pub struct RunBinaryConnectionTest<'a, Test> {
    pub test: &'a Test,
}
pub struct RunTwoWayBinaryConnectionTest<'a, Test> {
    pub test: &'a Test,
}
impl<'a, Test> RunBinaryConnectionTest<'a, Test>
where
    Test: BinaryConnectionTest,
{
    pub fn new(test: &'a Test) -> Self {
        Self { test }
    }
}
impl<'a, Test> RunTwoWayBinaryConnectionTest<'a, Test>
where
    Test: BinaryConnectionTest,
{
    pub fn new(test: &'a Test) -> Self {
        Self { test }
    }
}
impl<'a, Test, Overrides> BinaryChainTest for RunBinaryConnectionTest<'a, Test>
where
    Test: BinaryConnectionTest,
    Test: HasOverrides<Overrides = Overrides>,
    Overrides: ConnectionDelayOverride,
{
    fn run<ChainA: ChainHandle, ChainB: ChainHandle>(
        &self,
        config: &TestConfig,
        relayer: RelayerDriver,
        chains: ConnectedChains<ChainA, ChainB>,
    ) -> Result<(), Error> {
        let bootstrap_options = BootstrapConnectionOptions::default()
            .connection_delay(self.get_overrides().connection_delay())
            .bootstrap_with_random_ids(config.bootstrap_with_random_ids);
        let connection = bootstrap_connection(&chains.foreign_clients, bootstrap_options)?;
        let env_path = config.chain_store_dir.join("binary-connections.env");
        write_env(&env_path, &(&chains, &connection))?;
        info!("written connection environment to {}", env_path.display());
        self.test.run(config, relayer, chains, connection)?;
        Ok(())
    }
}
impl<'a, Test: BinaryConnectionTest> BinaryConnectionTest
    for RunTwoWayBinaryConnectionTest<'a, Test>
{
    fn run<ChainA: ChainHandle, ChainB: ChainHandle>(
        &self,
        config: &TestConfig,
        relayer: RelayerDriver,
        chains: ConnectedChains<ChainA, ChainB>,
        connection: ConnectedConnection<ChainA, ChainB>,
    ) -> Result<(), Error> {
        info!(
            "running two-way connection test, from {}/{} to {}/{}",
            chains.chain_id_a(),
            connection.connection_id_a,
            chains.chain_id_b(),
            connection.connection_id_b,
        );
        self.test
            .run(config, relayer.clone(), chains.clone(), connection.clone())?;
        info!(
            "running two-way connection test in the opposite direction, from {}/{} to {}/{}",
            chains.chain_id_b(),
            connection.connection_id_b,
            chains.chain_id_a(),
            connection.connection_id_a,
        );
        let chains = chains.flip();
        let connection = connection.flip();
        self.test.run(config, relayer, chains, connection)?;
        Ok(())
    }
}
impl<'a, Test, Overrides> BinaryConnectionTest for RunWithSupervisor<'a, Test>
where
    Test: BinaryConnectionTest,
    Test: HasOverrides<Overrides = Overrides>,
    Overrides: SupervisorOverride,
{
    fn run<ChainA: ChainHandle, ChainB: ChainHandle>(
        &self,
        config: &TestConfig,
        relayer: RelayerDriver,
        chains: ConnectedChains<ChainA, ChainB>,
        connection: ConnectedConnection<ChainA, ChainB>,
    ) -> Result<(), Error> {
        if self.get_overrides().should_spawn_supervisor() {
            relayer
                .clone()
                .with_supervisor(|| self.test.run(config, relayer, chains, connection))
        } else {
            hang_on_error(config.hang_on_fail, || {
                self.test.run(config, relayer, chains, connection)
            })
        }
    }
}
impl<'a, Test, Overrides> HasOverrides for RunBinaryConnectionTest<'a, Test>
where
    Test: HasOverrides<Overrides = Overrides>,
{
    type Overrides = Overrides;
    fn get_overrides(&self) -> &Self::Overrides {
        self.test.get_overrides()
    }
}
impl<'a, Test, Overrides> HasOverrides for RunTwoWayBinaryConnectionTest<'a, Test>
where
    Test: HasOverrides<Overrides = Overrides>,
{
    type Overrides = Overrides;
    fn get_overrides(&self) -> &Self::Overrides {
        self.test.get_overrides()
    }
}