use crate::cache::TransactionCache;
use crate::chaindef::BlockHash;
use crate::chaindef::OutPointHash;
use crate::chaindef::ScriptHash;
use crate::chaindef::Transaction;
use crate::chaindef::TxOut;
use crate::daemon::Daemon;
use crate::def::COIN;
use crate::encode::compute_outpoint_hash_from_tx;
use crate::encode::outpoint_hash;
use crate::indexes::outputindex::OutputIndexRow;
use crate::indexes::DBRow;
use crate::mempool::ConfirmationState;
use crate::mempool::MempoolEntry;
use crate::mempool::Tracker;

use crate::query::header::HeaderQuery;
use crate::query::queryutil;
use crate::rpc::encoding::blockhash_to_hex;
use crate::store::DBStore;
use crate::store::Row;
use crate::writebatch::PrefilledSpentData;
use anyhow::{Context, Result};
use bitcoincash::consensus::encode::{deserialize, serialize};
use bitcoincash::hash_types::Txid;
use bitcoincash::hashes::hex::ToHex;
use bitcoincash::network::constants::Network;
use bitcoincash::util::address::{Address, AddressType};
use futures::stream::TryStreamExt;
use futures_util::stream;
use futures_util::StreamExt;
use rust_decimal::prelude::*;
use serde_json::Value;
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
use tokio::select;

///  String returned is intended to be the same as produced by bitcoind
///  GetTxnOutputType
fn get_address_type(out: &TxOut, network: Network) -> Option<&str> {
    #[cfg(nexa)]
    {
        use crate::nexa::transaction::TxOutType;
        if out.txout_type == (TxOutType::TEMPLATE as u8) {
            return Some("scripttemplate");
        }
    }

    let script = &out.script_pubkey;
    if script.is_op_return() {
        return Some("nulldata");
    }
    let address = Address::from_script(script, network).ok()?;
    let address_type = address.address_type();
    match address_type {
        Some(AddressType::P2pkh) => Some("pubkeyhash"),
        Some(AddressType::P2sh) => Some("scripthash"),
        _ => {
            if !address.is_standard() {
                Some("nonstandard")
            } else {
                None
            }
        }
    }
}

#[cfg(bch)]
pub fn get_addresses(out: &TxOut, network: Network) -> Vec<String> {
    use crate::bch::cashaddr::encode as bchaddr_encode;
    use crate::bch::cashaddr::version_byte_flags;
    use bitcoincash::util::address::Payload::{PubkeyHash, ScriptHash};
    let script = &out.script_pubkey;
    let address = match Address::from_script(script, network).ok() {
        Some(a) => a,
        None => return vec![],
    };

    match address.payload {
        PubkeyHash(pubhash) => {
            let hash = pubhash.as_hash().to_vec();
            let encoded = bchaddr_encode(&hash, version_byte_flags::TYPE_P2PKH, network);
            let encoded_token =
                bchaddr_encode(&hash, version_byte_flags::TYPE_P2PKH_TOKEN, network);
            match encoded {
                Ok(addr) => {
                    // If it was P2PKH encoded, then it can also be token aware.
                    let addr_token = encoded_token.unwrap();
                    vec![addr, addr_token]
                }
                _ => vec![],
            }
        }
        ScriptHash(scripthash) => {
            let hash = scripthash.as_hash().to_vec();
            let encoded = bchaddr_encode(&hash, version_byte_flags::TYPE_P2SH, network);
            let encoded_token = bchaddr_encode(&hash, version_byte_flags::TYPE_P2SH_TOKEN, network);
            match encoded {
                Ok(addr) => {
                    // If it was P2SH encoded, then it can also be token aware.
                    let addr_token = encoded_token.unwrap();
                    vec![addr, addr_token]
                }
                _ => vec![],
            }
        }
        _ => vec![],
    }
}

/**
 * Derive address(s) for a output
 */
#[cfg(nexa)]
pub fn get_addresses(output: &TxOut, network: Network) -> Vec<String> {
    use crate::nexa::cashaddr::encode as nexaaddr_encode;
    use crate::nexa::cashaddr::version_byte_flags;
    use crate::nexa::transaction::TxOutType;
    use bitcoincash::util::address::Payload::{PubkeyHash, ScriptHash};

    if output.txout_type == TxOutType::TEMPLATE as u8 {
        let output_normalized = match output.scriptpubkey_without_token() {
            Ok(Some(s)) => s.to_bytes(),
            Ok(None) => output.script_pubkey.to_bytes(),
            Err(_) => {
                // invalid encoding, don't try to make it into an address.
                return vec![];
            }
        };

        if let Ok(addr) = nexaaddr_encode(
            &output_normalized,
            version_byte_flags::TYPE_SCRIPT_TEMPLATE,
            network,
        ) {
            return vec![addr];
        }
        return vec![];
    }

    if output.txout_type != TxOutType::SATOSHI as u8 {
        // unsupported output
        return vec![];
    }

    let address = match Address::from_script(&output.script_pubkey, network).ok() {
        Some(a) => a,
        None => return vec![],
    };

    match address.payload {
        PubkeyHash(pubhash) => {
            let hash = pubhash.as_hash().to_vec();
            if let Ok(addr) = nexaaddr_encode(&hash, version_byte_flags::TYPE_P2PKH, network) {
                return vec![addr];
            }
            vec![]
        }
        ScriptHash(scripthash) => {
            let hash = scripthash.as_hash().to_vec();
            if let Ok(addr) = nexaaddr_encode(&hash, version_byte_flags::TYPE_P2SH, network) {
                return vec![addr];
            }
            vec![]
        }
        _ => vec![],
    }
}

fn value_from_amount(amount: u64) -> Value {
    if amount == 0 {
        return json!(0.0);
    }
    let satoshis = Decimal::new(amount as i64, 0);
    // rust-decimal crate with feature 'serde-float' should make this work
    // without introducing precision errors
    json!(satoshis.checked_div(Decimal::new(COIN as i64, 0)).unwrap())
}

/**
 * Given an output, lookup how many satoshis were/are in the utxo.
 */
pub(crate) async fn sats_from_outpoint(
    store: &DBStore,
    outpointhash: &OutPointHash,
) -> Option<u64> {
    let key = OutputIndexRow::filter_by_outpointhash(outpointhash);
    let (key, value) = store.get(OutputIndexRow::CF, key).await;

    let row = OutputIndexRow::from_row(&Row {
        key: key.into_boxed_slice(),
        value: value?.into_boxed_slice(),
    });

    Some(row.value())
}

pub struct TxQuery {
    tx_cache: TransactionCache,
    daemon: Daemon,
    mempool: Arc<Tracker>,
    header: Arc<HeaderQuery>,
    duration: Arc<prometheus::HistogramVec>,
    network: Network,
    confirmed_index: Arc<DBStore>,
}

impl TxQuery {
    pub fn new(
        tx_cache: TransactionCache,
        daemon: Daemon,
        mempool: Arc<Tracker>,
        header: Arc<HeaderQuery>,
        duration: Arc<prometheus::HistogramVec>,
        network: Network,
        confirmed_index: Arc<DBStore>,
    ) -> TxQuery {
        TxQuery {
            tx_cache,
            daemon,
            mempool,
            header,
            duration,
            network,
            confirmed_index,
        }
    }

    pub async fn get_tx_funding_prevout(
        &self,
        prevout: &OutPointHash,
    ) -> Result<Option<(Transaction, u32, u32)>> {
        if let Some(found) =
            queryutil::get_tx_funding_prevout(self.mempool.index(), self, prevout).await?
        {
            return Ok(Some(found));
        }

        queryutil::get_tx_funding_prevout(&self.confirmed_index, self, prevout).await
    }

    /// Get a transaction by Txid.
    pub async fn get(
        &self,
        txid: &Txid,
        blockhash: Option<&BlockHash>,
        blockheight: Option<u32>,
        allow_by_txidem: bool,
    ) -> Result<Transaction> {
        let mempool_check = async {
            if blockhash.is_none() && blockheight.is_none() {
                self.mempool.get_txn(txid).await
            } else {
                None
            }
        };

        let cache_check = async { self.tx_cache.get(txid).await };

        // Try fetching from both local caches in parallel.
        select! {
            Some(cache_tx) = cache_check => {
                return Ok(cache_tx)
            }
            Some(mempool_tx) = mempool_check => {
                return Ok(mempool_tx)
            }
            else => {
                // Continue to fetch from node
            }
        };

        let _timer = self.duration.with_label_values(&["load_txn"]).start_timer();

        let hash: Option<BlockHash> = match blockhash {
            Some(hash) => Some(*hash),
            None => match self.header.get_by_txid(txid, blockheight).await {
                Ok(header) => header.map(|h| h.block_hash()),
                Err(_) => None,
            },
        };
        self.load_txn_from_bitcoind(txid, hash.as_ref(), allow_by_txidem)
            .await
    }

    /// Get an transaction known to be unconfirmed.
    ///
    /// This is slightly faster than `get` as it avoids blockhash lookup. May
    /// or may not return the transaction even if it is confirmed.
    pub async fn get_unconfirmed(&self, txid: &Txid) -> Result<Transaction> {
        if let Some(tx) = self.tx_cache.get(txid).await {
            Ok(tx)
        } else {
            self.load_txn_from_bitcoind(txid, None, false).await
        }
    }

    #[cfg(bch)]
    async fn inputs_to_json(&self, tx: &Transaction) -> Result<(Vec<Value>, u64)> {
        use bitcoincash::blockdata::token::Capability;

        use crate::errors::rpc_invalid_params;

        use super::queryutil::get_tx_funding_prevout;

        let inputs = stream::iter(tx.input.iter()).then(|txin| async move {

            let outpoint = outpoint_hash(&txin.previous_output);

            let value = match sats_from_outpoint(self.mempool.index(), &outpoint).await {
                Some(v) => Some(v),
                None => sats_from_outpoint(&self.confirmed_index, &outpoint).await
            };

            let utxo = if tx.is_coin_base() {
                None
            } else {
                let (utxo_creation_tx, output_index, _) = match get_tx_funding_prevout(self.mempool.index(), self, &outpoint).await? {
                    Some(found) => found,
                    None => match get_tx_funding_prevout(&self.confirmed_index, self, &outpoint).await? {
                        Some(found) => found,
                        None => return Err(rpc_invalid_params("could not find funding tx for outpoint".to_string()))
                    }
                };

                let utxo = utxo_creation_tx.output.get(output_index as usize).cloned();

                if utxo.is_none() {
                        bail!(rpc_invalid_params(format!(
                            "out_n {} does not exist on tx {}, the transaction has {} outputs",
                            output_index,
                            utxo_creation_tx.txid(),
                            utxo_creation_tx.output.len()
                        )));
                    }
                    utxo
            };

            let token = utxo.and_then(|u| u.token);

            let tokendata = if let Some(tokendata) = &token {

                let nft = if tokendata.has_nft() {

                    let capability = if tokendata.is_minting_nft() {
                        "minting".to_string()
                    } else if tokendata.capability() & Capability::Mutable as u8 != 0 {
                        "mutable".to_string()
                    } else {
                        "none".to_string()
                    };

                    Some(json!({
                        "commitment": tokendata.commitment.to_hex(),
                        "capability": capability
                    }))
                } else {
                    None
                };

                Some(json!({
                    "category": tokendata.id.to_hex(),
                    "amount": tokendata.amount.to_string(),
                    "nft": nft,
                }))
            } else {
                None
            };

            Ok((json!({
            // bitcoind adds scriptSig hex as 'coinbase' when the transaction is a coinbase
            "coinbase": if tx.is_coin_base() { Some(txin.script_sig.to_hex()) } else { None },
            "sequence": txin.sequence,
            "txid": txin.previous_output.txid.to_hex(),
            "vout": txin.previous_output.vout,
            "value_satoshi": value,
            "value_coin": value.map(value_from_amount),
            "scriptSig": {
                "asm": txin.script_sig.asm(),
                "hex": txin.script_sig.to_hex(),
            },
            "tokenData": tokendata,
        }), value.unwrap_or(0)))
    }).try_collect::<Vec<(Value, u64)>>().await?;

        let total_value_in = inputs.iter().map(|(_, v)| v).sum();
        let vin = inputs.into_iter().map(|(i, _)| i).collect();
        Ok((vin, total_value_in))
    }

    #[cfg(nexa)]
    async fn inputs_to_json(&self, tx: &Transaction) -> Result<(Vec<Value>, u64)> {
        use super::queryutil::get_tx_funding_prevout;
        use crate::errors::rpc_invalid_params;
        use crate::nexa::transaction::{parse_script_template, TxOutType};

        let inputs: Vec<(Value, u64)> = stream::iter(tx.input.iter()).then(|txin| async move {
            let outpoint = txin.previous_output.hash;
            let value = match sats_from_outpoint(self.mempool.index(), &outpoint).await {
                Some(v) => Some(v),
                None => sats_from_outpoint(&self.confirmed_index, &outpoint).await
            };

            let (utxo_creation_tx, output_index, _) = match get_tx_funding_prevout(self.mempool.index(), self, &outpoint).await? {
                Some(found) => found,
                None => match get_tx_funding_prevout(&self.confirmed_index, self, &outpoint).await? {
                    Some(found) => found,
                    None => return Err(rpc_invalid_params("could not find funding tx for outpoint".to_string()))
                }
            };

            let utxo = match utxo_creation_tx.output.get(output_index as usize) {
                Some(utxo) => utxo,
                None => {
                    bail!(rpc_invalid_params(format!(
                        "out_n {} does not exist on tx {}, the transaction has {} outputs",
                        output_index,
                        utxo_creation_tx.txid(),
                        utxo_creation_tx.output.len()
                    )));
                }
            };

            let template = if utxo.txout_type == TxOutType::TEMPLATE as u8 {
                parse_script_template(&utxo.script_pubkey)
            } else {
                None
            };

            let mut jval = json!({
                // bitcoind adds scriptSig hex as 'coinbase' when the transaction is a coinbase
                "coinbase": if tx.is_coin_base() { Some(txin.script_sig.to_hex()) } else { None },
                "sequence": txin.sequence,
                "outpoint": outpoint.to_hex(),
                "value_satoshi": value,
                "value_coin": value.map(value_from_amount),
                "value": value.map(value_from_amount),
                "scriptSig": {
                    "asm": txin.script_sig.asm(),
                    "hex": txin.script_sig.to_hex(),
                },
                "addresses": get_addresses(utxo, self.network)
            });

            let v = jval.as_object_mut().unwrap();
            v.insert(
                "group".to_string(),
                json!(template.as_ref().map(|t| t
                    .token
                    .as_ref()
                    .map(|token| token.as_cashaddr(self.network)))),
            );
            v.insert(
                "token_id_hex".to_string(),
                json!(template
                    .as_ref()
                    .map(|t| t.token.as_ref().map(|token| token.to_hex()))),
            );
            v.insert(
                "groupQuantity".to_string(),
                json!(template.as_ref().map(|t| t.quantity)),
            );
            v.insert(
                "groupAuthority".to_string(),
                json!(template.as_ref().map(|t| t.group_authority().unwrap_or(0))),
            );

            Ok((jval, value.unwrap_or(0)))
        }).try_collect().await?;

        let total_value = inputs.iter().map(|(_, value)| value).sum();
        let vin: Vec<Value> = inputs.into_iter().map(|(input, _)| input).collect();
        Ok((vin, total_value))
    }

    #[cfg(nexa)]
    fn outputs_to_json(&self, tx: &Transaction) -> (Vec<Value>, u64) {
        use crate::{
            encode::compute_outpoint_hash,
            nexa::transaction::{parse_script_template, TxOutType},
        };

        let total_output = tx.output.iter().map(|o| o.value).sum();
        let txidem = tx.txidem();

        let vout = tx.output
            .iter()
            .enumerate()
            .map(|(n, txout)| {
                let template = if txout.txout_type == (TxOutType::TEMPLATE as u8) {
                    parse_script_template(&txout.script_pubkey)
                } else {
                    None
                };

                json!({
                    "type": txout.txout_type,
                    "value": value_from_amount(txout.value),
                    "value_satoshi": txout.value,
                    "value_coin": value_from_amount(txout.value),
                    "n": n,
                    "scriptPubKey": {
                        "asm": txout.script_pubkey.asm(),
                        "hex": txout.script_pubkey.to_hex(),
                        "type": get_address_type(txout, self.network).unwrap_or_default(),
                        "addresses": get_addresses(txout, self.network),
                        "groupQuantity": template.as_ref().map(|t| t.quantity),
                        "groupAuthority": template.as_ref().map(|t| t.group_authority().unwrap_or(0)),
                        "scriptHash": template.as_ref().map(|t| t.scripthash_human()),
                        "argsHash": template.as_ref().map(|t| t.argumenthash.to_hex()),
                        "group": template.as_ref().map(|t| t.token.as_ref().map(|id| id.as_cashaddr(self.network))),
                        "token_id_hex": template.map(|t| t.token.map(|id| id.to_hex())),
                    },
                    "outpoint_hash": compute_outpoint_hash(&txidem, n as u32),

                })
            })
            .collect::<Vec<Value>>();

        (vout, total_output)
    }

    #[cfg(bch)]
    fn outputs_to_json(&self, tx: &Transaction) -> (Vec<Value>, u64) {
        use bitcoincash::blockdata::token::Capability;

        let capability_str = |capability: u8| {
            if (capability & Capability::Minting as u8) != 0 {
                "minting"
            } else if (capability & Capability::Mutable as u8) != 0 {
                "mutable"
            } else {
                "none"
            }
        };

        let total_value_out: u64 = tx.output.iter().map(|o| o.value).sum();

        let vout = tx
            .output
            .iter()
            .enumerate()
            .map(|(n, txout)| {
                let token_data = txout.token.as_ref().map(|token| {
                    json!({
                        "category": token.id,
                        "amount": token.amount.to_string(),
                        "nft": if token.has_nft() {
                            Some(json!({
                                "commitment": token.commitment.to_hex(),
                                "capability": capability_str(token.capability()),
                            }))
                        } else {
                            None
                        }
                    })
                });
                json!({
                "value_satoshi": txout.value,
                "value_coin": value_from_amount(txout.value),
                "value": value_from_amount(txout.value),
                "n": n,
                "tokenData": token_data,
                "scriptPubKey": {
                    "asm": txout.script_pubkey.asm(),
                    "hex": txout.script_pubkey.to_hex(),
                    "type": get_address_type(txout, self.network).unwrap_or_default(),
                    "addresses": get_addresses(txout, self.network),
                },
                })
            })
            .collect::<Vec<Value>>();

        (vout, total_value_out)
    }

    pub async fn get_verbose(&self, txid_or_txidem: &Txid, allow_by_txidem: bool) -> Result<Value> {
        let tx = self
            .get(txid_or_txidem, None, None, allow_by_txidem)
            .await?;

        let txid = tx.txid();

        let header = self
            .header
            .get_by_txid(&txid, None)
            .await
            .unwrap_or_default();
        let blocktime = header.as_ref().map(|header| header.time);

        let height = if let Some(ref header) = header {
            Some(
                self.header
                    .get_height(header)
                    .await
                    .context("header missing")?,
            )
        } else {
            None
        };

        let confirmations = match height {
            Some(ref height) => {
                let best = self.header.best().await;
                let best_height = self
                    .header
                    .get_height(&best)
                    .await
                    .context("header missing")?;

                Some(1 + best_height - height)
            }
            None => None,
        };

        let tx_serialized = serialize(&tx);

        let (vin, total_value_in) = self.inputs_to_json(&tx).await?;
        let (vout, total_value_out) = self.outputs_to_json(&tx);

        #[allow(unused_mut)]
        let mut tx_details = json!({
            "blockhash": header.map(|h| blockhash_to_hex(&h.block_hash())),
            "blocktime": blocktime,
            "height": height,
            "confirmations": confirmations,
            "hash": txid.to_hex(),
            "txid": txid.to_hex(),
            "size": tx_serialized.len(),
            "hex": hex::encode(tx_serialized),
            "locktime": tx.lock_time,
            "time": blocktime,
            "version": tx.version,
            "vin": vin,
            "vout": vout,
            "fee": value_from_amount(total_value_in.saturating_sub(total_value_out)),
            "fee_satoshi": total_value_in.saturating_sub(total_value_out),
        });

        #[cfg(nexa)]
        {
            tx_details
                .as_object_mut()
                .unwrap()
                .insert("txidem".to_string(), json!(tx.txidem().to_hex()));
        }

        Ok(json!(tx_details))
    }

    async fn load_txn_from_bitcoind(
        &self,
        txid: &Txid,
        blockhash: Option<&BlockHash>,
        allow_by_txidem: bool,
    ) -> Result<Transaction> {
        let value: Value = self
            .daemon
            .gettransaction_raw(txid, blockhash, /*verbose*/ false)
            .await?;
        let value_hex: &str = value.as_str().context("non-string tx")?;
        let serialized_tx = hex::decode(value_hex).context("non-hex tx")?;
        let tx: Transaction =
            deserialize(&serialized_tx).context("failed to parse serialized tx")?;
        let actual_txid = tx.txid();
        if txid != &actual_txid {
            match allow_by_txidem {
                #[cfg(nexa)]
                true => {
                    use bitcoin_hashes::Hash;
                    // If txid was actually a txidem, allow it on nexa. See issue #194
                    if tx.txidem().as_inner() != txid.as_inner() {
                        bail!(
                        "Requested transaction with ID {}, but received one with txid {} and txidem {}",
                        txid,
                        tx.txid(),
                        tx.txidem(),
                    );
                    }
                }
                _ => {
                    bail!(
                        "Requested transaction with txid {}, but received one with txid {}",
                        txid,
                        tx.txid()
                    );
                }
            }
        }
        self.tx_cache.put(actual_txid, serialized_tx).await;
        Ok(tx)
    }

    /// Returns the height the transaction is confirmed at.
    ///
    /// If the transaction is in mempool, it return -1 if it has unconfirmed
    /// parents, or 0 if not.
    ///
    /// Returns None if transaction does not exist.
    pub async fn get_confirmation_height(&self, txid: &Txid) -> Option<i64> {
        {
            match self.mempool.tx_confirmation_state(txid, None).await {
                ConfirmationState::InMempool => return Some(0),
                ConfirmationState::UnconfirmedParent => return Some(-1),
                _ => (),
            };
        }
        self.header
            .get_confirmed_height_for_tx(txid)
            .await
            .map(|height| height as i64)
    }

    /// Create input amount cache out of outsputs from given tx set, where possible.
    pub async fn create_input_cache(&self, txids: &HashSet<Txid>) -> HashMap<OutPointHash, u64> {
        stream::iter(txids)
            .filter_map(|txid| async move {
                match self.get_unconfirmed(txid).await {
                    Ok(tx) => {
                        let outputs = tx
                            .output
                            .iter()
                            .enumerate()
                            .map(|(n, o)| {
                                let outpoint = compute_outpoint_hash_from_tx(&tx, n as u32);
                                (outpoint, o.value)
                            })
                            .collect::<Vec<(OutPointHash, u64)>>();
                        Some(stream::iter(outputs))
                    }
                    Err(_) => None,
                }
            })
            .flatten()
            .collect::<HashMap<OutPointHash, u64>>()
            .await
    }

    // Requires that we have all the transactions in inputs (and enfoces this by failing).
    // Note: Mempool.add assumes we enforce no orphans!
    pub async fn create_prefilled_spent_data(
        &self,
        spending_tx: &Transaction,
    ) -> Result<HashMap<OutPointHash, PrefilledSpentData>> {
        let mut prefilled_spent_data = HashMap::with_capacity(spending_tx.input.len());

        for input in spending_tx.input.iter() {
            let outpoint = outpoint_hash(&input.previous_output);

            let (prevtx, vout) = match self.get_tx_funding_prevout(&outpoint).await {
                Ok(Some((tx, idx, _))) => (tx, idx as usize),
                _ => {
                    bail!("failed to get previous tx for input {}", outpoint.to_hex());
                }
            };

            let scripthash = ScriptHash::normalized_from_txout(&prevtx.output[vout]);
            let has_token = prevtx.output[vout].has_token();
            prefilled_spent_data.insert(
                outpoint,
                PrefilledSpentData {
                    scripthash,
                    has_token,
                },
            );
        }

        Ok(prefilled_spent_data)
    }

    /**
     * Create a mempool entry for given transaction.
     *
     * Note: We need to pass mempool, rather than use self.mempool to avoid dead lock,
     * since we call this method from the mempool itself.
     */
    pub async fn mempool_entry_from(
        &self,
        mempool: &Tracker,
        tx: Transaction,
        input_cache: &HashMap<OutPointHash, u64>,
    ) -> Result<MempoolEntry> {
        let total_stats_in: u64 = stream::iter(tx.input.iter())
            .then(|i| async move {
                let outpoint = outpoint_hash(&i.previous_output);
                match input_cache.get(&outpoint) {
                    Some(sats) => Ok(*sats),
                    None => match sats_from_outpoint(mempool.index(), &outpoint).await {
                        Some(v) => Ok(v),
                        None => sats_from_outpoint(&self.confirmed_index, &outpoint)
                            .await
                            .context(format!("Failed to get input {}", outpoint.to_hex())),
                    },
                }
            })
            .try_collect::<Vec<u64>>()
            .await?
            .into_iter()
            .sum();

        let total_sats_out: u64 = tx.output.iter().map(|o| o.value).sum();

        let fee: u64 = if total_sats_out > total_stats_in {
            if tx.is_coin_base() {
                0
            } else {
                // Invalid tx
                return Err(anyhow!("Inputs are greater than outputs"));
            }
        } else {
            total_stats_in - total_sats_out
        };

        Ok(MempoolEntry::new(fee, tx))
    }

    pub async fn get_mempool_entry(
        &self,
        mempool: &Tracker,
        txid: &Txid,
        input_cache: &HashMap<OutPointHash, u64>,
    ) -> Result<MempoolEntry> {
        let tx = self.get_unconfirmed(txid).await?;
        self.mempool_entry_from(mempool, tx, input_cache).await
    }
}

#[cfg(test)]
mod test {

    // Test case for bug where output of tx on the nexa chain was encoded with an extra prefixed byte.
    // Issue #188
    #[cfg(nexa)]
    #[test]
    fn test_encode_address_bug() {
        use super::get_addresses;
        use crate::nexa;
        use bitcoincash::{consensus::deserialize, Network};

        let txhex = "00010090c4c1de7a046ed38738c852d11c3520912bde69139f5774faa394bcf244b0b664222103b9521b653db81b3e5dde95092fa8483e22deb7b9646bd3c68e90ff1364a66120403ffee795329706d8e1fe88393869088b7c421604fa1dce99b478d28d6b2e6f89162b2921b3c82f4ac0eb6ff2b49a083c679ef20e572ba2b78954297efdfc17d6feffffff85ad0a00000000000201e09304000000000017005114247a304a71f7c08a66df3d41eb25c1609b4d75f001ca1806000000000017005114264164083c381bb50ea386d0913d1b6b8597c3cc861d0300";
        let tx: nexa::transaction::Transaction =
            deserialize(&hex::decode(&txhex).unwrap()).unwrap();
        let addresses = get_addresses(&tx.output[1], Network::Bitcoin);

        assert_eq!(1, addresses.len());
        let (payload, _, _) = nexa::cashaddr::decode(&addresses[0]).unwrap();
        assert_eq!(payload, tx.output[1].script_pubkey.to_bytes());
        assert_eq!(
            "nexa:nqtsq5g5yeqkgzpu8qdm2r4rsmgfz0gmdwze0s7vuwn0psk9",
            addresses[0]
        );
    }

    // Test case for bug where token and token amount was not removed from payload
    // Issue #195
    #[cfg(nexa)]
    #[test]
    fn test_encode_address_bug2() {
        let txhex = "0001002b21b6fbc90bf9d71c1a10ce0f7ce00486a6436e749ef50180fc494758d4239c64222103b586b8ff5362136ebbbf937dba7853e766436bff297025c5fdf2d202fe4190bc4065fb1adf72ecbec07e24e81a032e6628f8eb6068213083c9515634f3cb7c678673fba27a4dfcc29d7cd9614b43e7b82876fca372c25bc06a6c2df21e16a2598cfeffffffc22e023b00000000030000000000000000005a6a0438564c05054e49465459084e696674794172742368747470733a2f2f6e696674796172742e636173682f74642f6e696674792e6a736f6e200b4abe023ae013fd9cc30c9e8b3fbf40a06beceb5048419bd01cc8480a91fab00122020000000000004020cacf3d958161a925c28a970d3c40deec1a3fe06796fe1b4a7b68f377cdb900000833c10000000000fc511461977da240fe17110fa1e4df813957b058f3e2cf017b2a023b00000000170051146ff69e4cfcfeba70dd606cd35bb4010c791bf96a00000000";
        use super::get_addresses;
        use crate::nexa;
        use bitcoincash::{consensus::deserialize, Network};
        let tx: nexa::transaction::Transaction =
            deserialize(&hex::decode(&txhex).unwrap()).unwrap();
        let addresses = get_addresses(&tx.output[1], Network::Bitcoin);

        assert_eq!(1, addresses.len());
        let (payload, _, _) = nexa::cashaddr::decode(&addresses[0]).unwrap();
        assert_eq!(
            payload,
            tx.output[1]
                .scriptpubkey_without_token()
                .unwrap()
                .unwrap()
                .to_bytes()
        );
        assert_eq!(
            "nexa:nqtsq5g5vxthmgjqlct3zrapun0czw2hkpv08ck0frlkvpws",
            addresses[0]
        );
    }
}
