use crate::{Abstract, AbstractInterfaceError, IbcClient, IbcHost, VersionControl};
use abstract_std::{IBC_CLIENT, IBC_HOST};
use cw_orch::prelude::*;
pub struct AbstractIbc<Chain: CwEnv> {
    pub client: IbcClient<Chain>,
    pub host: IbcHost<Chain>,
}
impl<Chain: CwEnv> AbstractIbc<Chain> {
    pub fn new(chain: &Chain) -> Self {
        let ibc_client = IbcClient::new(IBC_CLIENT, chain.clone());
        let ibc_host = IbcHost::new(IBC_HOST, chain.clone());
        Self {
            client: ibc_client,
            host: ibc_host,
        }
    }
    pub fn upload(&self) -> Result<(), crate::AbstractInterfaceError> {
        self.client.upload()?;
        self.host.upload()?;
        Ok(())
    }
    pub fn instantiate(&self, abstr: &Abstract<Chain>, admin: &Addr) -> Result<(), CwOrchError> {
        self.client.instantiate(
            &abstract_std::ibc_client::InstantiateMsg {
                ans_host_address: abstr.ans_host.addr_str()?,
                version_control_address: abstr.version_control.addr_str()?,
            },
            Some(admin),
            None,
        )?;
        self.host.instantiate(
            &abstract_std::ibc_host::InstantiateMsg {
                ans_host_address: abstr.ans_host.addr_str()?,
                account_factory_address: abstr.account_factory.addr_str()?,
                version_control_address: abstr.version_control.addr_str()?,
            },
            Some(admin),
            None,
        )?;
        Ok(())
    }
    pub fn register(
        &self,
        version_control: &VersionControl<Chain>,
    ) -> Result<(), AbstractInterfaceError> {
        version_control.register_natives(vec![
            (
                self.client.as_instance(),
                ibc_client::contract::CONTRACT_VERSION.to_string(),
            ),
            (
                self.host.as_instance(),
                ibc_host::contract::CONTRACT_VERSION.to_string(),
            ),
        ])
    }
}
#[cfg(feature = "interchain")]
pub mod connection {
    use super::*;
    use abstract_std::ibc_client::ExecuteMsgFns as _;
    use abstract_std::ibc_client::QueryMsgFns;
    use abstract_std::ibc_host::ExecuteMsgFns as _;
    use abstract_std::objects::TruncatedChainId;
    use cw_orch::environment::Environment;
    use cw_orch_interchain::prelude::*;
    use cw_orch_polytone::interchain::PolytoneConnection;
    impl<Chain: IbcQueryHandler> Abstract<Chain> {
        pub fn connect_to<IBC: InterchainEnv<Chain>>(
            &self,
            remote_abstr: &Abstract<Chain>,
            interchain: &IBC,
        ) -> Result<(), AbstractInterfaceError> {
            connect_one_way_to(self, remote_abstr, interchain)?;
            connect_one_way_to(remote_abstr, self, interchain)?;
            Ok(())
        }
    }
    pub fn connect_one_way_to<Chain: IbcQueryHandler, IBC: InterchainEnv<Chain>>(
        abstr: &Abstract<Chain>,
        dest: &Abstract<Chain>,
        interchain: &IBC,
    ) -> Result<(), AbstractInterfaceError> {
        let chain1_id = abstr.ibc.client.environment().chain_id();
        let chain1_name = TruncatedChainId::from_chain_id(&chain1_id);
        let chain2_id = dest.ibc.client.environment().chain_id();
        let chain2_name = TruncatedChainId::from_chain_id(&chain2_id);
        let polytone_connection =
            PolytoneConnection::deploy_between_if_needed(interchain, &chain1_id, &chain2_id)?;
        let proxy_tx_result = abstr.ibc.client.register_infrastructure(
            chain2_name.clone(),
            dest.ibc.host.address()?.to_string(),
            polytone_connection.note.address()?.to_string(),
        )?;
        interchain.await_and_check_packets(&chain1_id, proxy_tx_result)?;
        let proxy_address = abstr.ibc.client.host(chain2_name)?;
        dest.ibc
            .host
            .register_chain_proxy(chain1_name, proxy_address.remote_polytone_proxy.unwrap())?;
        Ok(())
    }
}