use std::{collections::HashSet, sync::Arc};

use bitcoincash::Txid;
use serde_json::Value;

use crate::{errors::rpc_invalid_params, mempool::Tracker};

use super::parseutil::{hex_from_value, str_from_value_or_none};
use anyhow::Result;

pub struct MempoolRPC {
    mempool: Arc<Tracker>,
}

const FILTER_SCRIPTSIG: &str = "scriptsig";
const FILTER_SCRIPTPUBKEY: &str = "scriptpubkey";
const FILTER_OPERATION: &str = "operation";
const FILTER_OPERATION_UNION: &str = "union";
const FILTER_OPERATION_EXCEPT: &str = "except";

enum SetOperation {
    Union,
    Except,
}

impl MempoolRPC {
    pub fn new(mempool: Arc<Tracker>) -> Self {
        Self { mempool }
    }

    pub async fn mempool_get(&self, params: &[Value]) -> Result<Value> {
        let filter = if let Some(p) = params.first() {
            if p.is_null() {
                None
            } else if let Some(p) = p.as_object() {
                Some(p)
            } else {
                return Err(rpc_invalid_params(
                    "invalid value for filter; must be null or dict".to_string(),
                ));
            }
        } else {
            None
        };

        let txs = if let Some(filter) = filter {
            let mut valid_filter = false;
            let mut results: Vec<Vec<Txid>> = Vec::default();

            let setoperation =
                match str_from_value_or_none(filter.get(FILTER_OPERATION), FILTER_OPERATION)? {
                    Some(operation) => match operation.as_str() {
                        FILTER_OPERATION_EXCEPT => SetOperation::Except,
                        FILTER_OPERATION_UNION => SetOperation::Union,
                        _ => return Err(rpc_invalid_params("invalid operation param".to_string())),
                    },
                    None => SetOperation::Except,
                };

            if let Some(scriptsig) = filter.get(FILTER_SCRIPTSIG) {
                let filter = hex_from_value(Some(scriptsig), FILTER_SCRIPTSIG)?;
                valid_filter = true;
                results.push(
                    self.mempool
                        .get_txn_partially_matching_scriptsig(&filter)
                        .await,
                );
            }
            if let Some(scriptpubkey) = filter.get(FILTER_SCRIPTPUBKEY) {
                let filter = hex_from_value(Some(scriptpubkey), FILTER_SCRIPTPUBKEY)?;
                valid_filter = true;
                results.push(
                    self.mempool
                        .get_txn_partially_matching_scriptpubkey(&filter)
                        .await,
                );
            }
            if !valid_filter {
                return Err(rpc_invalid_params("Unknown filter parameters".to_string()));
            }

            match setoperation {
                SetOperation::Union => {
                    // combine all results
                    let union: HashSet<Txid> = results.into_iter().flatten().collect();
                    union.into_iter().collect()
                }
                SetOperation::Except => {
                    // exclude results not matching all filters
                    let mut iter = results.into_iter();
                    let mut except: HashSet<Txid> =
                        iter.next().unwrap_or_default().into_iter().collect();

                    for result in iter {
                        let current_set: HashSet<_> = result.into_iter().collect();
                        except = except.intersection(&current_set).cloned().collect();
                    }

                    except.into_iter().collect()
                }
            }
        } else {
            // No filter, get full mempool
            self.mempool.get_all_txn().await
        };

        Ok(json!({
            "transactions": txs,
        }))
    }

    pub async fn mempool_count(&self) -> Result<Value> {
        Ok(json!({"count": self.mempool.get_count().await}))
    }

    pub(crate) async fn mempool_get_fee_histogram(&self) -> Value {
        json!(self.mempool.fee_histogram().await)
    }
}
