use revm::primitives::{Address, FixedBytes, TxKind, U256};
use serde::{Deserialize, Serialize};

use crate::{
    misc::utils::ToU128,
    models::{
        json::{
            mev_log_group_json::MEVLogGroupJson, mev_opcode_json::MEVOpcodeJson,
            mev_state_diff_json::MEVStateDiffJson,
        },
        mev_transaction::{
            CallExtract, MEVTransaction, calculate_create_address, display_token,
            display_token_and_usd, display_usd, eth_to_usd,
        },
    },
};

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MEVTransactionJson {
    pub block_number: u64,
    pub signature: String,
    pub signature_hash: Option<String>,
    pub tx_hash: FixedBytes<32>,
    pub index: u64,
    pub from: Address,
    pub from_ens: Option<String>,
    pub to: Option<Address>,
    pub to_ens: Option<String>,
    pub nonce: u64,
    pub value: String,
    pub display_value: String,
    pub coinbase_transfer: Option<String>,
    pub display_coinbase_transfer: Option<String>,
    pub display_coinbase_transfer_usd: Option<String>,
    pub success: bool,
    pub gas_price: u128,
    pub gas_used: u64,
    pub tx_cost: u128,
    pub display_tx_cost: String,
    pub display_tx_cost_usd: Option<String>,
    pub full_tx_cost: Option<u128>,
    pub display_full_tx_cost: Option<String>,
    pub display_full_tx_cost_usd: Option<String>,
    pub calls: Option<Vec<CallExtract>>,
    pub log_groups: Vec<MEVLogGroupJson>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub opcodes: Option<Vec<MEVOpcodeJson>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub state_diff: Option<MEVStateDiffJson>,
}

impl From<&MEVTransaction> for MEVTransactionJson {
    fn from(tx: &MEVTransaction) -> Self {
        let log_groups = tx.log_groups().iter().map(MEVLogGroupJson::from).collect();

        let gas_tx_cost = tx.receipt.gas_used as u128 * tx.receipt.effective_gas_price;
        let full_tx_cost = tx.full_tx_cost().map(|amt| amt.to_u128());

        let to = match tx.to {
            TxKind::Create => Some(calculate_create_address(tx.nonce, tx.from())),
            TxKind::Call(address) => Some(address),
        };

        Self {
            block_number: tx.block_number,
            signature: tx.signature.clone(),
            signature_hash: tx.signature_hash.clone(),
            tx_hash: tx.tx_hash,
            index: tx.index,
            from: tx.from(),
            from_ens: tx.from_ens_name().map(|s| s.to_string()),
            to,
            to_ens: tx.to_ens_name().map(|s| s.to_string()),
            nonce: tx.nonce,
            value: tx.value().to_string(),
            coinbase_transfer: tx.coinbase_transfer.map(|amt| amt.to_string()),
            display_coinbase_transfer: tx
                .coinbase_transfer
                .map(|amt| display_token(amt, &tx.chain.currency_symbol, false)),
            display_coinbase_transfer_usd: tx.coinbase_transfer.and_then(|amt| {
                tx.native_token_price
                    .map(|price| display_usd(eth_to_usd(amt, price)))
            }),
            success: tx.receipt.success,
            gas_price: tx.receipt.effective_gas_price,
            tx_cost: gas_tx_cost,
            display_tx_cost: display_token(
                U256::from(gas_tx_cost),
                &tx.chain.currency_symbol,
                false,
            ),
            display_tx_cost_usd: tx
                .native_token_price
                .map(|price| display_usd(eth_to_usd(U256::from(gas_tx_cost), price))),
            display_value: display_token_and_usd(
                tx.value(),
                tx.native_token_price,
                &tx.chain.currency_symbol,
            ),
            full_tx_cost,
            display_full_tx_cost: full_tx_cost
                .map(|amt| display_token(U256::from(amt), &tx.chain.currency_symbol, false)),
            display_full_tx_cost_usd: full_tx_cost.and_then(|amt| {
                tx.native_token_price
                    .map(|price| display_usd(eth_to_usd(U256::from(amt), price)))
            }),
            gas_used: tx.receipt.gas_used,
            calls: tx.calls.clone(),
            log_groups,
            opcodes: tx
                .opcodes
                .as_ref()
                .map(|ops| ops.iter().map(MEVOpcodeJson::from).collect()),
            state_diff: tx.state_diff.as_ref().map(MEVStateDiffJson::from),
        }
    }
}
