use crate::common::{
    chain::Chain, ens::NameOrAddress, entity_id::EntityId, query_result::AccountQueryRes,
    types::AccountField,
};
use alloy::{
    primitives::Address,
    providers::{Provider, ProviderBuilder, RootProvider},
    transports::http::{Client, Http},
};
use futures::future::try_join_all;
use serde::{Deserialize, Serialize};
use std::error::Error;

#[derive(Debug, Serialize, Deserialize, thiserror::Error)]
pub enum AccountResolverErrors {
    #[error("Mismatch between Entity and EntityId, {0} can't be resolved as a account id")]
    MismatchEntityAndEntityId(String),
}

/// Resolve the query to get accounts after receiving an account entity expression
/// Iterate through entity_ids and map them to a futures list. Execute all futures concurrently and collect the results.
pub async fn resolve_account_query(
    entity_ids: Vec<EntityId>,
    fields: Vec<AccountField>,
    provider: &RootProvider<Http<Client>>,
) -> Result<Vec<AccountQueryRes>, Box<dyn Error>> {
    let mut account_futures = Vec::new();
    for entity_id in entity_ids {
        let fields = fields.clone();
        let provider = provider.clone();
        let account_future = async move {
            match entity_id {
                EntityId::Account(name_or_address) => {
                    let address = to_address(name_or_address).await?;
                    get_account(address, fields, &provider).await
                }
                id => Err(Box::new(AccountResolverErrors::MismatchEntityAndEntityId(
                    id.to_string(),
                ))
                .into()),
            }
        };

        account_futures.push(account_future);
    }

    let account_res = try_join_all(account_futures).await?;
    Ok(account_res)
}

async fn to_address(name_or_address: NameOrAddress) -> Result<Address, Box<dyn Error>> {
    match &name_or_address {
        NameOrAddress::Address(address) => Ok(*address),
        NameOrAddress::Name(_) => {
            let rpc_url = Chain::Ethereum.rpc_url()?;
            let provider = ProviderBuilder::new().on_http(rpc_url);
            let address = name_or_address.resolve(&provider).await?;
            Ok(address)
        }
    }
}

async fn get_account(
    address: Address,
    fields: Vec<AccountField>,
    provider: &RootProvider<Http<Client>>,
) -> Result<AccountQueryRes, Box<dyn Error>> {
    let mut account = AccountQueryRes::default();

    for field in &fields {
        match field {
            AccountField::Balance => {
                account.balance = Some(provider.get_balance(address).await?);
            }
            AccountField::Nonce => {
                account.nonce = Some(provider.get_transaction_count(address).await?);
            }
            AccountField::Address => {
                account.address = Some(address);
            }
            AccountField::Code => {
                account.code = Some(provider.get_code_at(address).await?);
            }
        }
    }

    Ok(account)
}

