use core::time::Duration;
use ibc_relayer::event::IbcEventWithHeight;
use ibc_relayer_types::applications::ics29_fee::packet_fee::IdentifiedPacketFees;
use ibc_relayer_types::core::ics04_channel::packet::Sequence;
use crate::chain::driver::ChainDriver;
use crate::chain::tagged::TaggedChainDriverExt;
use crate::error::Error;
use crate::ibc::token::TaggedTokenRef;
use crate::relayer::fee::{
    ibc_token_transfer_with_fee, pay_packet_fee, query_counterparty_payee,
    query_incentivized_packets, register_counterparty_payee, register_payee,
};
use crate::types::id::{TaggedChannelIdRef, TaggedPortIdRef};
use crate::types::tagged::*;
use crate::types::wallet::{Wallet, WalletAddress};
pub trait ChainFeeMethodsExt<Chain> {
    fn ibc_token_transfer_with_fee<Counterparty>(
        &self,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        sender: &MonoTagged<Chain, &Wallet>,
        recipient: &MonoTagged<Counterparty, &WalletAddress>,
        send_amount: &TaggedTokenRef<'_, Chain>,
        receive_fee: &TaggedTokenRef<'_, Chain>,
        ack_fee: &TaggedTokenRef<'_, Chain>,
        timeout_fee: &TaggedTokenRef<'_, Chain>,
        timeout: Duration,
    ) -> Result<Vec<IbcEventWithHeight>, Error>;
    fn pay_packet_fee<Counterparty>(
        &self,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        sequence: &DualTagged<Chain, Counterparty, Sequence>,
        payer: &MonoTagged<Chain, &Wallet>,
        receive_fee: &TaggedTokenRef<'_, Chain>,
        ack_fee: &TaggedTokenRef<'_, Chain>,
        timeout_fee: &TaggedTokenRef<'_, Chain>,
    ) -> Result<Vec<IbcEventWithHeight>, Error>;
    fn register_counterparty_payee<Counterparty>(
        &self,
        wallet: &MonoTagged<Chain, &Wallet>,
        counterparty_payee: &MonoTagged<Counterparty, &WalletAddress>,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
    ) -> Result<(), Error>;
    fn register_payee<Counterparty>(
        &self,
        wallet: &MonoTagged<Chain, &Wallet>,
        payee: &MonoTagged<Chain, &WalletAddress>,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
    ) -> Result<(), Error>;
    fn query_counterparty_payee<Counterparty>(
        &self,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        address: &MonoTagged<Chain, &WalletAddress>,
    ) -> Result<Option<MonoTagged<Counterparty, WalletAddress>>, Error>;
    fn query_incentivized_packets<Counterparty>(
        &self,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
    ) -> Result<Vec<IdentifiedPacketFees>, Error>;
}
impl<'a, Chain: Send> ChainFeeMethodsExt<Chain> for MonoTagged<Chain, &'a ChainDriver> {
    fn ibc_token_transfer_with_fee<Counterparty>(
        &self,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        sender: &MonoTagged<Chain, &Wallet>,
        recipient: &MonoTagged<Counterparty, &WalletAddress>,
        send_amount: &TaggedTokenRef<'_, Chain>,
        receive_fee: &TaggedTokenRef<'_, Chain>,
        ack_fee: &TaggedTokenRef<'_, Chain>,
        timeout_fee: &TaggedTokenRef<'_, Chain>,
        timeout: Duration,
    ) -> Result<Vec<IbcEventWithHeight>, Error> {
        let rpc_client = self.rpc_client()?;
        self.value().runtime.block_on(ibc_token_transfer_with_fee(
            rpc_client.as_ref(),
            &self.tx_config(),
            port_id,
            channel_id,
            sender,
            recipient,
            send_amount,
            receive_fee,
            ack_fee,
            timeout_fee,
            timeout,
        ))
    }
    fn pay_packet_fee<Counterparty>(
        &self,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        sequence: &DualTagged<Chain, Counterparty, Sequence>,
        payer: &MonoTagged<Chain, &Wallet>,
        receive_fee: &TaggedTokenRef<'_, Chain>,
        ack_fee: &TaggedTokenRef<'_, Chain>,
        timeout_fee: &TaggedTokenRef<'_, Chain>,
    ) -> Result<Vec<IbcEventWithHeight>, Error> {
        let rpc_client = self.rpc_client()?;
        self.value().runtime.block_on(pay_packet_fee(
            rpc_client.as_ref(),
            &self.tx_config(),
            port_id,
            channel_id,
            sequence,
            payer,
            receive_fee,
            ack_fee,
            timeout_fee,
        ))
    }
    fn register_counterparty_payee<Counterparty>(
        &self,
        wallet: &MonoTagged<Chain, &Wallet>,
        counterparty_payee: &MonoTagged<Counterparty, &WalletAddress>,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
    ) -> Result<(), Error> {
        let rpc_client = self.rpc_client()?;
        self.value().runtime.block_on(register_counterparty_payee(
            rpc_client.as_ref(),
            &self.tx_config(),
            wallet,
            counterparty_payee,
            channel_id,
            port_id,
        ))
    }
    fn register_payee<Counterparty>(
        &self,
        wallet: &MonoTagged<Chain, &Wallet>,
        payee: &MonoTagged<Chain, &WalletAddress>,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
    ) -> Result<(), Error> {
        let rpc_client = self.rpc_client()?;
        self.value().runtime.block_on(register_payee(
            rpc_client.as_ref(),
            &self.tx_config(),
            wallet,
            payee,
            channel_id,
            port_id,
        ))
    }
    fn query_counterparty_payee<Counterparty>(
        &self,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        address: &MonoTagged<Chain, &WalletAddress>,
    ) -> Result<Option<MonoTagged<Counterparty, WalletAddress>>, Error> {
        self.value().runtime.block_on(query_counterparty_payee(
            &self.tx_config().value().grpc_address,
            channel_id,
            address,
        ))
    }
    fn query_incentivized_packets<Counterparty>(
        &self,
        channel_id: &TaggedChannelIdRef<'_, Chain, Counterparty>,
        port_id: &TaggedPortIdRef<'_, Chain, Counterparty>,
    ) -> Result<Vec<IdentifiedPacketFees>, Error> {
        self.value().runtime.block_on(query_incentivized_packets(
            &self.tx_config().value().grpc_address,
            channel_id,
            port_id,
        ))
    }
}