//! Data shared between program runtime and built-in programs as well as SBF programs.
#![deny(clippy::indexing_slicing)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

use std::{
    cell::{Ref, RefCell, RefMut},
    collections::HashSet,
    rc::Rc,
};

use rialo_s_account::{ReadableAccount, StoredAccount};
use rialo_s_instruction::error::InstructionError;
use rialo_s_pubkey::Pubkey;
#[cfg(all(
    not(target_os = "solana"),
    feature = "debug-signature",
    debug_assertions
))]
use rialo_s_signature::Signature;
#[cfg(not(target_os = "solana"))]
use {rialo_s_rent::Rent, std::mem::MaybeUninit};

// Inlined to avoid rialo_s_system_interface dep
#[cfg(not(target_os = "solana"))]
const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
#[cfg(test)]
static_assertions::const_assert_eq!(
    MAX_PERMITTED_DATA_LENGTH,
    rialo_s_system_interface::MAX_PERMITTED_DATA_LENGTH
);

// Inlined to avoid rialo_s_system_interface dep
#[cfg(not(target_os = "solana"))]
const MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION: i64 =
    MAX_PERMITTED_DATA_LENGTH as i64 * 2;
#[cfg(test)]
static_assertions::const_assert_eq!(
    MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
    rialo_s_system_interface::MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
);

// Inlined to avoid rialo_s_account_info dep
#[cfg(not(target_os = "solana"))]
const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10;
#[cfg(test)]
static_assertions::const_assert_eq!(
    MAX_PERMITTED_DATA_INCREASE,
    rialo_s_account_info::MAX_PERMITTED_DATA_INCREASE
);

/// Index of an account inside of the TransactionContext or an InstructionContext.
pub type IndexOfAccount = u16;

/// Contains account meta data which varies between instruction.
///
/// It also contains indices to other structures for faster lookup.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InstructionAccount {
    /// Points to the account and its key in the `TransactionContext`
    pub index_in_transaction: IndexOfAccount,
    /// Points to the first occurrence in the parent `InstructionContext`
    ///
    /// This excludes the program accounts.
    pub index_in_caller: IndexOfAccount,
    /// Points to the first occurrence in the current `InstructionContext`
    ///
    /// This excludes the program accounts.
    pub index_in_callee: IndexOfAccount,
    /// Is this account supposed to sign
    pub is_signer: bool,
    /// Is this account allowed to become writable
    pub is_writable: bool,
}

/// An account key and the matching account
pub type TransactionAccount = (Pubkey, StoredAccount);

#[derive(Clone, Debug, PartialEq)]
pub struct TransactionAccounts {
    accounts: Vec<RefCell<StoredAccount>>,
    touched_flags: RefCell<Vec<bool>>,
}

impl TransactionAccounts {
    #[cfg(not(target_os = "solana"))]
    fn new(accounts: Vec<RefCell<StoredAccount>>) -> TransactionAccounts {
        TransactionAccounts {
            touched_flags: RefCell::new(vec![false; accounts.len()]),
            accounts,
        }
    }

    /// Sets whether the account at the given index is executable.
    /// Updates the underlying `StoredAccount` variant (converting between Data/Executable).
    /// NOTE: Do not call this while holding a BorrowedAccount for the same index.
    /// Use BorrowedAccount::set_executable() instead.
    #[cfg(not(target_os = "solana"))]
    pub fn set_executable(
        &self,
        index: IndexOfAccount,
        executable: bool,
    ) -> Result<(), InstructionError> {
        let account_cell = self
            .accounts
            .get(index as usize)
            .ok_or(InstructionError::NotEnoughAccountKeys)?;
        account_cell
            .borrow_mut()
            .set_executable(executable)
            .map_err(|_| InstructionError::InvalidAccountData)?;
        Ok(())
    }

    fn len(&self) -> usize {
        self.accounts.len()
    }

    pub fn get(&self, index: IndexOfAccount) -> Option<&RefCell<StoredAccount>> {
        self.accounts.get(index as usize)
    }

    #[cfg(not(target_os = "solana"))]
    pub fn touch(&self, index: IndexOfAccount) -> Result<(), InstructionError> {
        *self
            .touched_flags
            .borrow_mut()
            .get_mut(index as usize)
            .ok_or(InstructionError::NotEnoughAccountKeys)? = true;
        Ok(())
    }

    #[cfg(not(target_os = "solana"))]
    pub fn touched_count(&self) -> usize {
        self.touched_flags
            .borrow()
            .iter()
            .fold(0usize, |accumulator, was_touched| {
                accumulator.saturating_add(*was_touched as usize)
            })
    }

    pub fn try_borrow(
        &self,
        index: IndexOfAccount,
    ) -> Result<Ref<'_, StoredAccount>, InstructionError> {
        self.accounts
            .get(index as usize)
            .ok_or(InstructionError::MissingAccount)?
            .try_borrow()
            .map_err(|_| InstructionError::AccountBorrowFailed)
    }

    pub fn try_borrow_mut(
        &self,
        index: IndexOfAccount,
    ) -> Result<RefMut<'_, StoredAccount>, InstructionError> {
        self.accounts
            .get(index as usize)
            .ok_or(InstructionError::MissingAccount)?
            .try_borrow_mut()
            .map_err(|_| InstructionError::AccountBorrowFailed)
    }

    pub fn into_accounts(self) -> Vec<StoredAccount> {
        self.accounts
            .into_iter()
            .map(|account| account.into_inner())
            .collect()
    }

    pub fn destruct(self) -> (Vec<StoredAccount>, Vec<bool>) {
        (
            self.accounts
                .into_iter()
                .map(|account| account.into_inner())
                .collect(),
            self.touched_flags.into_inner(),
        )
    }

    pub fn push(&mut self, account: RefCell<StoredAccount>) {
        self.accounts.push(account);
        self.touched_flags.borrow_mut().push(false);
    }
}

/// Loaded transaction shared between runtime and programs.
///
/// This context is valid for the entire duration of a transaction being processed.
#[derive(Debug, Clone, PartialEq)]
pub struct TransactionContext {
    account_keys: Vec<Pubkey>,
    accounts: TransactionAccounts,

    // TODO: remove once ebpf is gone
    accounts_rc: Rc<TransactionAccounts>,

    instruction_stack_capacity: usize,
    instruction_trace_capacity: usize,
    instruction_stack: Vec<usize>,
    instruction_trace: Vec<InstructionContext>,
    return_data: TransactionReturnData,
    accounts_resize_delta: RefCell<i64>,
    #[cfg(not(target_os = "solana"))]
    remove_accounts_executable_flag_checks: bool,
    #[cfg(not(target_os = "solana"))]
    rent: Rent,
    /// Useful for debugging to filter by or to look it up on the explorer
    #[cfg(all(
        not(target_os = "solana"),
        feature = "debug-signature",
        debug_assertions
    ))]
    signature: Signature,
}

impl TransactionContext {
    /// Constructs a new TransactionContext
    ///
    /// The `transaction_accounts` parameter accepts any type that can be converted
    /// into `StoredAccount`, including `AccountSharedData` for convenience.
    #[cfg(not(target_os = "solana"))]
    pub fn new<T: Into<StoredAccount>>(
        transaction_accounts: Vec<(Pubkey, T)>,
        rent: Rent,
        instruction_stack_capacity: usize,
        instruction_trace_capacity: usize,
    ) -> Self {
        let (account_keys, accounts): (Vec<_>, Vec<_>) = transaction_accounts
            .into_iter()
            .map(|(key, account)| (key, RefCell::new(account.into())))
            .unzip();
        let accounts_rc = Rc::new(TransactionAccounts::new(accounts.clone()));

        Self {
            account_keys,
            accounts: TransactionAccounts::new(accounts),
            accounts_rc,
            instruction_stack_capacity,
            instruction_trace_capacity,
            instruction_stack: Vec::with_capacity(instruction_stack_capacity),
            instruction_trace: vec![InstructionContext::default()],
            return_data: TransactionReturnData::default(),
            accounts_resize_delta: RefCell::new(0),
            remove_accounts_executable_flag_checks: true,
            rent,
            #[cfg(all(
                not(target_os = "solana"),
                feature = "debug-signature",
                debug_assertions
            ))]
            signature: Signature::default(),
        }
    }

    #[cfg(not(target_os = "solana"))]
    pub fn set_remove_accounts_executable_flag_checks(&mut self, enabled: bool) {
        self.remove_accounts_executable_flag_checks = enabled;
    }

    #[cfg(not(target_os = "solana"))]
    pub fn add_account(&mut self, account: TransactionAccount) {
        let index_in_transaction = self.accounts.len();
        let (pubkey, account) = account;
        let kelvins = account.kelvins();

        self.accounts.push(RefCell::new(account));
        self.account_keys.push(pubkey);

        let instruction_context = self.get_current_instruction_context_mut().unwrap();
        let index_in_callee = instruction_context.instruction_accounts.len();

        instruction_context.add_instruction_account(
            InstructionAccount {
                index_in_transaction: index_in_transaction as IndexOfAccount,
                index_in_caller: 1,
                index_in_callee: index_in_callee as IndexOfAccount,
                is_signer: false,
                is_writable: true,
            },
            kelvins,
        );
    }

    /// Used in mock_process_instruction
    #[cfg(not(target_os = "solana"))]
    pub fn deconstruct_without_keys(self) -> Result<Vec<StoredAccount>, InstructionError> {
        if !self.instruction_stack.is_empty() {
            return Err(InstructionError::CallDepth);
        }

        Ok(self.accounts.into_accounts())
    }

    #[cfg(not(target_os = "solana"))]
    pub fn accounts(&self) -> &TransactionAccounts {
        &self.accounts
    }

    /// Touch fee payer.
    ///
    /// This is required for OCC executor as all transactions are executed in parallel
    /// and for sponsored transactions the fee payer may not get touched during execution
    /// since it was "touched" by the `TransactionBatchProcessor` when it deducted the fee.
    ///
    /// The fee payer must be explicitly touched if the transaction is executed in the OCC
    /// batch so that the scheduler is able to validate and re-execute transactions that
    /// in the same batch use the fee payer.
    #[cfg(not(target_os = "solana"))]
    pub fn touch_fee_payer(&self) {
        // fee payer is always the first account
        self.accounts.touch(0).expect("fee payer to exist");
    }

    // TODO: remove once ebpf is gone
    #[cfg(not(target_os = "solana"))]
    pub fn accounts_rc(&self) -> &Rc<TransactionAccounts> {
        &self.accounts_rc
    }

    pub fn account_keys(&self) -> impl Iterator<Item = &Pubkey> {
        self.account_keys.iter()
    }

    /// Stores the signature of the current transaction
    #[cfg(all(
        not(target_os = "solana"),
        feature = "debug-signature",
        debug_assertions
    ))]
    pub fn set_signature(&mut self, signature: &Signature) {
        self.signature = *signature;
    }

    /// Returns the signature of the current transaction
    #[cfg(all(
        not(target_os = "solana"),
        feature = "debug-signature",
        debug_assertions
    ))]
    pub fn get_signature(&self) -> &Signature {
        &self.signature
    }

    /// Returns the total number of accounts loaded in this Transaction
    pub fn get_number_of_accounts(&self) -> IndexOfAccount {
        self.accounts.len() as IndexOfAccount
    }

    /// Searches for an account by its key
    pub fn get_key_of_account_at_index(
        &self,
        index_in_transaction: IndexOfAccount,
    ) -> Result<&Pubkey, InstructionError> {
        self.account_keys
            .get(index_in_transaction as usize)
            .ok_or(InstructionError::NotEnoughAccountKeys)
    }

    /// Searches for an account by its key
    #[cfg(not(target_os = "solana"))]
    pub fn get_account_at_index(
        &self,
        index_in_transaction: IndexOfAccount,
    ) -> Result<&RefCell<StoredAccount>, InstructionError> {
        self.accounts
            .get(index_in_transaction)
            .ok_or(InstructionError::NotEnoughAccountKeys)
    }

    /// Searches for an account by its key
    pub fn find_index_of_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
        self.account_keys
            .iter()
            .position(|key| key == pubkey)
            .map(|index| index as IndexOfAccount)
    }

    /// Searches for a program account by its key
    pub fn find_index_of_program_account(&self, pubkey: &Pubkey) -> Option<IndexOfAccount> {
        self.account_keys
            .iter()
            .rposition(|key| key == pubkey)
            .map(|index| index as IndexOfAccount)
    }

    /// Gets the max length of the InstructionContext trace
    pub fn get_instruction_trace_capacity(&self) -> usize {
        self.instruction_trace_capacity
    }

    /// Returns the instruction trace length.
    ///
    /// Not counting the last empty InstructionContext which is always pre-reserved for the next instruction.
    /// See also `get_next_instruction_context()`.
    pub fn get_instruction_trace_length(&self) -> usize {
        self.instruction_trace.len().saturating_sub(1)
    }

    /// Gets an InstructionContext by its index in the trace
    pub fn get_instruction_context_at_index_in_trace(
        &self,
        index_in_trace: usize,
    ) -> Result<&InstructionContext, InstructionError> {
        self.instruction_trace
            .get(index_in_trace)
            .ok_or(InstructionError::CallDepth)
    }

    pub fn get_instruction_context_at_index_in_trace_mut(
        &mut self,
        index_in_trace: usize,
    ) -> Result<&mut InstructionContext, InstructionError> {
        self.instruction_trace
            .get_mut(index_in_trace)
            .ok_or(InstructionError::CallDepth)
    }

    /// Gets an InstructionContext by its nesting level in the stack
    pub fn get_instruction_context_at_nesting_level(
        &self,
        nesting_level: usize,
    ) -> Result<&InstructionContext, InstructionError> {
        let index_in_trace = *self
            .instruction_stack
            .get(nesting_level)
            .ok_or(InstructionError::CallDepth)?;
        let instruction_context = self.get_instruction_context_at_index_in_trace(index_in_trace)?;
        debug_assert_eq!(instruction_context.nesting_level, nesting_level);
        Ok(instruction_context)
    }

    pub fn get_instruction_context_at_nesting_level_mut(
        &mut self,
        nesting_level: usize,
    ) -> Result<&mut InstructionContext, InstructionError> {
        let index_in_trace = *self
            .instruction_stack
            .get(nesting_level)
            .ok_or(InstructionError::CallDepth)?;
        let instruction_context =
            self.get_instruction_context_at_index_in_trace_mut(index_in_trace)?;
        debug_assert_eq!(instruction_context.nesting_level, nesting_level);
        Ok(instruction_context)
    }

    /// Gets the max height of the InstructionContext stack
    pub fn get_instruction_stack_capacity(&self) -> usize {
        self.instruction_stack_capacity
    }

    /// Gets instruction stack height, top-level instructions are height
    /// `rialo_s_sdk::instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
    pub fn get_instruction_context_stack_height(&self) -> usize {
        self.instruction_stack.len()
    }

    /// Returns the current InstructionContext
    pub fn get_current_instruction_context(&self) -> Result<&InstructionContext, InstructionError> {
        let level = self
            .get_instruction_context_stack_height()
            .checked_sub(1)
            .ok_or(InstructionError::CallDepth)?;
        self.get_instruction_context_at_nesting_level(level)
    }

    pub fn get_current_instruction_context_mut(
        &mut self,
    ) -> Result<&mut InstructionContext, InstructionError> {
        let level = self
            .get_instruction_context_stack_height()
            .checked_sub(1)
            .ok_or(InstructionError::CallDepth)?;
        self.get_instruction_context_at_nesting_level_mut(level)
    }
    /// Returns the InstructionContext to configure for the next invocation.
    ///
    /// The last InstructionContext is always empty and pre-reserved for the next instruction.
    pub fn get_next_instruction_context(
        &mut self,
    ) -> Result<&mut InstructionContext, InstructionError> {
        self.instruction_trace
            .last_mut()
            .ok_or(InstructionError::CallDepth)
    }

    /// Pushes the next InstructionContext
    #[cfg(not(target_os = "solana"))]
    pub fn push(&mut self) -> Result<(), InstructionError> {
        let nesting_level = self.get_instruction_context_stack_height();
        let caller_instruction_context = self
            .instruction_trace
            .last()
            .ok_or(InstructionError::CallDepth)?;
        let callee_instruction_accounts_kelvin_sum =
            self.instruction_accounts_kelvin_sum(caller_instruction_context)?;
        if !self.instruction_stack.is_empty() {
            let caller_instruction_context = self.get_current_instruction_context()?;
            let original_caller_instruction_accounts_kelvin_sum =
                caller_instruction_context.instruction_accounts_kelvin_sum;
            let current_caller_instruction_accounts_kelvin_sum =
                self.instruction_accounts_kelvin_sum(caller_instruction_context)?;
            if original_caller_instruction_accounts_kelvin_sum
                != current_caller_instruction_accounts_kelvin_sum
            {
                return Err(InstructionError::UnbalancedInstruction);
            }
        }
        {
            let instruction_context = self.get_next_instruction_context()?;
            instruction_context.nesting_level = nesting_level;
            instruction_context.instruction_accounts_kelvin_sum =
                callee_instruction_accounts_kelvin_sum;
        }
        let index_in_trace = self.get_instruction_trace_length();
        if index_in_trace >= self.instruction_trace_capacity {
            return Err(InstructionError::MaxInstructionTraceLengthExceeded);
        }
        self.instruction_trace.push(InstructionContext::default());
        if nesting_level >= self.instruction_stack_capacity {
            return Err(InstructionError::CallDepth);
        }
        self.instruction_stack.push(index_in_trace);
        Ok(())
    }

    /// Pops the current InstructionContext
    #[cfg(not(target_os = "solana"))]
    pub fn pop(&mut self) -> Result<(), InstructionError> {
        if self.instruction_stack.is_empty() {
            return Err(InstructionError::CallDepth);
        }
        // Verify (before we pop) that the total sum of all kelvins in this instruction did not change
        let detected_an_unbalanced_instruction =
            self.get_current_instruction_context()
                .and_then(|instruction_context| {
                    // Verify all executable accounts have no outstanding refs
                    for account_index in instruction_context.program_accounts.iter() {
                        self.get_account_at_index(*account_index)?
                            .try_borrow_mut()
                            .map_err(|_| InstructionError::AccountBorrowOutstanding)?;
                    }
                    self.instruction_accounts_kelvin_sum(instruction_context)
                        .map(|instruction_accounts_kelvin_sum| {
                            instruction_context.instruction_accounts_kelvin_sum
                                != instruction_accounts_kelvin_sum
                        })
                });
        // Always pop, even if we `detected_an_unbalanced_instruction`
        self.instruction_stack.pop();
        if detected_an_unbalanced_instruction? {
            Err(InstructionError::UnbalancedInstruction)
        } else {
            Ok(())
        }
    }

    /// Gets the return data of the current InstructionContext or any above
    pub fn get_return_data(&self) -> (&Pubkey, &[u8]) {
        (&self.return_data.program_id, &self.return_data.data)
    }

    /// Set the return data of the current InstructionContext
    pub fn set_return_data(
        &mut self,
        program_id: Pubkey,
        data: Vec<u8>,
    ) -> Result<(), InstructionError> {
        self.return_data = TransactionReturnData { program_id, data };
        Ok(())
    }

    /// Calculates the sum of all kelvins within an instruction
    #[cfg(not(target_os = "solana"))]
    fn instruction_accounts_kelvin_sum(
        &self,
        instruction_context: &InstructionContext,
    ) -> Result<u128, InstructionError> {
        let mut instruction_accounts_kelvin_sum: u128 = 0;
        for instruction_account_index in 0..instruction_context.get_number_of_instruction_accounts()
        {
            if instruction_context
                .is_instruction_account_duplicate(instruction_account_index)?
                .is_some()
            {
                continue; // Skip duplicate account
            }
            let index_in_transaction = instruction_context
                .get_index_of_instruction_account_in_transaction(instruction_account_index)?;
            instruction_accounts_kelvin_sum = (self
                .get_account_at_index(index_in_transaction)?
                .try_borrow()
                .map_err(|_| InstructionError::AccountBorrowOutstanding)?
                .kelvins() as u128)
                .checked_add(instruction_accounts_kelvin_sum)
                .ok_or(InstructionError::ArithmeticOverflow)?;
        }
        Ok(instruction_accounts_kelvin_sum)
    }

    /// Returns the accounts resize delta
    pub fn accounts_resize_delta(&self) -> Result<i64, InstructionError> {
        self.accounts_resize_delta
            .try_borrow()
            .map_err(|_| InstructionError::GenericError)
            .map(|value_ref| *value_ref)
    }
}

/// Return data at the end of a transaction
#[cfg_attr(
    feature = "serde",
    derive(serde_derive::Deserialize, serde_derive::Serialize)
)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TransactionReturnData {
    pub program_id: Pubkey,
    pub data: Vec<u8>,
}

/// Loaded instruction shared between runtime and programs.
///
/// This context is valid for the entire duration of a (possibly cross program) instruction being processed.
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct InstructionContext {
    nesting_level: usize,
    instruction_accounts_kelvin_sum: u128,
    program_accounts: Vec<IndexOfAccount>,
    instruction_accounts: Vec<InstructionAccount>,
    instruction_data: Vec<u8>,
}

impl InstructionContext {
    /// Used together with TransactionContext::get_next_instruction_context()
    #[cfg(not(target_os = "solana"))]
    pub fn configure(
        &mut self,
        program_accounts: &[IndexOfAccount],
        instruction_accounts: &[InstructionAccount],
        instruction_data: &[u8],
    ) {
        self.program_accounts = program_accounts.to_vec();
        self.instruction_accounts = instruction_accounts.to_vec();
        self.instruction_data = instruction_data.to_vec();
    }

    /// Add new instruction account loaded during runtime of the program.
    pub fn add_instruction_account(&mut self, account: InstructionAccount, kelvins: u64) {
        self.instruction_accounts_kelvin_sum += kelvins as u128;
        self.instruction_accounts.push(account);
    }

    /// How many Instructions were on the stack after this one was pushed
    ///
    /// That is the number of nested parent Instructions plus one (itself).
    pub fn get_stack_height(&self) -> usize {
        self.nesting_level.saturating_add(1)
    }

    /// Number of program accounts
    pub fn get_number_of_program_accounts(&self) -> IndexOfAccount {
        self.program_accounts.len() as IndexOfAccount
    }

    /// Number of accounts in this Instruction (without program accounts)
    pub fn get_number_of_instruction_accounts(&self) -> IndexOfAccount {
        self.instruction_accounts.len() as IndexOfAccount
    }

    /// Assert that enough accounts were supplied to this Instruction
    pub fn check_number_of_instruction_accounts(
        &self,
        expected_at_least: IndexOfAccount,
    ) -> Result<(), InstructionError> {
        if self.get_number_of_instruction_accounts() < expected_at_least {
            Err(InstructionError::NotEnoughAccountKeys)
        } else {
            Ok(())
        }
    }

    /// Data parameter for the programs `process_instruction` handler
    pub fn get_instruction_data(&self) -> &[u8] {
        &self.instruction_data
    }

    /// Searches for a program account by its key
    pub fn find_index_of_program_account(
        &self,
        transaction_context: &TransactionContext,
        pubkey: &Pubkey,
    ) -> Option<IndexOfAccount> {
        self.program_accounts
            .iter()
            .position(|index_in_transaction| {
                transaction_context
                    .account_keys
                    .get(*index_in_transaction as usize)
                    == Some(pubkey)
            })
            .map(|index| index as IndexOfAccount)
    }

    /// Searches for an instruction account by its key
    pub fn find_index_of_instruction_account(
        &self,
        transaction_context: &TransactionContext,
        pubkey: &Pubkey,
    ) -> Option<IndexOfAccount> {
        self.instruction_accounts
            .iter()
            .position(|instruction_account| {
                transaction_context
                    .account_keys
                    .get(instruction_account.index_in_transaction as usize)
                    == Some(pubkey)
            })
            .map(|index| index as IndexOfAccount)
    }

    /// Translates the given instruction wide program_account_index into a transaction wide index
    pub fn get_index_of_program_account_in_transaction(
        &self,
        program_account_index: IndexOfAccount,
    ) -> Result<IndexOfAccount, InstructionError> {
        Ok(*self
            .program_accounts
            .get(program_account_index as usize)
            .ok_or(InstructionError::NotEnoughAccountKeys)?)
    }

    /// Translates the given instruction wide instruction_account_index into a transaction wide index
    pub fn get_index_of_instruction_account_in_transaction(
        &self,
        instruction_account_index: IndexOfAccount,
    ) -> Result<IndexOfAccount, InstructionError> {
        Ok(self
            .instruction_accounts
            .get(instruction_account_index as usize)
            .ok_or(InstructionError::NotEnoughAccountKeys)?
            .index_in_transaction as IndexOfAccount)
    }

    /// Returns `Some(instruction_account_index)` if this is a duplicate
    /// and `None` if it is the first account with this key
    pub fn is_instruction_account_duplicate(
        &self,
        instruction_account_index: IndexOfAccount,
    ) -> Result<Option<IndexOfAccount>, InstructionError> {
        let index_in_callee = self
            .instruction_accounts
            .get(instruction_account_index as usize)
            .ok_or(InstructionError::NotEnoughAccountKeys)?
            .index_in_callee;
        Ok(if index_in_callee == instruction_account_index {
            None
        } else {
            Some(index_in_callee)
        })
    }

    /// Gets the key of the last program account of this Instruction
    pub fn get_last_program_key<'a, 'b: 'a>(
        &'a self,
        transaction_context: &'b TransactionContext,
    ) -> Result<&'b Pubkey, InstructionError> {
        self.get_index_of_program_account_in_transaction(
            self.get_number_of_program_accounts().saturating_sub(1),
        )
        .and_then(|index_in_transaction| {
            transaction_context.get_key_of_account_at_index(index_in_transaction)
        })
    }

    fn try_borrow_account<'a, 'b: 'a>(
        &'a self,
        transaction_context: &'b TransactionContext,
        index_in_transaction: IndexOfAccount,
        index_in_instruction: IndexOfAccount,
    ) -> Result<BorrowedAccount<'a>, InstructionError> {
        let account = transaction_context
            .accounts
            .get(index_in_transaction)
            .ok_or(InstructionError::MissingAccount)?
            .try_borrow_mut()
            .map_err(|_| InstructionError::AccountBorrowFailed)?;
        Ok(BorrowedAccount {
            transaction_context,
            instruction_context: self,
            index_in_transaction,
            index_in_instruction,
            account,
        })
    }

    /// Gets the last program account of this Instruction
    pub fn try_borrow_last_program_account<'a, 'b: 'a>(
        &'a self,
        transaction_context: &'b TransactionContext,
    ) -> Result<BorrowedAccount<'a>, InstructionError> {
        let result = self.try_borrow_program_account(
            transaction_context,
            self.get_number_of_program_accounts().saturating_sub(1),
        );
        debug_assert!(result.is_ok());
        result
    }

    /// Tries to borrow a program account from this Instruction
    pub fn try_borrow_program_account<'a, 'b: 'a>(
        &'a self,
        transaction_context: &'b TransactionContext,
        program_account_index: IndexOfAccount,
    ) -> Result<BorrowedAccount<'a>, InstructionError> {
        let index_in_transaction =
            self.get_index_of_program_account_in_transaction(program_account_index)?;
        self.try_borrow_account(
            transaction_context,
            index_in_transaction,
            program_account_index,
        )
    }

    /// Gets an instruction account of this Instruction
    pub fn try_borrow_instruction_account<'a, 'b: 'a>(
        &'a self,
        transaction_context: &'b TransactionContext,
        instruction_account_index: IndexOfAccount,
    ) -> Result<BorrowedAccount<'a>, InstructionError> {
        let index_in_transaction =
            self.get_index_of_instruction_account_in_transaction(instruction_account_index)?;
        self.try_borrow_account(
            transaction_context,
            index_in_transaction,
            self.get_number_of_program_accounts()
                .saturating_add(instruction_account_index),
        )
    }

    /// Returns whether an instruction account is a signer
    pub fn is_instruction_account_signer(
        &self,
        instruction_account_index: IndexOfAccount,
    ) -> Result<bool, InstructionError> {
        Ok(self
            .instruction_accounts
            .get(instruction_account_index as usize)
            .ok_or(InstructionError::MissingAccount)?
            .is_signer)
    }

    /// Returns whether an instruction account is writable
    pub fn is_instruction_account_writable(
        &self,
        instruction_account_index: IndexOfAccount,
    ) -> Result<bool, InstructionError> {
        Ok(self
            .instruction_accounts
            .get(instruction_account_index as usize)
            .ok_or(InstructionError::MissingAccount)?
            .is_writable)
    }

    /// Calculates the set of all keys of signer instruction accounts in this Instruction
    pub fn get_signers(
        &self,
        transaction_context: &TransactionContext,
    ) -> Result<HashSet<Pubkey>, InstructionError> {
        let mut result = HashSet::new();
        for instruction_account in self.instruction_accounts.iter() {
            if instruction_account.is_signer {
                result.insert(
                    *transaction_context
                        .get_key_of_account_at_index(instruction_account.index_in_transaction)?,
                );
            }
        }
        Ok(result)
    }
}

/// Shared account borrowed from the TransactionContext and an InstructionContext.
#[derive(Debug)]
pub struct BorrowedAccount<'a> {
    transaction_context: &'a TransactionContext,
    instruction_context: &'a InstructionContext,
    index_in_transaction: IndexOfAccount,
    index_in_instruction: IndexOfAccount,
    account: RefMut<'a, StoredAccount>,
}

impl BorrowedAccount<'_> {
    /// Returns the transaction context
    pub fn transaction_context(&self) -> &TransactionContext {
        self.transaction_context
    }

    /// Returns the index of this account (transaction wide)
    #[inline]
    pub fn get_index_in_transaction(&self) -> IndexOfAccount {
        self.index_in_transaction
    }

    /// Returns the public key of this account (transaction wide)
    #[inline]
    pub fn get_key(&self) -> &Pubkey {
        self.transaction_context
            .get_key_of_account_at_index(self.index_in_transaction)
            .unwrap()
    }

    /// Returns the owner of this account (transaction wide)
    #[inline]
    pub fn get_owner(&self) -> &Pubkey {
        self.account.owner()
    }

    /// Assignes the owner of this account (transaction wide)
    #[cfg(not(target_os = "solana"))]
    pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
        // Only the owner can assign a new owner
        if !self.is_owned_by_current_program() {
            return Err(InstructionError::ModifiedProgramId);
        }
        // and only if the account is writable
        if !self.is_writable() {
            return Err(InstructionError::ModifiedProgramId);
        }
        // and only if the account is not executable
        if self.is_executable_internal() {
            return Err(InstructionError::ModifiedProgramId);
        }
        // and only if the data is zero-initialized or empty
        if !is_zeroed(self.get_data()) {
            return Err(InstructionError::ModifiedProgramId);
        }
        // don't touch the account if the owner does not change
        if self.get_owner().to_bytes() == pubkey {
            return Ok(());
        }
        self.touch()?;
        self.account.try_copy_into_owner_from_slice(pubkey)?;
        Ok(())
    }

    /// Returns the number of kelvins of this account (transaction wide)
    #[inline]
    pub fn get_kelvins(&self) -> u64 {
        self.account.kelvins()
    }

    /// Overwrites the number of kelvins of this account (transaction wide)
    #[cfg(not(target_os = "solana"))]
    pub fn set_kelvins(&mut self, kelvins: u64) -> Result<(), InstructionError> {
        // An account not owned by the program cannot have its balance decrease
        if !self.is_owned_by_current_program() && kelvins < self.get_kelvins() {
            return Err(InstructionError::ExternalAccountKelvinSpend);
        }
        // The balance of read-only may not change
        if !self.is_writable() {
            return Err(InstructionError::ReadonlyKelvinChange);
        }
        // The balance of executable accounts may not change
        if self.is_executable_internal() {
            return Err(InstructionError::ExecutableKelvinChange);
        }
        // don't touch the account if the kelvins do not change
        if self.get_kelvins() == kelvins {
            return Ok(());
        }
        self.touch()?;
        self.account.set_kelvins(kelvins);
        Ok(())
    }

    /// Adds kelvins to this account (transaction wide)
    #[cfg(not(target_os = "solana"))]
    pub fn checked_add_kelvins(&mut self, kelvins: u64) -> Result<(), InstructionError> {
        self.set_kelvins(
            self.get_kelvins()
                .checked_add(kelvins)
                .ok_or(InstructionError::ArithmeticOverflow)?,
        )
    }

    /// Subtracts kelvins from this account (transaction wide)
    #[cfg(not(target_os = "solana"))]
    pub fn checked_sub_kelvins(&mut self, kelvins: u64) -> Result<(), InstructionError> {
        self.set_kelvins(
            self.get_kelvins()
                .checked_sub(kelvins)
                .ok_or(InstructionError::ArithmeticOverflow)?,
        )
    }

    /// Returns a read-only slice of the account data (transaction wide)
    #[inline]
    pub fn get_data(&self) -> &[u8] {
        self.account.data()
    }

    /// Returns a writable slice of the account data (transaction wide)
    #[cfg(not(target_os = "solana"))]
    pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
        self.can_data_be_changed()?;
        self.touch()?;
        self.make_data_mut();
        self.account.try_data_as_mut_slice().map_err(Into::into)
    }

    /// Returns the spare capacity of the vector backing the account data.
    ///
    /// This method should only ever be used during CPI, where after a shrinking
    /// realloc we want to zero the spare capacity.
    #[cfg(not(target_os = "solana"))]
    pub fn spare_data_capacity_mut(&mut self) -> Result<&mut [MaybeUninit<u8>], InstructionError> {
        debug_assert!(!self.account.is_shared());
        self.account
            .try_spare_data_capacity_mut()
            .map_err(Into::into)
    }

    /// Overwrites the account data and size (transaction wide).
    ///
    /// You should always prefer set_data_from_slice(). Calling this method is
    /// currently safe but requires some special casing during CPI when direct
    /// account mapping is enabled.
    #[cfg(all(
        not(target_os = "solana"),
        any(test, feature = "dev-context-only-utils")
    ))]
    pub fn set_data(&mut self, data: Vec<u8>) -> Result<(), InstructionError> {
        self.can_data_be_resized(data.len())?;
        self.can_data_be_changed()?;
        self.touch()?;

        self.update_accounts_resize_delta(data.len())?;
        self.account.try_set_data(data)?;
        Ok(())
    }

    /// Overwrites the account data and size (transaction wide).
    ///
    /// Call this when you have a slice of data you do not own and want to
    /// replace the account data with it.
    #[cfg(not(target_os = "solana"))]
    pub fn set_data_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
        self.can_data_be_resized(data.len())?;
        self.can_data_be_changed()?;
        self.touch()?;
        self.update_accounts_resize_delta(data.len())?;
        // Note that we intentionally don't call self.make_data_mut() here.  make_data_mut() will
        // allocate + memcpy the current data if self.account is shared. We don't need the memcpy
        // here tho because account.set_data_from_slice(data) is going to replace the content
        // anyway.
        self.account.try_set_data_from_slice(data)?;

        Ok(())
    }

    /// Resizes the account data (transaction wide)
    ///
    /// Fills it with zeros at the end if is extended or truncates at the end otherwise.
    #[cfg(not(target_os = "solana"))]
    pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
        self.can_data_be_resized(new_length)?;
        self.can_data_be_changed()?;
        // don't touch the account if the length does not change
        if self.get_data().len() == new_length {
            return Ok(());
        }
        self.touch()?;
        self.update_accounts_resize_delta(new_length)?;
        self.account.try_resize(new_length, 0)?;
        Ok(())
    }

    /// Appends all elements in a slice to the account
    #[cfg(not(target_os = "solana"))]
    pub fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
        let new_len = self.get_data().len().saturating_add(data.len());
        self.can_data_be_resized(new_len)?;
        self.can_data_be_changed()?;

        if data.is_empty() {
            return Ok(());
        }

        self.touch()?;
        self.update_accounts_resize_delta(new_len)?;
        // Even if extend_from_slice never reduces capacity, still realloc using
        // make_data_mut() if necessary so that we grow the account of the full
        // max realloc length in one go, avoiding smaller reallocations.
        self.make_data_mut();
        self.account.try_extend_from_slice(data)?;
        Ok(())
    }

    /// Reserves capacity for at least additional more elements to be inserted
    /// in the given account. Does nothing if capacity is already sufficient.
    #[cfg(not(target_os = "solana"))]
    pub fn reserve(&mut self, additional: usize) -> Result<(), InstructionError> {
        // Note that we don't need to call can_data_be_changed() here nor
        // touch() the account. reserve() only changes the capacity of the
        // memory that holds the account but it doesn't actually change content
        // nor length of the account.
        self.make_data_mut();
        self.account.reserve(additional);

        Ok(())
    }

    /// Returns the number of bytes the account can hold without reallocating.
    #[cfg(not(target_os = "solana"))]
    pub fn capacity(&self) -> usize {
        self.account.capacity()
    }

    /// Returns whether the underlying AccountSharedData is shared.
    ///
    /// The data is shared if the account has been loaded from the accounts database and has never
    /// been written to. Writing to an account unshares it.
    ///
    /// During account serialization, if an account is shared it'll get mapped as CoW, else it'll
    /// get mapped directly as writable.
    #[cfg(not(target_os = "solana"))]
    pub fn is_shared(&self) -> bool {
        self.account.is_shared()
    }

    #[cfg(not(target_os = "solana"))]
    fn make_data_mut(&mut self) {
        // if the account is still shared, it means this is the first time we're
        // about to write into it. Make the account mutable by copying it in a
        // buffer with MAX_PERMITTED_DATA_INCREASE capacity so that if the
        // transaction reallocs, we don't have to copy the whole account data a
        // second time to fullfill the realloc.
        //
        // NOTE: The account memory region CoW code in bpf_loader::create_vm() implements the same
        // logic and must be kept in sync.
        if self.account.is_shared() {
            self.account.reserve(MAX_PERMITTED_DATA_INCREASE);
        }
    }

    /// Deserializes the account data into a state
    #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
    pub fn get_state<T: serde::de::DeserializeOwned>(&self) -> Result<T, InstructionError> {
        self.account
            .deserialize_data()
            .map_err(|_| InstructionError::InvalidAccountData)
    }

    /// Serializes a state into the account data
    #[cfg(all(not(target_os = "solana"), feature = "bincode"))]
    pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
        let data = self.get_data_mut()?;
        let serialized_size =
            bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
        if serialized_size > data.len() as u64 {
            return Err(InstructionError::AccountDataTooSmall);
        }
        bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)?;
        Ok(())
    }

    // Returns whether or the kelvins currently in the account is sufficient for rent exemption should the
    // data be resized to the given size
    #[cfg(not(target_os = "solana"))]
    pub fn is_rent_exempt_at_data_length(&self, data_length: usize) -> bool {
        self.transaction_context
            .rent
            .is_exempt(self.get_kelvins(), data_length)
    }

    /// Returns whether this account is executable (transaction wide)
    /// Uses the StoredAccount variant directly since we already have the account borrowed.
    #[inline]
    #[deprecated(since = "2.1.0", note = "Use `get_owner` instead")]
    pub fn is_executable(&self) -> bool {
        self.account.executable()
    }

    /// Feature gating to remove `is_executable` flag related checks
    #[cfg(not(target_os = "solana"))]
    #[inline]
    fn is_executable_internal(&self) -> bool {
        !self
            .transaction_context
            .remove_accounts_executable_flag_checks
            && self.account.executable()
    }

    /// Returns the rent epoch of this account (transaction wide)
    #[cfg(not(target_os = "solana"))]
    #[inline]
    pub fn get_rent_epoch(&self) -> u64 {
        self.account.rent_epoch()
    }

    /// Sets whether this account is executable (transaction wide)
    ///
    /// This updates the StoredAccount variant directly (converting between Data/Executable).
    #[cfg(not(target_os = "solana"))]
    pub fn set_executable(&mut self, executable: bool) -> Result<(), InstructionError> {
        // to become executable, an account must already be rent exempt
        if executable && !self.is_rent_exempt_at_data_length(self.get_data().len()) {
            return Err(InstructionError::ExecutableAccountNotRentExempt);
        }
        // one can not clear the executable flag
        if !executable && self.account.executable() {
            return Err(InstructionError::ExecutableModified);
        }
        // don't touch the account if the executable flag does not change
        if self.account.executable() == executable {
            return Ok(());
        }
        // only the account owner can set the executable flag
        if !self.is_owned_by_current_program() {
            return Err(InstructionError::ExecutableModified);
        }
        // and only if the account is writable
        if !self.is_writable() {
            return Err(InstructionError::ExecutableModified);
        }
        // and only if the data is not empty
        if self.get_data().is_empty() {
            return Err(InstructionError::ExecutableDataModified);
        }
        self.touch()?;
        self.account
            .set_executable(executable)
            .map_err(|_| InstructionError::InvalidAccountData)?;
        Ok(())
    }

    /// Returns whether this account is a signer (instruction wide)
    pub fn is_signer(&self) -> bool {
        if self.index_in_instruction < self.instruction_context.get_number_of_program_accounts() {
            return false;
        }
        self.instruction_context
            .is_instruction_account_signer(
                self.index_in_instruction
                    .saturating_sub(self.instruction_context.get_number_of_program_accounts()),
            )
            .unwrap_or_default()
    }

    /// Returns whether this account is writable (instruction wide)
    pub fn is_writable(&self) -> bool {
        if self.index_in_instruction < self.instruction_context.get_number_of_program_accounts() {
            return false;
        }
        self.instruction_context
            .is_instruction_account_writable(
                self.index_in_instruction
                    .saturating_sub(self.instruction_context.get_number_of_program_accounts()),
            )
            .unwrap_or_default()
    }

    /// Returns true if the owner of this account is the current `InstructionContext`s last program (instruction wide)
    pub fn is_owned_by_current_program(&self) -> bool {
        self.instruction_context
            .get_last_program_key(self.transaction_context)
            .map(|key| key == self.get_owner())
            .unwrap_or_default()
    }

    /// Returns an error if the account data can not be mutated by the current program
    #[cfg(not(target_os = "solana"))]
    pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
        // Only non-executable accounts data can be changed
        if self.is_executable_internal() {
            return Err(InstructionError::ExecutableDataModified);
        }
        // and only if the account is writable
        if !self.is_writable() {
            return Err(InstructionError::ReadonlyDataModified);
        }
        // and only if we are the owner
        if !self.is_owned_by_current_program() {
            return Err(InstructionError::ExternalAccountDataModified);
        }
        Ok(())
    }

    /// Returns an error if the account data can not be resized to the given length
    #[cfg(not(target_os = "solana"))]
    pub fn can_data_be_resized(&self, new_length: usize) -> Result<(), InstructionError> {
        let old_length = self.get_data().len();
        // Only the owner can change the length of the data
        if new_length != old_length && !self.is_owned_by_current_program() {
            return Err(InstructionError::AccountDataSizeChanged);
        }
        // The new length can not exceed the maximum permitted length
        if new_length > MAX_PERMITTED_DATA_LENGTH as usize {
            return Err(InstructionError::InvalidRealloc);
        }
        // The resize can not exceed the per-transaction maximum
        let length_delta = (new_length as i64).saturating_sub(old_length as i64);
        if self
            .transaction_context
            .accounts_resize_delta()?
            .saturating_add(length_delta)
            > MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
        {
            return Err(InstructionError::MaxAccountsDataAllocationsExceeded);
        }
        Ok(())
    }

    #[cfg(not(target_os = "solana"))]
    fn touch(&self) -> Result<(), InstructionError> {
        self.transaction_context
            .accounts()
            .touch(self.index_in_transaction)
    }

    #[cfg(not(target_os = "solana"))]
    fn update_accounts_resize_delta(&mut self, new_len: usize) -> Result<(), InstructionError> {
        let mut accounts_resize_delta = self
            .transaction_context
            .accounts_resize_delta
            .try_borrow_mut()
            .map_err(|_| InstructionError::GenericError)?;
        *accounts_resize_delta = accounts_resize_delta
            .saturating_add((new_len as i64).saturating_sub(self.get_data().len() as i64));
        Ok(())
    }
}

/// Everything that needs to be recorded from a TransactionContext after execution
#[cfg(not(target_os = "solana"))]
pub struct ExecutionRecord {
    pub accounts: Vec<TransactionAccount>,
    pub touched_accounts: Vec<bool>,
    pub return_data: TransactionReturnData,
    pub touched_account_count: u64,
    pub accounts_resize_delta: i64,
}

#[cfg(not(target_os = "solana"))]
impl From<TransactionContext> for ExecutionRecord {
    fn from(context: TransactionContext) -> Self {
        // // TODO: remove once ebpf is gone
        // // let accounts = Rc::try_unwrap(context.accounts)
        // //     .expect("transaction_context.accounts has unexpected outstanding refs");
        let accounts = context.accounts;
        let touched_account_count = accounts.touched_count() as u64;
        let (accounts, touched_accounts) = accounts.destruct();

        Self {
            accounts: context.account_keys.into_iter().zip(accounts).collect(),
            touched_accounts,
            return_data: context.return_data,
            touched_account_count,
            accounts_resize_delta: RefCell::into_inner(context.accounts_resize_delta),
        }
    }
}

#[cfg(not(target_os = "solana"))]
fn is_zeroed(buf: &[u8]) -> bool {
    const ZEROS_LEN: usize = 1024;
    const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
    let mut chunks = buf.chunks_exact(ZEROS_LEN);

    #[allow(clippy::indexing_slicing)]
    {
        chunks.all(|chunk| chunk == &ZEROS[..])
            && chunks.remainder() == &ZEROS[..chunks.remainder().len()]
    }
}

#[cfg(all(test, not(target_os = "solana")))]
#[allow(clippy::indexing_slicing)]
mod tests {
    use rialo_s_account::{
        Account, AccountSharedData, BalanceAccount, ExecutableAccount, ReadableAccount,
        WritableAccount,
    };
    use rialo_s_rent::Rent;
    use rialo_s_sdk_ids::bpf_loader;

    use super::*;

    // ============== Helper Functions ==============

    fn create_test_transaction_context(accounts: Vec<TransactionAccount>) -> TransactionContext {
        TransactionContext::new(accounts, Rent::default(), 10, 10)
    }

    fn create_data_account(kelvins: u64, data_len: usize, owner: &Pubkey) -> StoredAccount {
        StoredAccount::Data(AccountSharedData::new(kelvins, data_len, owner))
    }

    fn create_balance_account(kelvins: u64, owner: &Pubkey) -> StoredAccount {
        StoredAccount::Balance(BalanceAccount::new(kelvins, owner))
    }

    fn create_executable_account(kelvins: u64, data: Vec<u8>, owner: &Pubkey) -> StoredAccount {
        StoredAccount::Executable(ExecutableAccount::new_with_data(kelvins, data, owner))
    }

    // ============== Serialization/Deserialization Round-Trip Tests ==============
    // These tests require the bincode feature

    #[cfg(feature = "bincode")]
    #[test]
    fn test_stored_account_serde_roundtrip_data_variant() {
        let owner = Pubkey::new_unique();
        let mut account = AccountSharedData::new(1000, 100, &owner);
        // Fill with some test data
        account
            .data_as_mut_slice()
            .iter_mut()
            .enumerate()
            .for_each(|(i, b)| *b = (i % 256) as u8);
        let original = StoredAccount::Data(account);

        // Serialize
        let serialized = bincode::serialize(&original).expect("Failed to serialize Data variant");

        // Deserialize
        let deserialized: StoredAccount =
            bincode::deserialize(&serialized).expect("Failed to deserialize Data variant");

        // Verify
        assert!(deserialized.is_data());
        assert_eq!(original.kelvins(), deserialized.kelvins());
        assert_eq!(original.data(), deserialized.data());
        assert_eq!(original.owner(), deserialized.owner());
        assert_eq!(original.rent_epoch(), deserialized.rent_epoch());
        assert_eq!(original.executable(), deserialized.executable());
    }

    #[cfg(feature = "bincode")]
    #[test]
    fn test_stored_account_serde_roundtrip_balance_variant() {
        let owner = Pubkey::new_unique();
        let original =
            StoredAccount::Balance(BalanceAccount::new_with_rent_epoch(5000, &owner, 42));

        // Serialize
        let serialized =
            bincode::serialize(&original).expect("Failed to serialize Balance variant");

        // Deserialize
        let deserialized: StoredAccount =
            bincode::deserialize(&serialized).expect("Failed to deserialize Balance variant");

        // Verify
        assert!(deserialized.is_balance());
        assert_eq!(original.kelvins(), deserialized.kelvins());
        assert!(deserialized.data().is_empty());
        assert_eq!(original.owner(), deserialized.owner());
        assert_eq!(original.rent_epoch(), deserialized.rent_epoch());
        assert!(!deserialized.executable());
    }

    #[cfg(feature = "bincode")]
    #[test]
    fn test_stored_account_serde_roundtrip_executable_variant() {
        let owner = Pubkey::new_unique();
        let program_data: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
        let original = StoredAccount::Executable(ExecutableAccount::new(
            2000,
            program_data.clone(),
            &owner,
            99,
        ));

        // Serialize
        let serialized =
            bincode::serialize(&original).expect("Failed to serialize Executable variant");

        // Deserialize
        let deserialized: StoredAccount =
            bincode::deserialize(&serialized).expect("Failed to deserialize Executable variant");

        // Verify
        assert!(deserialized.executable());
        assert_eq!(original.kelvins(), deserialized.kelvins());
        assert_eq!(original.data(), deserialized.data());
        assert_eq!(original.owner(), deserialized.owner());
        assert_eq!(original.rent_epoch(), deserialized.rent_epoch());
    }

    #[cfg(feature = "bincode")]
    #[test]
    fn test_stored_account_serde_roundtrip_all_variants_mixed() {
        let owner = Pubkey::new_unique();

        let accounts: Vec<StoredAccount> = vec![
            create_data_account(100, 50, &owner),
            create_balance_account(200, &owner),
            create_executable_account(300, vec![1, 2, 3, 4], &owner),
            create_data_account(0, 0, &owner), // Empty data account
            create_balance_account(0, &owner), // Zero kelvins balance
        ];

        for (i, original) in accounts.iter().enumerate() {
            let serialized = bincode::serialize(original)
                .unwrap_or_else(|_| panic!("Failed to serialize account {}", i));
            let deserialized: StoredAccount = bincode::deserialize(&serialized)
                .unwrap_or_else(|_| panic!("Failed to deserialize account {}", i));

            assert_eq!(
                original.kelvins(),
                deserialized.kelvins(),
                "Kelvins mismatch at {}",
                i
            );
            assert_eq!(
                original.data(),
                deserialized.data(),
                "Data mismatch at {}",
                i
            );
            assert_eq!(
                original.owner(),
                deserialized.owner(),
                "Owner mismatch at {}",
                i
            );
            assert_eq!(
                original.executable(),
                deserialized.executable(),
                "Executable mismatch at {}",
                i
            );
        }
    }

    // ============== Zero-Kelvin Edge Case Tests ==============

    #[test]
    fn test_zero_kelvin_executable_account() {
        let owner = Pubkey::new_unique();
        let program_data = vec![0xEF, 0xBE, 0xAD, 0xDE]; // Some "bytecode"
        let executable = create_executable_account(0, program_data.clone(), &owner);

        assert!(executable.executable());
        assert_eq!(executable.kelvins(), 0);
        assert_eq!(executable.data(), &program_data);
        assert_eq!(executable.owner(), &owner);
    }

    #[test]
    fn test_zero_kelvin_executable_in_transaction_context() {
        let owner = Pubkey::new_unique();
        let key = Pubkey::new_unique();
        let program_data = vec![0x01, 0x02, 0x03];

        let accounts = vec![(
            key,
            create_executable_account(0, program_data.clone(), &owner),
        )];

        let ctx = create_test_transaction_context(accounts);

        // Verify we can access the account
        let account = ctx.get_account_at_index(0).unwrap();
        let borrowed = account.borrow();
        assert_eq!(borrowed.kelvins(), 0);
        assert_eq!(borrowed.data(), &program_data);
        assert!(borrowed.executable());
    }

    #[test]
    fn test_zero_kelvin_data_account() {
        let owner = Pubkey::new_unique();
        let data_account = create_data_account(0, 100, &owner);

        assert!(!data_account.executable());
        assert_eq!(data_account.kelvins(), 0);
        assert_eq!(data_account.data().len(), 100);
    }

    #[test]
    fn test_zero_kelvin_balance_account() {
        let owner = Pubkey::new_unique();
        let balance_account = create_balance_account(0, &owner);

        assert!(!balance_account.executable());
        assert_eq!(balance_account.kelvins(), 0);
        assert!(balance_account.data().is_empty());
    }

    // ============== Account Variant Transition Tests ==============

    #[test]
    fn test_data_to_executable_transition() {
        // To become executable, an account must be owned by a valid program loader
        let owner = bpf_loader::id();
        let mut account = create_data_account(1000, 100, &owner);

        assert!(!account.executable());
        assert!(account.is_data());

        // Transition to executable
        account.set_executable(true).unwrap();

        assert!(account.executable());
        assert!(!account.is_data());
        assert_eq!(account.kelvins(), 1000);
    }

    #[test]
    fn test_executable_to_data_transition() {
        let owner = Pubkey::new_unique();
        let mut account = create_executable_account(2000, vec![1, 2, 3], &owner);

        assert!(account.executable());

        // Transition back to data
        account.set_executable(false).unwrap();

        assert!(!account.executable());
        assert!(account.is_data());
        assert_eq!(account.kelvins(), 2000);
        assert_eq!(account.data(), &[1, 2, 3]); // Data preserved
    }

    #[test]
    fn test_balance_to_executable_transition() {
        // Balance accounts cannot become executable because they have no data.
        // Programs must have bytecode data.
        let owner = bpf_loader::id();
        let mut account = create_balance_account(500, &owner);

        assert!(!account.executable());
        assert!(account.is_balance());

        // Transition to executable should fail - Balance accounts have no data
        let result = account.set_executable(true);
        assert!(result.is_err());

        // Account should remain unchanged
        assert!(!account.executable());
        assert!(account.is_balance());
        assert_eq!(account.kelvins(), 500);
    }

    #[test]
    fn test_variant_transition_in_transaction_context() {
        // To become executable, accounts must be owned by a valid program loader
        let owner = bpf_loader::id();
        let program_id = Pubkey::new_unique();
        let account_key = Pubkey::new_unique();

        // Create a transaction with a data account that will become executable
        let accounts = vec![
            (
                program_id,
                create_executable_account(1000, vec![1, 2, 3], &owner),
            ),
            (account_key, create_data_account(500, 50, &owner)),
        ];

        let ctx = create_test_transaction_context(accounts);

        // Set it to executable
        ctx.accounts().set_executable(1, true).unwrap();

        // Verify the underlying account changed variant
        let account = ctx.get_account_at_index(1).unwrap();
        assert!(account.borrow().executable());
    }

    #[test]
    fn test_multiple_transitions_preserve_data() {
        // To become executable, accounts must be owned by a valid program loader
        let owner = bpf_loader::id();
        let original_data: Vec<u8> = (0..100).collect();

        let mut account = StoredAccount::Data(AccountSharedData::new(1000, 100, &owner));
        // Fill with test data
        if let StoredAccount::Data(ref mut inner) = account {
            inner.data_as_mut_slice().copy_from_slice(&original_data);
        }

        // Data -> Executable
        account.set_executable(true).unwrap();
        assert!(account.executable());
        assert_eq!(account.data(), &original_data);

        // Executable -> Data
        account.set_executable(false).unwrap();
        assert!(account.is_data());
        assert_eq!(account.data(), &original_data);

        // Data -> Executable again
        account.set_executable(true).unwrap();
        assert!(account.executable());
        assert_eq!(account.data(), &original_data);
    }

    #[test]
    fn test_idempotent_executable_transitions() {
        let owner = Pubkey::new_unique();
        let mut data_account = create_data_account(100, 10, &owner);
        let mut exec_account = create_executable_account(200, vec![1, 2], &owner);

        // Setting executable to false on non-executable should be no-op
        data_account.set_executable(false).unwrap();
        assert!(data_account.is_data());

        // Setting executable to true on executable should be no-op
        exec_account.set_executable(true).unwrap();
        assert!(exec_account.executable());
    }

    // ============== TransactionContext Integration Tests ==============

    #[test]
    fn test_transaction_context_with_all_variants() {
        let owner = Pubkey::new_unique();
        let key1 = Pubkey::new_unique();
        let key2 = Pubkey::new_unique();
        let key3 = Pubkey::new_unique();

        let accounts = vec![
            (key1, create_data_account(1000, 100, &owner)),
            (key2, create_balance_account(2000, &owner)),
            (
                key3,
                create_executable_account(3000, vec![1, 2, 3, 4], &owner),
            ),
        ];

        let ctx = create_test_transaction_context(accounts);

        // Verify account count
        assert_eq!(ctx.get_number_of_accounts(), 3);

        // Verify keys are correctly stored
        assert_eq!(ctx.get_key_of_account_at_index(0).unwrap(), &key1);
        assert_eq!(ctx.get_key_of_account_at_index(1).unwrap(), &key2);
        assert_eq!(ctx.get_key_of_account_at_index(2).unwrap(), &key3);
    }

    #[test]
    fn test_execution_record_with_all_variants() {
        let owner = Pubkey::new_unique();
        let key1 = Pubkey::new_unique();
        let key2 = Pubkey::new_unique();
        let key3 = Pubkey::new_unique();

        let accounts = vec![
            (key1, create_data_account(1000, 100, &owner)),
            (key2, create_balance_account(2000, &owner)),
            (
                key3,
                create_executable_account(3000, vec![1, 2, 3, 4], &owner),
            ),
        ];

        let ctx = create_test_transaction_context(accounts);

        // Convert to ExecutionRecord
        let record: ExecutionRecord = ctx.into();

        // Verify all accounts are preserved with correct variants
        assert_eq!(record.accounts.len(), 3);

        assert_eq!(record.accounts[0].0, key1);
        assert!(record.accounts[0].1.is_data());

        assert_eq!(record.accounts[1].0, key2);
        assert!(record.accounts[1].1.is_balance());

        assert_eq!(record.accounts[2].0, key3);
        assert!(record.accounts[2].1.executable());
    }

    #[test]
    fn test_borrowed_account_executable_check() {
        let owner = Pubkey::new_unique();
        let program_id = Pubkey::new_unique();
        let account_key = Pubkey::new_unique();

        let accounts = vec![
            (
                program_id,
                create_executable_account(1000, vec![1, 2, 3], &owner),
            ),
            (account_key, create_data_account(500, 50, &program_id)), // Owned by program
        ];

        let mut ctx = create_test_transaction_context(accounts);

        // Setup instruction context
        let instruction_accounts = vec![InstructionAccount {
            index_in_transaction: 1,
            index_in_caller: 0,
            index_in_callee: 0,
            is_signer: true,
            is_writable: true,
        }];

        ctx.get_next_instruction_context()
            .unwrap()
            .configure(&[0], &instruction_accounts, &[]);
        ctx.push().unwrap();

        // Borrow the account and check executable status
        {
            let instruction_ctx = ctx.get_current_instruction_context().unwrap();
            let borrowed = instruction_ctx
                .try_borrow_instruction_account(&ctx, 0)
                .unwrap();

            #[allow(deprecated)]
            {
                assert!(!borrowed.is_executable());
            }
        }

        ctx.pop().unwrap();
    }

    // ============== Touched Flags Tests ==============

    #[test]
    fn test_touched_flags_with_all_variants() {
        let owner = Pubkey::new_unique();
        let key1 = Pubkey::new_unique();
        let key2 = Pubkey::new_unique();
        let key3 = Pubkey::new_unique();

        let accounts = vec![
            (key1, create_data_account(1000, 100, &owner)),
            (key2, create_balance_account(2000, &owner)),
            (key3, create_executable_account(3000, vec![1, 2, 3], &owner)),
        ];

        let ctx = create_test_transaction_context(accounts);

        // Initially no accounts touched
        assert_eq!(ctx.accounts().touched_count(), 0);

        // Touch each account type
        ctx.accounts().touch(0).unwrap(); // Data
        assert_eq!(ctx.accounts().touched_count(), 1);

        ctx.accounts().touch(1).unwrap(); // Balance
        assert_eq!(ctx.accounts().touched_count(), 2);

        ctx.accounts().touch(2).unwrap(); // Executable
        assert_eq!(ctx.accounts().touched_count(), 3);
    }

    // ============== Data Mutation Tests ==============

    #[test]
    fn test_data_mutation_on_data_account() {
        let owner = Pubkey::new_unique();
        let key = Pubkey::new_unique();

        let accounts = vec![(key, create_data_account(1000, 100, &owner))];

        let ctx = create_test_transaction_context(accounts);

        // Modify data
        {
            let account = ctx.get_account_at_index(0).unwrap();
            let mut borrowed = account.borrow_mut();
            borrowed.try_set_data_from_slice(&[1, 2, 3, 4, 5]).unwrap();
        }

        // Verify modification
        let account = ctx.get_account_at_index(0).unwrap();
        let borrowed = account.borrow();
        assert_eq!(&borrowed.data()[..5], &[1, 2, 3, 4, 5]);
    }

    #[test]
    fn test_data_mutation_on_executable_account() {
        let owner = Pubkey::new_unique();
        let key = Pubkey::new_unique();

        let accounts = vec![(key, create_executable_account(1000, vec![0; 100], &owner))];

        let ctx = create_test_transaction_context(accounts);

        // Modify data (executable accounts can have their data modified in certain contexts)
        {
            let account = ctx.get_account_at_index(0).unwrap();
            let mut borrowed = account.borrow_mut();
            borrowed
                .try_set_data_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF])
                .unwrap();
        }

        // Verify modification and executable status preserved
        let account = ctx.get_account_at_index(0).unwrap();
        let borrowed = account.borrow();
        assert_eq!(borrowed.data(), &[0xDE, 0xAD, 0xBE, 0xEF]);
        assert!(borrowed.executable());
    }

    #[test]
    fn test_kelvins_mutation_all_variants() {
        let owner = Pubkey::new_unique();
        let key1 = Pubkey::new_unique();
        let key2 = Pubkey::new_unique();
        let key3 = Pubkey::new_unique();

        let accounts = vec![
            (key1, create_data_account(1000, 10, &owner)),
            (key2, create_balance_account(2000, &owner)),
            (key3, create_executable_account(3000, vec![1], &owner)),
        ];

        let ctx = create_test_transaction_context(accounts);

        // Modify kelvins on each variant
        for i in 0..3 {
            let account = ctx.get_account_at_index(i).unwrap();
            let mut borrowed = account.borrow_mut();
            let original = borrowed.kelvins();
            borrowed.set_kelvins(original + 100);
        }

        // Verify
        assert_eq!(
            ctx.get_account_at_index(0).unwrap().borrow().kelvins(),
            1100
        );
        assert_eq!(
            ctx.get_account_at_index(1).unwrap().borrow().kelvins(),
            2100
        );
        assert_eq!(
            ctx.get_account_at_index(2).unwrap().borrow().kelvins(),
            3100
        );
    }

    // ============== Conversion Tests ==============

    #[test]
    fn test_stored_account_to_account_conversion() {
        let owner = Pubkey::new_unique();

        let stored = create_data_account(1000, 100, &owner);
        let account: Account = stored.clone().into();

        assert_eq!(stored.kelvins(), account.kelvins());
        assert_eq!(stored.owner(), account.owner());
        assert_eq!(stored.data().len(), account.data().len());
    }

    // ============== Edge Cases ==============

    #[test]
    fn test_empty_data_account() {
        let owner = Pubkey::new_unique();
        let account = create_data_account(100, 0, &owner);

        assert!(account.data().is_empty());
        assert!(!account.executable());
        assert_eq!(account.capacity(), 0);
    }

    #[test]
    fn test_large_data_account() {
        let owner = Pubkey::new_unique();
        let large_size = 10 * 1024 * 1024; // 10 MB (MAX_PERMITTED_DATA_LENGTH)
        let account = create_data_account(1_000_000_000, large_size, &owner);

        assert_eq!(account.data().len(), large_size);
        assert!(!account.executable());
    }

    #[test]
    fn test_max_kelvins_all_variants() {
        let owner = Pubkey::new_unique();

        let data = create_data_account(u64::MAX, 10, &owner);
        let balance = create_balance_account(u64::MAX, &owner);
        let exec = create_executable_account(u64::MAX, vec![1], &owner);

        assert_eq!(data.kelvins(), u64::MAX);
        assert_eq!(balance.kelvins(), u64::MAX);
        assert_eq!(exec.kelvins(), u64::MAX);
    }

    // ============== is_shared Tests ==============

    #[test]
    fn test_is_shared_for_all_variants() {
        let owner = Pubkey::new_unique();

        let data = create_data_account(100, 10, &owner);
        let balance = create_balance_account(200, &owner);
        let exec = create_executable_account(300, vec![1], &owner);

        // Freshly created accounts should not be shared (single reference)
        assert!(!data.is_shared());
        assert!(!balance.is_shared()); // Balance is always not shared
        assert!(!exec.is_shared());
    }

    // ============== Capacity Tests ==============

    #[test]
    fn test_capacity_for_all_variants() {
        let owner = Pubkey::new_unique();

        let data = create_data_account(100, 50, &owner);
        let balance = create_balance_account(200, &owner);
        let exec = create_executable_account(300, vec![1, 2, 3, 4, 5], &owner);

        assert!(data.capacity() >= 50);
        assert_eq!(balance.capacity(), 0); // Balance has no capacity
        assert!(exec.capacity() >= 5);
    }

    // ============== Performance Tests ==============
    // These tests verify that high-volume operations work correctly with the enum indirection.
    // They serve as basic performance regression tests by exercising many operations.

    #[test]
    fn test_high_volume_account_access() {
        let owner = Pubkey::new_unique();
        const NUM_ACCOUNTS: usize = 100;
        const ITERATIONS: usize = 1000;

        // Create a transaction context with many accounts of all types
        let mut accounts = Vec::with_capacity(NUM_ACCOUNTS);
        for i in 0..NUM_ACCOUNTS {
            let key = Pubkey::new_unique();
            let account = match i % 3 {
                0 => create_data_account((i as u64) * 100, i % 50 + 1, &owner),
                1 => create_balance_account((i as u64) * 100, &owner),
                _ => create_executable_account((i as u64) * 100, vec![i as u8; 10], &owner),
            };
            accounts.push((key, account));
        }

        let ctx = create_test_transaction_context(accounts);

        // Perform many read operations
        for _ in 0..ITERATIONS {
            for i in 0..NUM_ACCOUNTS {
                let index = i as IndexOfAccount;

                // Borrow account and access properties
                let account = ctx.get_account_at_index(index).unwrap();
                let borrowed = account.borrow();
                let _kelvins = borrowed.kelvins();
                let _data_len = borrowed.data().len();
                let _owner = borrowed.owner();
            }
        }

        // Verify final state is consistent
        for i in 0..NUM_ACCOUNTS {
            let account = ctx.get_account_at_index(i as IndexOfAccount).unwrap();
            let borrowed = account.borrow();
            assert_eq!(borrowed.kelvins(), (i as u64) * 100);
        }
    }

    #[test]
    fn test_high_volume_kelvin_mutations() {
        let owner = Pubkey::new_unique();
        const NUM_ACCOUNTS: usize = 50;
        const ITERATIONS: usize = 100;

        let mut accounts = Vec::with_capacity(NUM_ACCOUNTS);
        for i in 0..NUM_ACCOUNTS {
            let key = Pubkey::new_unique();
            let account = match i % 3 {
                0 => create_data_account(1000, 10, &owner),
                1 => create_balance_account(1000, &owner),
                _ => create_executable_account(1000, vec![1, 2], &owner),
            };
            accounts.push((key, account));
        }

        let ctx = create_test_transaction_context(accounts);

        // Perform many write operations
        for iteration in 0..ITERATIONS {
            for i in 0..NUM_ACCOUNTS {
                let account = ctx.get_account_at_index(i as IndexOfAccount).unwrap();
                let mut borrowed = account.borrow_mut();
                let current = borrowed.kelvins();
                borrowed.set_kelvins(current + 1);
            }
            // Verify intermediate state
            if iteration % 10 == 0 {
                for i in 0..NUM_ACCOUNTS {
                    let account = ctx.get_account_at_index(i as IndexOfAccount).unwrap();
                    let borrowed = account.borrow();
                    assert_eq!(borrowed.kelvins(), 1000 + (iteration as u64) + 1);
                }
            }
        }

        // Verify final state
        for i in 0..NUM_ACCOUNTS {
            let account = ctx.get_account_at_index(i as IndexOfAccount).unwrap();
            let borrowed = account.borrow();
            assert_eq!(borrowed.kelvins(), 1000 + (ITERATIONS as u64));
        }
    }

    #[test]
    fn test_high_volume_variant_transitions() {
        // To become executable, accounts must be owned by a valid program loader
        let owner = bpf_loader::id();
        const NUM_TRANSITIONS: usize = 100;

        let mut account = create_data_account(1000, 50, &owner);

        for i in 0..NUM_TRANSITIONS {
            // Alternate between executable and non-executable
            let should_be_executable = i % 2 == 0;
            account.set_executable(should_be_executable).unwrap();

            assert_eq!(account.executable(), should_be_executable);
            assert_eq!(account.kelvins(), 1000);
            // Data should be preserved across transitions
            assert_eq!(account.data().len(), 50);
        }
    }

    #[test]
    fn test_concurrent_reads_via_multiple_borrows() {
        let owner = Pubkey::new_unique();
        const NUM_ACCOUNTS: usize = 10;

        let mut accounts = Vec::with_capacity(NUM_ACCOUNTS);
        for i in 0..NUM_ACCOUNTS {
            let key = Pubkey::new_unique();
            let account = create_data_account((i as u64) * 100, 50, &owner);
            accounts.push((key, account));
        }

        let ctx = create_test_transaction_context(accounts);

        // Multiple simultaneous reads should work
        for i in 0..NUM_ACCOUNTS {
            let account1 = ctx.accounts().try_borrow(i as IndexOfAccount).unwrap();
            let kelvins1 = account1.kelvins();

            // Reading from a different account should work
            for j in 0..NUM_ACCOUNTS {
                if j != i {
                    let account2 = ctx.accounts().try_borrow(j as IndexOfAccount).unwrap();
                    let kelvins2 = account2.kelvins();
                    assert_eq!(kelvins2, (j as u64) * 100);
                }
            }

            assert_eq!(kelvins1, (i as u64) * 100);
        }
    }
}
