use alloy_primitives::{Address, TxHash, U256};
use clickhouse::Row;
use malachite::{num::basic::traits::Zero, Rational};
use redefined::Redefined;
use reth_primitives::BlockHash;
use rkyv::{Archive, Deserialize as rDeserialize, Serialize as rSerialize};
use serde::Serialize;
use serde_with::serde_as;
use super::{
    builder::BuilderInfo,
    cex::{quotes::CexPriceMap, trades::CexTradeMap},
    dex::DexQuotes,
    traits::LibmdbxReader,
};
use crate::{
    block_metadata::RelayBlockMetadata,
    constants::WETH_ADDRESS,
    db::{dex::BlockPrice, redefined_types::primitives::*},
    implement_table_value_codecs_with_zc,
    pair::Pair,
    serde_utils::{option_addresss, u256, vec_txhash},
    FastHashSet,
};
#[allow(unused_imports)]
use crate::{db::cex::CexExchange, normalized_actions::NormalizedSwap};
#[serde_as]
#[derive(
    Debug, Default, Row, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Redefined,
)]
#[redefined_attr(derive(
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    rDeserialize,
    rSerialize,
    Archive
))]
pub struct BlockMetadataInner {
    #[serde(with = "u256")]
    pub block_hash:             U256,
    pub block_timestamp:        u64,
    pub relay_timestamp:        Option<u64>,
    pub p2p_timestamp:          Option<u64>,
    #[serde(with = "option_addresss")]
    pub proposer_fee_recipient: Option<Address>,
    pub proposer_mev_reward:    Option<u128>,
    #[serde(with = "vec_txhash")]
    pub private_flow:           Vec<TxHash>,
}
implement_table_value_codecs_with_zc!(BlockMetadataInnerRedefined);
impl BlockMetadataInner {
    pub fn make_new(
        block_hash: BlockHash,
        block_timestamp: u64,
        relay: Option<RelayBlockMetadata>,
        p2p_timestamp: Option<u64>,
        private_flow: Vec<TxHash>,
    ) -> Self {
        Self {
            block_hash: block_hash.into(),
            block_timestamp,
            relay_timestamp: relay.as_ref().and_then(|r| r.relay_timestamp),
            p2p_timestamp,
            proposer_fee_recipient: relay.as_ref().map(|r| r.proposer_fee_recipient),
            proposer_mev_reward: relay.as_ref().map(|r| r.proposer_mev_reward),
            private_flow,
        }
    }
}
#[derive(Debug, Clone, derive_more::Deref, derive_more::AsRef, Default)]
pub struct Metadata {
    #[deref]
    #[as_ref]
    pub block_metadata: BlockMetadata,
    pub cex_quotes:     CexPriceMap,
    pub dex_quotes:     Option<DexQuotes>,
    pub builder_info:   Option<BuilderInfo>,
    pub cex_trades:     Option<CexTradeMap>,
}
impl Metadata {
    pub fn display_pairs_quotes<DB: LibmdbxReader>(&self, db: &DB) {
        self.cex_quotes.quotes.iter().for_each(|(exchange, pairs)| {
            pairs.keys().for_each(|key| {
                let Ok(token0) = db.try_fetch_token_info(key.0).map(|s| s.symbol.clone()) else {
                    return
                };
                let Ok(token1) = db.try_fetch_token_info(key.1).map(|s| s.symbol.clone()) else {
                    return
                };
                if &token0 == "WETH" && &token1 == "USDT" {
                    tracing::info!(?exchange, "{}-{} in quotes", token0, token1);
                }
            });
        });
    }
    pub fn get_gas_price_usd(&self, gas_used: u128, quote_token: Address) -> Rational {
        let gas_used_rational = Rational::from_unsigneds(gas_used, 10u128.pow(18));
        let eth_price = self.get_eth_price(quote_token);
        gas_used_rational * eth_price
    }
    pub fn get_eth_price(&self, quote_token: Address) -> Rational {
        if self.block_metadata.eth_prices != Rational::ZERO {
            return self.block_metadata.eth_prices.clone()
        }
        self.dex_quotes
            .as_ref()
            .and_then(|dex_quotes| {
                dex_quotes.price_for_block(Pair(WETH_ADDRESS, quote_token), BlockPrice::Average)
            })
            .unwrap_or(Rational::ZERO)
    }
    pub fn into_full_metadata(mut self, dex_quotes: DexQuotes) -> Self {
        self.dex_quotes = Some(dex_quotes);
        self
    }
    pub fn with_builder_info(mut self, builder_info: BuilderInfo) -> Self {
        self.builder_info = Some(builder_info);
        self
    }
    pub fn block_num(&self) -> u64 {
        self.block_num
    }
}
#[derive(Debug, Clone, Default)]
pub struct BlockMetadata {
    pub block_num:              u64,
    pub block_hash:             U256,
    pub block_timestamp:        u64,
    pub relay_timestamp:        Option<u64>,
    pub p2p_timestamp:          Option<u64>,
    pub proposer_fee_recipient: Option<Address>,
    pub proposer_mev_reward:    Option<u128>,
    pub eth_prices:             Rational,
    pub private_flow:           FastHashSet<TxHash>,
}
impl BlockMetadata {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        block_num: u64,
        block_hash: U256,
        block_timestamp: u64,
        relay_timestamp: Option<u64>,
        p2p_timestamp: Option<u64>,
        proposer_fee_recipient: Option<Address>,
        proposer_mev_reward: Option<u128>,
        eth_prices: Rational,
        private_flow: FastHashSet<TxHash>,
    ) -> Self {
        Self {
            block_num,
            block_hash,
            relay_timestamp,
            p2p_timestamp,
            eth_prices,
            proposer_fee_recipient,
            proposer_mev_reward,
            private_flow,
            block_timestamp,
        }
    }
    pub fn microseconds_block_timestamp(&self) -> u64 {
        self.block_timestamp * 1_000_000
    }
    pub fn into_metadata(
        self,
        cex_quotes: CexPriceMap,
        dex_quotes: Option<DexQuotes>,
        builder_info: Option<BuilderInfo>,
        cex_trades: Option<CexTradeMap>,
    ) -> Metadata {
        Metadata { block_metadata: self, cex_quotes, dex_quotes, builder_info, cex_trades }
    }
}