use crate::{
    database::SubState,
    env::{fill_cfg_and_block_env, fill_tx_env},
    eth_dao_fork::{DAO_HARDFORK_BENEFICIARY, DAO_HARDKFORK_ACCOUNTS},
    into_reth_log,
    stack::{InspectorStack, InspectorStackConfig},
    to_reth_acc,
};
use reth_consensus_common::calc;
use reth_interfaces::executor::{BlockExecutionError, BlockValidationError};
use reth_primitives::{
    Account, Address, Block, BlockNumber, Bloom, Bytecode, ChainSpec, Hardfork, Header, Receipt,
    ReceiptWithBloom, TransactionSigned, Withdrawal, H256, U256,
};
use reth_provider::{BlockExecutor, PostState, StateProvider};
use revm::{
    db::{AccountState, CacheDB, DatabaseRef},
    primitives::{
        hash_map::{self, Entry},
        Account as RevmAccount, AccountInfo, ResultAndState,
    },
    EVM,
};
use std::{
    collections::{BTreeMap, HashMap},
    sync::Arc,
};

/// Main block executor
pub struct Executor<DB>
where
    DB: StateProvider,
{
    /// The configured chain-spec
    pub chain_spec: Arc<ChainSpec>,
    evm: EVM<SubState<DB>>,
    stack: InspectorStack,
}

impl<DB> From<Arc<ChainSpec>> for Executor<DB>
where
    DB: StateProvider,
{
    /// Instantiates a new executor from the chainspec. Must call
    /// `with_db` to set the database before executing.
    fn from(chain_spec: Arc<ChainSpec>) -> Self {
        let evm = EVM::new();
        Executor { chain_spec, evm, stack: InspectorStack::new(InspectorStackConfig::default()) }
    }
}

impl<DB> Executor<DB>
where
    DB: StateProvider,
{
    /// Creates a new executor from the given chain spec and database.
    pub fn new(chain_spec: Arc<ChainSpec>, db: SubState<DB>) -> Self {
        let mut evm = EVM::new();
        evm.database(db);

        Executor { chain_spec, evm, stack: InspectorStack::new(InspectorStackConfig::default()) }
    }

    /// Configures the executor with the given inspectors.
    pub fn with_stack(mut self, stack: InspectorStack) -> Self {
        self.stack = stack;
        self
    }

    /// Gives a reference to the database
    pub fn db(&mut self) -> &mut SubState<DB> {
        self.evm.db().expect("db to not be moved")
    }

    fn recover_senders(
        &self,
        body: &[TransactionSigned],
        senders: Option<Vec<Address>>,
    ) -> Result<Vec<Address>, BlockExecutionError> {
        if let Some(senders) = senders {
            if body.len() == senders.len() {
                Ok(senders)
            } else {
                Err(BlockValidationError::SenderRecoveryError.into())
            }
        } else {
            body.iter()
                .map(|tx| {
                    tx.recover_signer().ok_or(BlockValidationError::SenderRecoveryError.into())
                })
                .collect()
        }
    }

    /// Initializes the config and block env.
    fn init_env(&mut self, header: &Header, total_difficulty: U256) {
        fill_cfg_and_block_env(
            &mut self.evm.env.cfg,
            &mut self.evm.env.block,
            &self.chain_spec,
            header,
            total_difficulty,
        );
    }

    /// Commit change to the run-time database, and update the given [PostState] with the changes
    /// made in the transaction, which can be persisted to the database.
    fn commit_changes(
        &mut self,
        block_number: BlockNumber,
        changes: hash_map::HashMap<Address, RevmAccount>,
        has_state_clear_eip: bool,
        post_state: &mut PostState,
    ) {
        let db = self.db();
        commit_state_changes(db, post_state, block_number, changes, has_state_clear_eip);
    }

    /// Collect all balance changes at the end of the block.
    ///
    /// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular
    /// state changes (DAO fork).
    fn post_block_balance_increments(&self, block: &Block, td: U256) -> HashMap<Address, U256> {
        post_block_balance_increments(
            &self.chain_spec,
            block.number,
            block.difficulty,
            block.beneficiary,
            block.timestamp,
            td,
            &block.ommers,
            block.withdrawals.as_deref(),
        )
    }

    /// Irregular state change at Ethereum DAO hardfork
    fn apply_dao_fork_changes(
        &mut self,
        block_number: BlockNumber,
        post_state: &mut PostState,
    ) -> Result<(), BlockExecutionError> {
        let db = self.db();

        let mut drained_balance = U256::ZERO;

        // drain all accounts ether
        for address in DAO_HARDKFORK_ACCOUNTS {
            let db_account =
                db.load_account(address).map_err(|_| BlockExecutionError::ProviderError)?;
            let old = to_reth_acc(&db_account.info);
            // drain balance
            drained_balance += core::mem::take(&mut db_account.info.balance);
            let new = to_reth_acc(&db_account.info);
            // assume it is changeset as it is irregular state change
            post_state.change_account(block_number, address, old, new);
        }

        // add drained ether to beneficiary.
        let beneficiary = DAO_HARDFORK_BENEFICIARY;
        self.increment_account_balance(block_number, beneficiary, drained_balance, post_state)?;

        Ok(())
    }

    /// Increment the balance for the given account in the [PostState].
    fn increment_account_balance(
        &mut self,
        block_number: BlockNumber,
        address: Address,
        increment: U256,
        post_state: &mut PostState,
    ) -> Result<(), BlockExecutionError> {
        increment_account_balance(self.db(), post_state, block_number, address, increment)
            .map_err(|_| BlockExecutionError::ProviderError)
    }

    /// Runs a single transaction in the configured environment and proceeds
    /// to return the result and state diff (without applying it).
    ///
    /// Assumes the rest of the block environment has been filled via `init_block_env`.
    pub fn transact(
        &mut self,
        transaction: &TransactionSigned,
        sender: Address,
    ) -> Result<ResultAndState, BlockExecutionError> {
        // Fill revm structure.
        fill_tx_env(&mut self.evm.env.tx, transaction, sender);

        let hash = transaction.hash();
        let out = if self.stack.should_inspect(&self.evm.env, hash) {
            // execution with inspector.
            let output = self.evm.inspect(&mut self.stack);
            tracing::trace!(
                target: "evm",
                ?hash, ?output, ?transaction, env = ?self.evm.env,
                "Executed transaction"
            );
            output
        } else {
            // main execution.
            self.evm.transact()
        };
        out.map_err(|e| BlockValidationError::EVM { hash, message: format!("{e:?}") }.into())
    }

    /// Runs the provided transactions and commits their state to the run-time database.
    ///
    /// The returned [PostState] can be used to persist the changes to disk, and contains the
    /// changes made by each transaction.
    ///
    /// The changes in [PostState] have a transition ID associated with them: there is one
    /// transition ID for each transaction (with the first executed tx having transition ID 0, and
    /// so on).
    ///
    /// The second returned value represents the total gas used by this block of transactions.
    pub fn execute_transactions(
        &mut self,
        block: &Block,
        total_difficulty: U256,
        senders: Option<Vec<Address>>,
    ) -> Result<(PostState, u64), BlockExecutionError> {
        // perf: do not execute empty blocks
        if block.body.is_empty() {
            return Ok((PostState::default(), 0))
        }
        let senders = self.recover_senders(&block.body, senders)?;

        self.init_env(&block.header, total_difficulty);

        let mut cumulative_gas_used = 0;
        let mut post_state = PostState::with_tx_capacity(block.number, block.body.len());
        for (transaction, sender) in block.body.iter().zip(senders) {
            // The sum of the transaction’s gas limit, Tg, and the gas utilised in this block prior,
            // must be no greater than the block’s gasLimit.
            let block_available_gas = block.header.gas_limit - cumulative_gas_used;
            if transaction.gas_limit() > block_available_gas {
                return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
                    transaction_gas_limit: transaction.gas_limit(),
                    block_available_gas,
                }
                .into())
            }
            // Execute transaction.
            let ResultAndState { result, state } = self.transact(transaction, sender)?;

            // commit changes
            self.commit_changes(
                block.number,
                state,
                self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(block.number),
                &mut post_state,
            );

            // append gas used
            cumulative_gas_used += result.gas_used();

            // Push transaction changeset and calculate header bloom filter for receipt.
            post_state.add_receipt(
                block.number,
                Receipt {
                    tx_type: transaction.tx_type(),
                    // Success flag was added in `EIP-658: Embedding transaction status code in
                    // receipts`.
                    success: result.is_success(),
                    cumulative_gas_used,
                    // convert to reth log
                    logs: result.into_logs().into_iter().map(into_reth_log).collect(),
                },
            );
        }

        Ok((post_state, cumulative_gas_used))
    }

    /// Applies the post-block changes, assuming the poststate is generated after executing
    /// tranactions
    pub fn apply_post_block_changes(
        &mut self,
        block: &Block,
        total_difficulty: U256,
        mut post_state: PostState,
    ) -> Result<PostState, BlockExecutionError> {
        // Add block rewards
        let balance_increments = self.post_block_balance_increments(block, total_difficulty);
        for (address, increment) in balance_increments.into_iter() {
            self.increment_account_balance(block.number, address, increment, &mut post_state)?;
        }

        // Perform DAO irregular state change
        if self.chain_spec.fork(Hardfork::Dao).transitions_at_block(block.number) {
            self.apply_dao_fork_changes(block.number, &mut post_state)?;
        }
        Ok(post_state)
    }
}

impl<DB> BlockExecutor<DB> for Executor<DB>
where
    DB: StateProvider,
{
    fn execute(
        &mut self,
        block: &Block,
        total_difficulty: U256,
        senders: Option<Vec<Address>>,
    ) -> Result<PostState, BlockExecutionError> {
        let (post_state, cumulative_gas_used) =
            self.execute_transactions(block, total_difficulty, senders)?;

        // Check if gas used matches the value set in header.
        if block.gas_used != cumulative_gas_used {
            return Err(BlockValidationError::BlockGasUsed {
                got: cumulative_gas_used,
                expected: block.gas_used,
            }
            .into())
        }

        self.apply_post_block_changes(block, total_difficulty, post_state)
    }

    fn execute_and_verify_receipt(
        &mut self,
        block: &Block,
        total_difficulty: U256,
        senders: Option<Vec<Address>>,
    ) -> Result<PostState, BlockExecutionError> {
        let post_state = self.execute(block, total_difficulty, senders)?;

        // TODO Before Byzantium, receipts contained state root that would mean that expensive
        // operation as hashing that is needed for state root got calculated in every
        // transaction This was replaced with is_success flag.
        // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658
        if self.chain_spec.fork(Hardfork::Byzantium).active_at_block(block.header.number) {
            verify_receipt(
                block.header.receipts_root,
                block.header.logs_bloom,
                post_state.receipts(block.number).iter(),
            )?;
        }

        Ok(post_state)
    }
}

/// Increment the balance for the given account in the [PostState].
///
/// Returns an error if the database encountered an error while loading the account.
pub fn increment_account_balance<DB>(
    db: &mut CacheDB<DB>,
    post_state: &mut PostState,
    block_number: BlockNumber,
    address: Address,
    increment: U256,
) -> Result<(), <DB as DatabaseRef>::Error>
where
    DB: DatabaseRef,
{
    let beneficiary = db.load_account(address)?;
    let old = to_reth_acc(&beneficiary.info);
    // Increment beneficiary balance by mutating db entry in place.
    beneficiary.info.balance += increment;
    let new = to_reth_acc(&beneficiary.info);
    match beneficiary.account_state {
        AccountState::NotExisting => {
            // if account was not existing that means that storage is not
            // present.
            beneficiary.account_state = AccountState::StorageCleared;

            // if account was not present append `Created` changeset
            post_state.create_account(
                block_number,
                address,
                Account { nonce: 0, balance: new.balance, bytecode_hash: None },
            )
        }

        AccountState::StorageCleared | AccountState::Touched | AccountState::None => {
            // If account is None that means that EVM didn't touch it.
            // we are changing the state to Touched as account can have
            // storage in db.
            if beneficiary.account_state == AccountState::None {
                beneficiary.account_state = AccountState::Touched;
            }
            // if account was present, append changed changeset.
            post_state.change_account(block_number, address, old, new);
        }
    }

    Ok(())
}

/// Commit change to the _run-time_ database [CacheDB], and update the given [PostState] with the
/// changes made in the transaction, which can be persisted to the database.
///
/// Note: This does _not_ commit to the underlying database [DatabaseRef], but only to the
/// [CacheDB].
pub fn commit_state_changes<DB>(
    db: &mut CacheDB<DB>,
    post_state: &mut PostState,
    block_number: BlockNumber,
    changes: hash_map::HashMap<Address, RevmAccount>,
    has_state_clear_eip: bool,
) where
    DB: DatabaseRef,
{
    // iterate over all changed accounts
    for (address, account) in changes {
        if account.is_destroyed {
            // get old account that we are destroying.
            let db_account = match db.accounts.entry(address) {
                Entry::Occupied(entry) => entry.into_mut(),
                Entry::Vacant(_entry) => {
                    panic!("Left panic to critically jumpout if happens, as every account should be hot loaded.");
                }
            };

            let account_exists = !matches!(db_account.account_state, AccountState::NotExisting);
            if account_exists {
                // Insert into `change` a old account and None for new account
                // and mark storage to be wiped
                post_state.destroy_account(block_number, address, to_reth_acc(&db_account.info));
            }

            // clear cached DB and mark account as not existing
            db_account.storage.clear();
            db_account.account_state = AccountState::NotExisting;
            db_account.info = AccountInfo::default();

            continue
        } else {
            // check if account code is new or old.
            // does it exist inside cached contracts if it doesn't it is new bytecode that
            // we are inserting inside `change`
            if let Some(ref code) = account.info.code {
                if !code.is_empty() && !db.contracts.contains_key(&account.info.code_hash) {
                    db.contracts.insert(account.info.code_hash, code.clone());
                    post_state.add_bytecode(account.info.code_hash, Bytecode(code.clone()));
                }
            }

            // get old account that is going to be overwritten or none if it does not exist
            // and get new account that was just inserted. new account mut ref is used for
            // inserting storage
            let cached_account = match db.accounts.entry(address) {
                Entry::Vacant(entry) => {
                    let entry = entry.insert(Default::default());
                    entry.info = account.info.clone();
                    entry.account_state = AccountState::NotExisting; // we will promote account state down the road
                    let new_account = to_reth_acc(&entry.info);

                    #[allow(clippy::nonminimal_bool)]
                    // If account was touched before state clear EIP, create it.
                    if !has_state_clear_eip ||
                        // If account was touched after state clear EIP, create it only if it is not empty.
                        (has_state_clear_eip && !new_account.is_empty())
                    {
                        post_state.create_account(block_number, address, new_account);
                    }

                    entry
                }
                Entry::Occupied(entry) => {
                    let entry = entry.into_mut();

                    let old_account = to_reth_acc(&entry.info);
                    let new_account = to_reth_acc(&account.info);

                    let account_non_existent =
                        matches!(entry.account_state, AccountState::NotExisting);

                    // Before state clear EIP, create account if it doesn't exist
                    if (!has_state_clear_eip && account_non_existent)
                        // After state clear EIP, create account only if it is not empty
                        || (has_state_clear_eip && entry.info.is_empty() && !new_account.is_empty())
                    {
                        post_state.create_account(block_number, address, new_account);
                    } else if old_account != new_account {
                        post_state.change_account(
                            block_number,
                            address,
                            to_reth_acc(&entry.info),
                            new_account,
                        );
                    } else if has_state_clear_eip && new_account.is_empty() && !account_non_existent
                    {
                        // The account was touched, but it is empty, so it should be deleted.
                        // This also deletes empty accounts which were created before state clear
                        // EIP.
                        post_state.destroy_account(block_number, address, new_account);
                    }

                    entry.info = account.info.clone();
                    entry
                }
            };

            cached_account.account_state = if account.storage_cleared {
                cached_account.storage.clear();
                AccountState::StorageCleared
            } else if cached_account.account_state.is_storage_cleared() {
                // the account already exists and its storage was cleared, preserve its previous
                // state
                AccountState::StorageCleared
            } else if has_state_clear_eip &&
                matches!(cached_account.account_state, AccountState::NotExisting) &&
                cached_account.info.is_empty()
            {
                AccountState::NotExisting
            } else {
                AccountState::Touched
            };

            // Insert storage.
            let mut storage_changeset = BTreeMap::new();

            // insert storage into new db account.
            cached_account.storage.extend(account.storage.into_iter().map(|(key, value)| {
                if value.is_changed() {
                    storage_changeset.insert(key, (value.original_value(), value.present_value()));
                }
                (key, value.present_value())
            }));

            // Insert into change.
            if !storage_changeset.is_empty() {
                post_state.change_storage(block_number, address, storage_changeset);
            }
        }
    }
}

/// Verify receipts
pub fn verify_receipt<'a>(
    expected_receipts_root: H256,
    expected_logs_bloom: Bloom,
    receipts: impl Iterator<Item = &'a Receipt> + Clone,
) -> Result<(), BlockExecutionError> {
    // Check receipts root.
    let receipts_with_bloom = receipts.map(|r| r.clone().into()).collect::<Vec<ReceiptWithBloom>>();
    let receipts_root = reth_primitives::proofs::calculate_receipt_root(&receipts_with_bloom);
    if receipts_root != expected_receipts_root {
        return Err(BlockValidationError::ReceiptRootDiff {
            got: receipts_root,
            expected: expected_receipts_root,
        }
        .into())
    }

    // Create header log bloom.
    let logs_bloom = receipts_with_bloom.iter().fold(Bloom::zero(), |bloom, r| bloom | r.bloom);
    if logs_bloom != expected_logs_bloom {
        return Err(BlockValidationError::BloomLogDiff {
            expected: Box::new(expected_logs_bloom),
            got: Box::new(logs_bloom),
        }
        .into())
    }

    Ok(())
}

/// Collect all balance changes at the end of the block.
///
/// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular
/// state changes (DAO fork).
#[allow(clippy::too_many_arguments)]
#[inline]
pub fn post_block_balance_increments(
    chain_spec: &ChainSpec,
    block_number: u64,
    block_difficulty: U256,
    beneficiary: Address,
    block_timestamp: u64,
    total_difficulty: U256,
    ommers: &[Header],
    withdrawals: Option<&[Withdrawal]>,
) -> HashMap<Address, U256> {
    let mut balance_increments = HashMap::new();

    // Add block rewards if they are enabled.
    if let Some(base_block_reward) =
        calc::base_block_reward(chain_spec, block_number, block_difficulty, total_difficulty)
    {
        // Ommer rewards
        for ommer in ommers {
            *balance_increments.entry(ommer.beneficiary).or_default() +=
                calc::ommer_reward(base_block_reward, block_number, ommer.number);
        }

        // Full block reward
        *balance_increments.entry(beneficiary).or_default() +=
            calc::block_reward(base_block_reward, ommers.len());
    }

    // process withdrawals
    insert_post_block_withdrawals_balance_increments(
        chain_spec,
        block_timestamp,
        withdrawals,
        &mut balance_increments,
    );

    balance_increments
}

/// Returns a map of addresses to their balance increments if shanghai is active at the given
/// timestamp.
#[inline]
pub fn post_block_withdrawals_balance_increments(
    chain_spec: &ChainSpec,
    block_timestamp: u64,
    withdrawals: &[Withdrawal],
) -> HashMap<Address, U256> {
    let mut balance_increments = HashMap::with_capacity(withdrawals.len());
    insert_post_block_withdrawals_balance_increments(
        chain_spec,
        block_timestamp,
        Some(withdrawals),
        &mut balance_increments,
    );
    balance_increments
}

/// Applies all withdrawal balance increments if shanghai is active at the given timestamp to the
/// given `balance_increments` map.
#[inline]
pub fn insert_post_block_withdrawals_balance_increments(
    chain_spec: &ChainSpec,
    block_timestamp: u64,
    withdrawals: Option<&[Withdrawal]>,
    balance_increments: &mut HashMap<Address, U256>,
) {
    // Process withdrawals
    if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(block_timestamp) {
        if let Some(withdrawals) = withdrawals {
            for withdrawal in withdrawals {
                *balance_increments.entry(withdrawal.address).or_default() +=
                    withdrawal.amount_wei();
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::database::State;
    use once_cell::sync::Lazy;
    use reth_consensus_common::calc;
    use reth_primitives::{
        constants::ETH_TO_WEI, hex_literal::hex, keccak256, Account, Address, BlockNumber,
        Bytecode, Bytes, ChainSpecBuilder, ForkCondition, StorageKey, H256, MAINNET, U256,
    };
    use reth_provider::{
        post_state::{AccountChanges, Storage, StorageTransition, StorageWipe},
        AccountReader, BlockHashReader, StateProvider, StateRootProvider,
    };
    use reth_rlp::Decodable;
    use std::{collections::HashMap, str::FromStr};

    static DEFAULT_REVM_ACCOUNT: Lazy<RevmAccount> = Lazy::new(|| RevmAccount {
        info: AccountInfo::default(),
        storage: hash_map::HashMap::default(),
        is_destroyed: false,
        is_touched: false,
        storage_cleared: false,
        is_not_existing: false,
    });

    #[derive(Debug, Default, Clone, Eq, PartialEq)]
    struct StateProviderTest {
        accounts: HashMap<Address, (HashMap<StorageKey, U256>, Account)>,
        contracts: HashMap<H256, Bytecode>,
        block_hash: HashMap<u64, H256>,
    }

    impl StateProviderTest {
        /// Insert account.
        fn insert_account(
            &mut self,
            address: Address,
            mut account: Account,
            bytecode: Option<Bytes>,
            storage: HashMap<StorageKey, U256>,
        ) {
            if let Some(bytecode) = bytecode {
                let hash = keccak256(&bytecode);
                account.bytecode_hash = Some(hash);
                self.contracts.insert(hash, Bytecode::new_raw(bytecode.into()));
            }
            self.accounts.insert(address, (storage, account));
        }
    }

    impl AccountReader for StateProviderTest {
        fn basic_account(&self, address: Address) -> reth_interfaces::Result<Option<Account>> {
            let ret = Ok(self.accounts.get(&address).map(|(_, acc)| *acc));
            ret
        }
    }

    impl BlockHashReader for StateProviderTest {
        fn block_hash(&self, number: u64) -> reth_interfaces::Result<Option<H256>> {
            Ok(self.block_hash.get(&number).cloned())
        }

        fn canonical_hashes_range(
            &self,
            start: BlockNumber,
            end: BlockNumber,
        ) -> reth_interfaces::Result<Vec<H256>> {
            let range = start..end;
            Ok(self
                .block_hash
                .iter()
                .filter_map(|(block, hash)| range.contains(block).then_some(*hash))
                .collect())
        }
    }

    impl StateRootProvider for StateProviderTest {
        fn state_root(&self, _post_state: PostState) -> reth_interfaces::Result<H256> {
            todo!()
        }
    }

    impl StateProvider for StateProviderTest {
        fn storage(
            &self,
            account: Address,
            storage_key: reth_primitives::StorageKey,
        ) -> reth_interfaces::Result<Option<reth_primitives::StorageValue>> {
            Ok(self
                .accounts
                .get(&account)
                .and_then(|(storage, _)| storage.get(&storage_key).cloned()))
        }

        fn bytecode_by_hash(&self, code_hash: H256) -> reth_interfaces::Result<Option<Bytecode>> {
            Ok(self.contracts.get(&code_hash).cloned())
        }

        fn proof(
            &self,
            _address: Address,
            _keys: &[H256],
        ) -> reth_interfaces::Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)> {
            todo!()
        }
    }

    #[test]
    fn sanity_execution() {
        // Got rlp block from: src/GeneralStateTestsFiller/stChainId/chainIdGasCostFiller.json

        let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice();
        let mut block = Block::decode(&mut block_rlp).unwrap();

        let mut ommer = Header::default();
        let ommer_beneficiary =
            Address::from_str("3000000000000000000000000000000000000000").unwrap();
        ommer.beneficiary = ommer_beneficiary;
        ommer.number = block.number;
        block.ommers = vec![ommer];

        let mut db = StateProviderTest::default();

        let account1 = Address::from_str("1000000000000000000000000000000000000000").unwrap();
        let account2 = Address::from_str("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap();
        let account3 = Address::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap();

        // pre state
        db.insert_account(
            account1,
            Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None },
            Some(hex!("5a465a905090036002900360015500").into()),
            HashMap::new(),
        );

        let account3_old_info = Account {
            balance: U256::from(0x3635c9adc5dea00000u128),
            nonce: 0x00,
            bytecode_hash: None,
        };

        db.insert_account(
            account3,
            Account {
                balance: U256::from(0x3635c9adc5dea00000u128),
                nonce: 0x00,
                bytecode_hash: None,
            },
            None,
            HashMap::new(),
        );

        // spec at berlin fork
        let chain_spec = Arc::new(ChainSpecBuilder::mainnet().berlin_activated().build());

        let db = SubState::new(State::new(db));

        // execute chain and verify receipts
        let mut executor = Executor::new(chain_spec, db);
        let post_state = executor.execute_and_verify_receipt(&block, U256::ZERO, None).unwrap();

        let base_block_reward = ETH_TO_WEI * 2;
        let block_reward = calc::block_reward(base_block_reward, 1);

        let account1_info = Account { balance: U256::ZERO, nonce: 0x00, bytecode_hash: None };
        let account2_info = Account {
            // Block reward decrease
            balance: U256::from(0x1bc16d674ece94bau128 - 0x1bc16d674ec80000u128),
            nonce: 0x00,
            bytecode_hash: None,
        };
        let account2_info_with_block_reward = Account {
            balance: account2_info.balance + block_reward,
            nonce: 0x00,
            bytecode_hash: None,
        };
        let account3_info = Account {
            balance: U256::from(0x3635c9adc5de996b46u128),
            nonce: 0x01,
            bytecode_hash: None,
        };
        let ommer_beneficiary_info = Account {
            nonce: 0,
            balance: calc::ommer_reward(base_block_reward, block.number, block.ommers[0].number),
            bytecode_hash: None,
        };

        // Check if cache is set
        // account1
        let db = executor.db();
        let cached_acc1 = db.accounts.get(&account1).unwrap();
        assert_eq!(cached_acc1.info.balance, account1_info.balance);
        assert_eq!(cached_acc1.info.nonce, account1_info.nonce);
        assert_eq!(cached_acc1.account_state, AccountState::Touched);
        assert_eq!(cached_acc1.storage.len(), 1);
        assert_eq!(cached_acc1.storage.get(&U256::from(1)), Some(&U256::from(2)));

        // account2 Block reward
        let cached_acc2 = db.accounts.get(&account2).unwrap();
        assert_eq!(cached_acc2.info.balance, account2_info.balance + block_reward);
        assert_eq!(cached_acc2.info.nonce, account2_info.nonce);
        assert_eq!(cached_acc2.account_state, AccountState::Touched);
        assert_eq!(cached_acc2.storage.len(), 0);

        // account3
        let cached_acc3 = db.accounts.get(&account3).unwrap();
        assert_eq!(cached_acc3.info.balance, account3_info.balance);
        assert_eq!(cached_acc3.info.nonce, account3_info.nonce);
        assert_eq!(cached_acc3.account_state, AccountState::Touched);
        assert_eq!(cached_acc3.storage.len(), 0);

        assert!(
            post_state.accounts().get(&account1).is_none(),
            "Account should not be present in post-state since it was not changed"
        );

        // Clone and sort to make the test deterministic
        assert_eq!(
            post_state.account_changes().inner,
            BTreeMap::from([(
                block.number,
                BTreeMap::from([
                    // New account
                    (account2, None),
                    // Changed account
                    (account3, Some(account3_old_info)),
                    // Ommer reward
                    (ommer_beneficiary, None)
                ])
            ),]),
            "Account changeset did not match"
        );
        assert_eq!(
            post_state.storage_changes().inner,
            BTreeMap::from([(
                block.number,
                BTreeMap::from([(
                    account1,
                    StorageTransition {
                        wipe: StorageWipe::None,
                        // Slot 1 changed from 0 to 2
                        storage: BTreeMap::from([(U256::from(1), U256::ZERO)])
                    }
                )])
            )]),
            "Storage changeset did not match"
        );

        // Check final post-state
        assert_eq!(
            post_state.storage(),
            &BTreeMap::from([(
                account1,
                Storage {
                    times_wiped: 0,
                    storage: BTreeMap::from([(U256::from(1), U256::from(2))])
                }
            )]),
            "Should have changed 1 storage slot"
        );
        assert_eq!(post_state.bytecodes().len(), 0, "Should have zero new bytecodes");

        let accounts = post_state.accounts();
        assert_eq!(
            accounts.len(),
            3,
            "Should have 4 accounts (account 2, 3 and the ommer beneficiary)"
        );
        assert_eq!(
            accounts.get(&account2).unwrap(),
            &Some(account2_info_with_block_reward),
            "Account 2 state is wrong"
        );
        assert_eq!(
            accounts.get(&account3).unwrap(),
            &Some(account3_info),
            "Account 3 state is wrong"
        );
        assert_eq!(
            accounts.get(&ommer_beneficiary).unwrap(),
            &Some(ommer_beneficiary_info),
            "Ommer beneficiary state is wrong"
        );
    }

    #[test]
    fn dao_hardfork_irregular_state_change() {
        let header = Header { number: 1, ..Header::default() };

        let mut db = StateProviderTest::default();

        let mut beneficiary_balance = 0;
        for (i, dao_address) in DAO_HARDKFORK_ACCOUNTS.iter().enumerate() {
            db.insert_account(
                *dao_address,
                Account { balance: U256::from(i), nonce: 0x00, bytecode_hash: None },
                None,
                HashMap::new(),
            );
            beneficiary_balance += i;
        }

        let chain_spec = Arc::new(
            ChainSpecBuilder::from(&*MAINNET)
                .homestead_activated()
                .with_fork(Hardfork::Dao, ForkCondition::Block(1))
                .build(),
        );

        let db = SubState::new(State::new(db));
        // execute chain and verify receipts
        let mut executor = Executor::new(chain_spec, db);
        let out = executor
            .execute_and_verify_receipt(
                &Block { header, body: vec![], ommers: vec![], withdrawals: None },
                U256::ZERO,
                None,
            )
            .unwrap();

        // Check if cache is set
        // beneficiary
        let db = executor.db();
        let dao_beneficiary = db.accounts.get(&DAO_HARDFORK_BENEFICIARY).unwrap();

        assert_eq!(dao_beneficiary.info.balance, U256::from(beneficiary_balance));
        for address in DAO_HARDKFORK_ACCOUNTS.iter() {
            let account = db.accounts.get(address).unwrap();
            assert_eq!(account.info.balance, U256::ZERO);
        }

        // check changesets
        let beneficiary_state = out.accounts().get(&DAO_HARDFORK_BENEFICIARY).unwrap().unwrap();
        assert_eq!(
            beneficiary_state,
            Account { balance: U256::from(beneficiary_balance), ..Default::default() },
        );
        for address in DAO_HARDKFORK_ACCOUNTS.iter() {
            let updated_account = out.accounts().get(address).unwrap().unwrap();
            assert_eq!(updated_account, Account { balance: U256::ZERO, ..Default::default() });
        }
    }

    #[test]
    fn test_selfdestruct() {
        // Modified version of eth test. Storage is added for selfdestructed account to see
        // that changeset is set.
        // Got rlp block from: src/GeneralStateTestsFiller/stArgsZeroOneBalance/suicideNonConst.json

        let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice();
        let block = Block::decode(&mut block_rlp).unwrap();
        let mut db = StateProviderTest::default();

        let address_caller = Address::from_str("a94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap();
        let address_selfdestruct =
            Address::from_str("095e7baea6a6c7c4c2dfeb977efac326af552d87").unwrap();

        // pre state
        let pre_account_caller = Account {
            balance: U256::from(0x0de0b6b3a7640000u64),
            nonce: 0x00,
            bytecode_hash: None,
        };

        db.insert_account(address_caller, pre_account_caller, None, HashMap::new());

        // insert account that will selfd

        let pre_account_selfdestroyed = Account {
            balance: U256::ZERO,
            nonce: 0x00,
            bytecode_hash: Some(H256(hex!(
                "56a7d44a4ecf086c34482ad1feb1007087fc56fae6dbefbd3f416002933f1705"
            ))),
        };

        let selfdestroyed_storage =
            BTreeMap::from([(H256::zero(), U256::ZERO), (H256::from_low_u64_be(1), U256::from(1))]);
        db.insert_account(
            address_selfdestruct,
            pre_account_selfdestroyed,
            Some(hex!("73095e7baea6a6c7c4c2dfeb977efac326af552d8731ff00").into()),
            selfdestroyed_storage.into_iter().collect::<HashMap<_, _>>(),
        );

        // spec at berlin fork
        let chain_spec = Arc::new(ChainSpecBuilder::mainnet().berlin_activated().build());

        let db = SubState::new(State::new(db));

        // execute chain and verify receipts
        let mut executor = Executor::new(chain_spec, db);
        let out = executor.execute_and_verify_receipt(&block, U256::ZERO, None).unwrap();

        assert_eq!(out.bytecodes().len(), 0, "Should have zero new bytecodes");

        let post_account_caller = Account {
            balance: U256::from(0x0de0b6b3a761cf60u64),
            nonce: 0x01,
            bytecode_hash: None,
        };

        assert_eq!(
            out.accounts().get(&address_caller).unwrap().unwrap(),
            post_account_caller,
            "Caller account has changed and fee is deduced"
        );

        assert_eq!(
            out.accounts().get(&address_selfdestruct).unwrap(),
            &None,
            "Selfdestructed account should have been deleted"
        );
        assert!(
            out.storage().get(&address_selfdestruct).unwrap().wiped(),
            "Selfdestructed account should have its storage wiped"
        );
    }

    // Test vector from https://github.com/ethereum/tests/blob/3156db5389921125bb9e04142d18e0e7b0cf8d64/BlockchainTests/EIPTests/bc4895-withdrawals/twoIdenticalIndexDifferentValidator.json
    #[test]
    fn test_withdrawals() {
        let block_rlp = hex!("f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa048cd9a5957e45beebf80278a5208b0cbe975ab4b4adb0da1509c67b26f2be3ffa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a04a220ebe55034d51f8a58175bb504b6ebf883105010a1f6d42e557c18bbd5d69c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da020194c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710");
        let block = Block::decode(&mut block_rlp.as_slice()).unwrap();
        let withdrawals = block.withdrawals.as_ref().unwrap();
        assert_eq!(withdrawals.len(), 4);

        let withdrawal_beneficiary =
            Address::from_str("c94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap();

        // spec at shanghai fork
        let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build());

        let db = SubState::new(State::new(StateProviderTest::default()));

        // execute chain and verify receipts
        let mut executor = Executor::new(chain_spec, db);
        let out = executor.execute_and_verify_receipt(&block, U256::ZERO, None).unwrap();

        let withdrawal_sum = withdrawals.iter().fold(U256::ZERO, |sum, w| sum + w.amount_wei());
        let beneficiary_account = executor.db().accounts.get(&withdrawal_beneficiary).unwrap();
        assert_eq!(beneficiary_account.info.balance, withdrawal_sum);
        assert_eq!(beneficiary_account.info.nonce, 0);
        assert_eq!(beneficiary_account.account_state, AccountState::StorageCleared);

        assert_eq!(
            out.accounts().get(&withdrawal_beneficiary).unwrap(),
            &Some(Account { nonce: 0, balance: withdrawal_sum, bytecode_hash: None }),
            "Withdrawal account should have gotten its balance set"
        );

        // Execute same block again
        let out = executor.execute_and_verify_receipt(&block, U256::ZERO, None).unwrap();

        assert_eq!(
            out.accounts().get(&withdrawal_beneficiary).unwrap(),
            &Some(Account {
                nonce: 0,
                balance: withdrawal_sum + withdrawal_sum,
                bytecode_hash: None
            }),
            "Withdrawal account should have gotten its balance set"
        );
    }

    #[test]
    fn test_account_state_preserved() {
        let account = Address::from_str("c94f5374fce5edbc8e2a8697c15331677e6ebf0b").unwrap();

        let mut db = StateProviderTest::default();
        db.insert_account(account, Account::default(), None, HashMap::default());

        let chain_spec = Arc::new(ChainSpecBuilder::mainnet().istanbul_activated().build());
        let db = SubState::new(State::new(db));

        let mut executor = Executor::new(chain_spec, db);
        // touch account
        executor.commit_changes(
            1,
            hash_map::HashMap::from([(account, DEFAULT_REVM_ACCOUNT.clone())]),
            true,
            &mut PostState::default(),
        );
        // destroy account
        executor.commit_changes(
            1,
            hash_map::HashMap::from([(
                account,
                RevmAccount {
                    is_destroyed: true,
                    is_touched: true,
                    ..DEFAULT_REVM_ACCOUNT.clone()
                },
            )]),
            true,
            &mut PostState::default(),
        );
        // re-create account
        executor.commit_changes(
            1,
            hash_map::HashMap::from([(
                account,
                RevmAccount {
                    is_touched: true,
                    storage_cleared: true,
                    ..DEFAULT_REVM_ACCOUNT.clone()
                },
            )]),
            true,
            &mut PostState::default(),
        );
        // touch account
        executor.commit_changes(
            1,
            hash_map::HashMap::from([(account, DEFAULT_REVM_ACCOUNT.clone())]),
            true,
            &mut PostState::default(),
        );

        let db = executor.db();

        let account = db.load_account(account).unwrap();
        assert_eq!(account.account_state, AccountState::StorageCleared);
    }

    /// If the account is created and destroyed within the same transaction, we shouldn't generate
    /// the changeset.
    #[test]
    fn test_account_created_destroyed() {
        let address = Address::random();

        let mut db = SubState::new(State::new(StateProviderTest::default()));
        db.load_account(address).unwrap(); // hot load the non-existing account

        let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build());
        let mut executor = Executor::new(chain_spec, db);
        let mut post_state = PostState::default();

        executor.commit_changes(
            1,
            hash_map::HashMap::from([(
                address,
                RevmAccount {
                    is_destroyed: true,
                    storage_cleared: true,
                    ..DEFAULT_REVM_ACCOUNT.clone()
                },
            )]),
            true,
            &mut post_state,
        );

        assert!(post_state.account_changes().is_empty());
    }

    /// If the account was touched, but remained unchanged over the course of multiple transactions,
    /// no changeset should be generated.
    #[test]
    fn test_touched_unchanged_account() {
        let address = Address::random();

        let db = SubState::new(State::new(StateProviderTest::default()));

        let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build());
        let mut executor = Executor::new(chain_spec, db);
        let mut post_state = PostState::default();

        executor.commit_changes(
            1,
            hash_map::HashMap::from([(
                address,
                RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() },
            )]),
            true,
            &mut post_state,
        );
        assert!(post_state.account_changes().is_empty());

        executor.commit_changes(
            1,
            hash_map::HashMap::from([(
                address,
                RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() },
            )]),
            true,
            &mut post_state,
        );
        assert_eq!(post_state.account_changes(), &AccountChanges::default());
    }

    #[test]
    fn test_state_clear_eip_touch_account() {
        let address = Address::random();

        let mut state_provider = StateProviderTest::default();
        state_provider.insert_account(address, Account::default(), None, HashMap::default());
        let mut db = SubState::new(State::new(state_provider));
        db.load_account(address).unwrap(); // hot load the account

        let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build());
        let mut executor = Executor::new(chain_spec, db);
        let mut post_state = PostState::default();

        // Touch an empty account before state clearing EIP. Nothing should happen.
        executor.commit_changes(
            1,
            hash_map::HashMap::from([(
                address,
                RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() },
            )]),
            false,
            &mut post_state,
        );
        assert_eq!(post_state.accounts(), &BTreeMap::default());
        assert_eq!(post_state.account_changes(), &AccountChanges::default());

        // Touch an empty account after state clearing EIP. The account should be destroyed.
        executor.commit_changes(
            2,
            hash_map::HashMap::from([(
                address,
                RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() },
            )]),
            true,
            &mut post_state,
        );
        assert_eq!(post_state.accounts(), &BTreeMap::from([(address, None)]));
        assert_eq!(
            post_state.account_changes(),
            &AccountChanges {
                size: 1,
                inner: BTreeMap::from([(2, BTreeMap::from([(address, Some(Account::default()))]))])
            }
        );
    }

    #[test]
    fn test_state_clear_eip_create_account() {
        let address1 = Address::random();
        let address2 = Address::random();
        let address3 = Address::random();
        let address4 = Address::random();

        let state_provider = StateProviderTest::default();
        let mut db = SubState::new(State::new(state_provider));
        db.load_account(address1).unwrap(); // hot load account 1

        let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build());
        let mut executor = Executor::new(chain_spec, db);

        // Create empty accounts before state clearing EIP.
        let mut post_state_before_state_clear = PostState::default();
        executor.commit_changes(
            1,
            hash_map::HashMap::from([
                (address1, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }),
                (address2, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }),
            ]),
            false,
            &mut post_state_before_state_clear,
        );
        assert_eq!(
            post_state_before_state_clear.accounts(),
            &BTreeMap::from([
                (address1, Some(Account::default())),
                (address2, Some(Account::default()))
            ])
        );
        assert_eq!(
            post_state_before_state_clear.account_changes(),
            &AccountChanges {
                size: 2,
                inner: BTreeMap::from([(1, BTreeMap::from([(address1, None), (address2, None)]))])
            }
        );

        // Empty accounts should not be created after state clearing EIP.
        let mut post_state_after_state_clear = PostState::default();
        executor.commit_changes(
            2,
            hash_map::HashMap::from([
                (address3, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }),
                (address4, RevmAccount { is_touched: true, ..DEFAULT_REVM_ACCOUNT.clone() }),
            ]),
            true,
            &mut post_state_after_state_clear,
        );
        assert_eq!(post_state_after_state_clear.accounts(), &BTreeMap::default());
        assert_eq!(post_state_after_state_clear.account_changes(), &AccountChanges::default());
    }
}
