use std::sync::Arc;
use brontes_database::libmdbx::LibmdbxReader;
use brontes_metrics::inspectors::OutlierMetrics;
use brontes_types::{
    db::dex::BlockPrice,
    mev::{Bundle, BundleData, MevType, SearcherTx},
    normalized_actions::{accounting::ActionAccounting, Action},
    tree::BlockTree,
    ActionIter, BlockData, FastHashSet, MultiBlockData, ToFloatNearest, TreeSearchBuilder,
};
use itertools::multizip;
use malachite::{num::basic::traits::Zero, Rational};
use reth_primitives::Address;
use super::{MAX_PROFIT, MIN_PROFIT};
use crate::{shared_utils::SharedInspectorUtils, Inspector, Metadata};
pub struct SearcherActivity<'db, DB: LibmdbxReader> {
    utils: SharedInspectorUtils<'db, DB>,
}
impl<'db, DB: LibmdbxReader> SearcherActivity<'db, DB> {
    pub fn new(quote: Address, db: &'db DB, metrics: Option<OutlierMetrics>) -> Self {
        Self { utils: SharedInspectorUtils::new(quote, db, metrics) }
    }
}
impl<DB: LibmdbxReader> Inspector for SearcherActivity<'_, DB> {
    type Result = Vec<Bundle>;
    fn get_id(&self) -> &str {
        "SearcherActivity"
    }
    fn get_quote_token(&self) -> Address {
        self.utils.quote
    }
    fn inspect_block(&self, mut data: MultiBlockData) -> Self::Result {
        let block = data.per_block_data.pop().expect("no blocks");
        let BlockData { metadata, tree } = block;
        self.utils
            .get_metrics()
            .map(|m| {
                m.run_inspector(MevType::SearcherTx, || {
                    self.inspect_block_inner(tree.clone(), metadata.clone())
                })
            })
            .unwrap_or_else(|| self.inspect_block_inner(tree, metadata))
    }
}
impl<DB: LibmdbxReader> SearcherActivity<'_, DB> {
    fn inspect_block_inner(
        &self,
        tree: Arc<BlockTree<Action>>,
        metadata: Arc<Metadata>,
    ) -> Vec<Bundle> {
        let search_args = TreeSearchBuilder::default()
            .with_actions([Action::is_transfer, Action::is_eth_transfer]);
        let (hashes, transfers): (Vec<_>, Vec<_>) = tree.clone().collect_all(search_args).unzip();
        let tx_info = tree.get_tx_info_batch(&hashes, self.utils.db);
        multizip((hashes, transfers, tx_info))
            .filter_map(|(tx_hash, transfers, info)| {
                if transfers.is_empty() {
                    return None
                }
                let info = info?;
                (info.searcher_eoa_info.is_some() || info.searcher_contract_info.is_some()).then(
                    || {
                        let deltas = transfers
                            .clone()
                            .into_iter()
                            .chain(info.get_total_eth_value().iter().cloned().map(Action::from))
                            .account_for_actions();
                        let mut searcher_address: FastHashSet<Address> = FastHashSet::default();
                        searcher_address.insert(info.eoa);
                        if let Some(mev_contract) = info.mev_contract {
                            searcher_address.insert(mev_contract);
                        }
                        let (rev_usd, mut has_dex_price) = if let Some(rev) =
                            self.utils.get_full_block_price(
                                BlockPrice::Lowest,
                                searcher_address,
                                &deltas,
                                metadata.clone(),
                            ) {
                            (Some(rev), true)
                        } else {
                            (Some(Rational::ZERO), false)
                        };
                        let gas_paid = metadata
                            .get_gas_price_usd(info.gas_details.gas_paid(), self.utils.quote);
                        let mut profit = rev_usd
                            .map(|rev| rev - gas_paid)
                            .filter(|_| has_dex_price)
                            .unwrap_or_default();
                        if profit >= MAX_PROFIT || profit <= MIN_PROFIT {
                            has_dex_price = false;
                            profit = Rational::ZERO;
                        }
                        let header = self.utils.build_bundle_header_searcher_activity(
                            vec![deltas],
                            vec![tx_hash],
                            &info,
                            profit.to_float(),
                            BlockPrice::Lowest,
                            &[info.gas_details],
                            metadata.clone(),
                            MevType::SearcherTx,
                            !has_dex_price,
                        );
                        Some(Bundle {
                            header,
                            data: BundleData::Unknown(SearcherTx {
                                block_number: metadata.block_num,
                                tx_hash,
                                gas_details: info.gas_details,
                                transfers: transfers
                                    .into_iter()
                                    .collect_action_vec(Action::try_transfer),
                            }),
                        })
                    },
                )?
            })
            .collect::<Vec<_>>()
    }
}