use std::time::Duration;

use eyre::eyre;
use secrecy::SecretString;

use crate::Result;
use crate::domain::account::AccountStatus;
use crate::domain::address::TronAddress;
use crate::domain::trx::Trx;
use crate::listener::ListenerHandle;
use crate::provider::TronProvider;
use crate::signer::PrehashSigner;

use builder::PermissionHandler;

pub mod builder;
pub mod pending;

#[derive(Clone)]
pub enum Auth {
    Bearer { name: String, secret: SecretString },
}

#[derive(bon::Builder, Clone)]
pub struct Client<P, S> {
    pub(crate) provider: P,
    signer: Option<S>,
}

impl<P, S> Client<P, S>
where
    P: TronProvider,
    S: PrehashSigner,
{
    pub fn provider(&self) -> &P {
        &self.provider
    }
    pub fn signer_address(&self) -> Option<TronAddress> {
        self.signer.as_ref().and_then(|s| s.address())
    }
    pub fn with_signer<NewS>(self, s: NewS) -> Client<P, NewS> {
        Client::<P, NewS> {
            provider: self.provider,
            signer: Some(s),
        }
    }
    pub fn send_trx(&self) -> builder::TransferBuilder<'_, P, S> {
        builder::Transfer::with_client(self)
    }
    pub fn trx_balance(&self) -> builder::TrxBalanceBuilder<'_, P, S> {
        builder::TrxBalance::with_client(self)
    }
    pub fn read_contract<C>(
        &self,
        call: C,
    ) -> builder::ReadContractBuilder<'_, P, S, C> {
        builder::ReadContract::with_client_and_call(self, call)
    }
    pub fn freeze_balance(&self) -> builder::FreezeBalanceBuilder<'_, P, S> {
        builder::FreezeBalance::with_client(self)
    }
    pub fn unfreeze_balance(
        &self,
    ) -> builder::UnfreezeBalanceBuilder<'_, P, S> {
        builder::UnfreezeBalance::with_client(self)
    }
    pub fn cancel_all_unfreeze(
        &self,
    ) -> builder::CancelAllUnfreezeBuilder<'_, P, S> {
        builder::CancelAllUnfreeze::with_client(self)
    }
    pub fn delegate(&self) -> builder::DelegateBuilder<'_, P, S> {
        builder::Delegate::with_client(self)
    }
    pub fn undelegate(&self) -> builder::UndelegateBuilder<'_, P, S> {
        builder::Undelegate::with_client(self)
    }
    pub fn withdraw_unfreeze(
        &self,
    ) -> builder::WithdrawUnfreezeBuilder<'_, P, S> {
        builder::WithdrawUnfreeze::with_client(self)
    }
    pub fn create_contract(
        &self,
        contract: String,
    ) -> builder::CreateContractBuilder<'_, P, S> {
        builder::CreateContract::with_client_and_contract(self, contract)
    }
    pub async fn listener(
        &self,
        block_poll_interval: Duration,
    ) -> ListenerHandle
    where
        P: Clone + Send + Sync + 'static,
        S: Clone + Send + Sync + 'static,
        S::Error: std::fmt::Debug,
    {
        let listener = crate::listener::Listener::new(
            self.to_owned(),
            block_poll_interval,
        );
        listener.run().await
    }
    pub async fn account_permissions(
        &self,
        address: TronAddress,
    ) -> Result<PermissionHandler<'_, P, S>>
    where
        crate::error::Error: From<S::Error>,
    {
        PermissionHandler::new(self, address).await
    }
    pub async fn energy_price(&self) -> Result<Trx> {
        let chain_parameters = self.provider.chain_parameters().await?;
        let energy_price = chain_parameters
            .get("getEnergyFee")
            .ok_or(eyre!("not found getTransactionFee"))?;
        Ok((*energy_price).into())
    }
    pub async fn bandwidth_price(&self) -> Result<Trx> {
        let chain_parameters = self.provider.chain_parameters().await?;
        let bandwidth_unit_price = chain_parameters
            .get("getTransactionFee")
            .ok_or(eyre!("not found getTransactionFee"))?;
        Ok((*bandwidth_unit_price).into())
    }
    pub async fn check_account(
        &self,
        address: TronAddress,
    ) -> Result<AccountStatus> {
        let account = self.provider.get_account(address).await?;
        Ok(account.status())
    }
}
