use crate::{
    error::PlerkleSerializationError,
    solana_geyser_plugin_interface_shims::{
        ReplicaAccountInfoV2, ReplicaBlockInfoV2, ReplicaTransactionInfoV2,
        ReplicaTransactionInfoV3, SlotStatus,
    },
    AccountInfo, AccountInfoArgs, BlockInfo, BlockInfoArgs, CompiledInnerInstruction,
    CompiledInnerInstructionArgs, CompiledInnerInstructions, CompiledInnerInstructionsArgs,
    CompiledInstruction, CompiledInstructionArgs, Pubkey as FBPubkey, Pubkey, SlotStatusInfo,
    SlotStatusInfoArgs, Status as FBSlotStatus, TransactionInfo, TransactionInfoArgs,
    TransactionVersion,
};
use chrono::Utc;
use flatbuffers::{FlatBufferBuilder, WIPOffset};
use solana_sdk::{
    message::{SanitizedMessage, VersionedMessage},
    transaction::VersionedTransaction,
};
use solana_transaction_status::{
    option_serializer::OptionSerializer, EncodedConfirmedTransactionWithStatusMeta, UiInstruction,
    UiTransactionStatusMeta,
};

pub fn serialize_account<'a>(
    mut builder: FlatBufferBuilder<'a>,
    account: &ReplicaAccountInfoV2,
    slot: u64,
    is_startup: bool,
) -> FlatBufferBuilder<'a> {
    // Serialize vector data.
    let pubkey: Pubkey = account.pubkey.into();
    let owner: Pubkey = account.owner.into();
    let data = builder.create_vector(account.data);

    // Serialize everything into Account Info table.
    let seen_at = Utc::now();
    let account_info = AccountInfo::create(
        &mut builder,
        &AccountInfoArgs {
            pubkey: Some(&pubkey),
            lamports: account.lamports,
            owner: Some(&owner),
            executable: account.executable,
            rent_epoch: account.rent_epoch,
            data: Some(data),
            write_version: account.write_version,
            slot,
            is_startup,
            seen_at: seen_at.timestamp_millis(),
        },
    );

    // Finalize buffer and return to caller.
    builder.finish(account_info, None);
    builder
}

pub fn serialize_slot_status<'a>(
    mut builder: FlatBufferBuilder<'a>,
    slot: u64,
    parent: Option<u64>,
    status: SlotStatus,
) -> FlatBufferBuilder<'a> {
    // Convert to flatbuffer enum.
    let status = match status {
        SlotStatus::Confirmed => FBSlotStatus::Confirmed,
        SlotStatus::Processed => FBSlotStatus::Processed,
        SlotStatus::Rooted => FBSlotStatus::Rooted,
    };

    // Serialize everything into Slot Status Info table.
    let seen_at = Utc::now();
    let slot_status = SlotStatusInfo::create(
        &mut builder,
        &SlotStatusInfoArgs {
            slot,
            parent,
            status,
            seen_at: seen_at.timestamp_millis(),
        },
    );

    // Finalize buffer and return to caller.
    builder.finish(slot_status, None);
    builder
}

pub fn serialize_transaction<'a>(
    mut builder: FlatBufferBuilder<'a>,
    transaction_info: &ReplicaTransactionInfoV2,
    slot: u64,
) -> FlatBufferBuilder<'a> {
    // Flatten and serialize account keys.
    let account_keys = transaction_info.transaction.message().account_keys();
    let atl_keys = &transaction_info.transaction_status_meta.loaded_addresses;

    let account_keys = {
        let mut account_keys_fb_vec = vec![];
        for key in account_keys.iter() {
            account_keys_fb_vec.push(FBPubkey(key.to_bytes()));
        }

        for i in &atl_keys.writable {
            let pubkey = FBPubkey(i.to_bytes());
            account_keys_fb_vec.push(pubkey);
        }

        for i in &atl_keys.readonly {
            let pubkey = FBPubkey(i.to_bytes());
            account_keys_fb_vec.push(pubkey);
        }

        if !account_keys_fb_vec.is_empty() {
            Some(builder.create_vector(&account_keys_fb_vec))
        } else {
            None
        }
    };

    // Serialize log messages.
    let log_messages =
        if let Some(log_messages) = &transaction_info.transaction_status_meta.log_messages {
            let mut log_messages_fb_vec = Vec::with_capacity(log_messages.len());
            for message in log_messages {
                log_messages_fb_vec.push(builder.create_string(message));
            }
            Some(builder.create_vector(&log_messages_fb_vec))
        } else {
            None
        };

    // Serialize inner instructions.
    let inner_instructions = if let Some(inner_instructions_vec) = transaction_info
        .transaction_status_meta
        .inner_instructions
        .as_ref()
    {
        let mut overall_fb_vec = Vec::with_capacity(inner_instructions_vec.len());
        for inner_instructions in inner_instructions_vec.iter() {
            let index = inner_instructions.index;
            let mut instructions_fb_vec = Vec::with_capacity(inner_instructions.instructions.len());
            for compiled_instruction in inner_instructions.instructions.iter() {
                let program_id_index = compiled_instruction.instruction.program_id_index;
                let accounts =
                    Some(builder.create_vector(&compiled_instruction.instruction.accounts));
                let data = Some(builder.create_vector(&compiled_instruction.instruction.data));
                let compiled = CompiledInstruction::create(
                    &mut builder,
                    &CompiledInstructionArgs {
                        program_id_index,
                        accounts,
                        data,
                    },
                );
                instructions_fb_vec.push(CompiledInnerInstruction::create(
                    &mut builder,
                    &CompiledInnerInstructionArgs {
                        compiled_instruction: Some(compiled),
                        stack_height: 0, // Available since Solana 1.15 but unused by DAS consumers
                    },
                ));
            }

            let instructions = Some(builder.create_vector(&instructions_fb_vec));
            overall_fb_vec.push(CompiledInnerInstructions::create(
                &mut builder,
                &CompiledInnerInstructionsArgs {
                    index,
                    instructions,
                },
            ))
        }

        Some(builder.create_vector(&overall_fb_vec))
    } else {
        None
    };
    let message = transaction_info.transaction.message();
    let version = match message {
        SanitizedMessage::Legacy(_) => TransactionVersion::Legacy,
        SanitizedMessage::V0(_) => TransactionVersion::V0,
    };

    // Serialize outer instructions.
    let outer_instructions = message.instructions();
    let outer_instructions = if !outer_instructions.is_empty() {
        let mut instructions_fb_vec = Vec::with_capacity(outer_instructions.len());
        for compiled_instruction in outer_instructions.iter() {
            let program_id_index = compiled_instruction.program_id_index;
            let accounts = Some(builder.create_vector(&compiled_instruction.accounts));
            let data = Some(builder.create_vector(&compiled_instruction.data));
            instructions_fb_vec.push(CompiledInstruction::create(
                &mut builder,
                &CompiledInstructionArgs {
                    program_id_index,
                    accounts,
                    data,
                },
            ));
        }
        Some(builder.create_vector(&instructions_fb_vec))
    } else {
        None
    };
    let seen_at = Utc::now();
    let txn_sig = transaction_info.signature.to_string();
    let signature_offset = builder.create_string(&txn_sig);
    let slot_idx = format!("{}_{}", slot, transaction_info.index);
    let slot_index_offset = builder.create_string(&slot_idx);
    // Serialize everything into Transaction Info table.
    let transaction_info_ser = TransactionInfo::create(
        &mut builder,
        &TransactionInfoArgs {
            is_vote: transaction_info.is_vote,
            account_keys,
            log_messages,
            inner_instructions: None,
            outer_instructions,
            slot,
            slot_index: Some(slot_index_offset),
            seen_at: seen_at.timestamp_millis(),
            signature: Some(signature_offset),
            compiled_inner_instructions: inner_instructions,
            version,
        },
    );

    // Finalize buffer and return to caller.
    builder.finish(transaction_info_ser, None);
    builder
}

pub fn serialize_transaction_v3<'a>(
    mut builder: FlatBufferBuilder<'a>,
    transaction_info: &ReplicaTransactionInfoV3,
    slot: u64,
) -> FlatBufferBuilder<'a> {
    let message = &transaction_info.transaction.message;
    let account_keys = {
        let static_keys = message.static_account_keys();
        let atl_keys = &transaction_info.transaction_status_meta.loaded_addresses;
        let mut account_keys_fb_vec = Vec::with_capacity(
            static_keys.len() + atl_keys.writable.len() + atl_keys.readonly.len(),
        );
        for key in static_keys.iter() {
            account_keys_fb_vec.push(FBPubkey(key.to_bytes()));
        }
        for i in &atl_keys.writable {
            account_keys_fb_vec.push(FBPubkey(i.to_bytes()));
        }
        for i in &atl_keys.readonly {
            account_keys_fb_vec.push(FBPubkey(i.to_bytes()));
        }
        if !account_keys_fb_vec.is_empty() {
            Some(builder.create_vector(&account_keys_fb_vec))
        } else {
            None
        }
    };

    let log_messages =
        if let Some(log_messages) = &transaction_info.transaction_status_meta.log_messages {
            let mut log_messages_fb_vec = Vec::with_capacity(log_messages.len());
            for message in log_messages {
                log_messages_fb_vec.push(builder.create_string(message));
            }
            Some(builder.create_vector(&log_messages_fb_vec))
        } else {
            None
        };

    let inner_instructions = if let Some(inner_instructions_vec) = transaction_info
        .transaction_status_meta
        .inner_instructions
        .as_ref()
    {
        let mut overall_fb_vec = Vec::with_capacity(inner_instructions_vec.len());
        for inner_instructions in inner_instructions_vec.iter() {
            let index = inner_instructions.index;
            let mut instructions_fb_vec = Vec::with_capacity(inner_instructions.instructions.len());
            for compiled_instruction in inner_instructions.instructions.iter() {
                let program_id_index = compiled_instruction.instruction.program_id_index;
                let accounts =
                    Some(builder.create_vector(&compiled_instruction.instruction.accounts));
                let data = Some(builder.create_vector(&compiled_instruction.instruction.data));
                let compiled = CompiledInstruction::create(
                    &mut builder,
                    &CompiledInstructionArgs {
                        program_id_index,
                        accounts,
                        data,
                    },
                );
                instructions_fb_vec.push(CompiledInnerInstruction::create(
                    &mut builder,
                    &CompiledInnerInstructionArgs {
                        compiled_instruction: Some(compiled),
                        stack_height: 0, // Available since Solana 1.15 but unused by DAS consumers
                    },
                ));
            }

            let instructions = Some(builder.create_vector(&instructions_fb_vec));
            overall_fb_vec.push(CompiledInnerInstructions::create(
                &mut builder,
                &CompiledInnerInstructionsArgs {
                    index,
                    instructions,
                },
            ))
        }

        Some(builder.create_vector(&overall_fb_vec))
    } else {
        None
    };

    let version = match message {
        VersionedMessage::Legacy(_) => TransactionVersion::Legacy,
        VersionedMessage::V0(_) => TransactionVersion::V0,
    };

    let outer_instructions = message.instructions();
    let outer_instructions = if !outer_instructions.is_empty() {
        let mut instructions_fb_vec = Vec::with_capacity(outer_instructions.len());
        for compiled_instruction in outer_instructions.iter() {
            let program_id_index = compiled_instruction.program_id_index;
            let accounts = Some(builder.create_vector(&compiled_instruction.accounts));
            let data = Some(builder.create_vector(&compiled_instruction.data));
            instructions_fb_vec.push(CompiledInstruction::create(
                &mut builder,
                &CompiledInstructionArgs {
                    program_id_index,
                    accounts,
                    data,
                },
            ));
        }
        Some(builder.create_vector(&instructions_fb_vec))
    } else {
        None
    };

    let seen_at = Utc::now();
    let txn_sig = transaction_info.signature.to_string();
    let signature_offset = builder.create_string(&txn_sig);
    let slot_idx = format!("{}_{}", slot, transaction_info.index);
    let slot_index_offset = builder.create_string(&slot_idx);
    let transaction_info_ser = TransactionInfo::create(
        &mut builder,
        &TransactionInfoArgs {
            is_vote: transaction_info.is_vote,
            account_keys,
            log_messages,
            inner_instructions: None,
            outer_instructions,
            slot,
            slot_index: Some(slot_index_offset),
            seen_at: seen_at.timestamp_millis(),
            signature: Some(signature_offset),
            compiled_inner_instructions: inner_instructions,
            version,
        },
    );

    builder.finish(transaction_info_ser, None);
    builder
}

pub fn serialize_block<'a>(
    mut builder: FlatBufferBuilder<'a>,
    block_info: &ReplicaBlockInfoV2,
) -> FlatBufferBuilder<'a> {
    // Serialize blockash.
    let blockhash = Some(builder.create_string(block_info.blockhash));

    // Serialize rewards.
    let rewards = None;

    // Serialize everything into Block Info table.
    let seen_at = Utc::now();
    let block_info = BlockInfo::create(
        &mut builder,
        &BlockInfoArgs {
            slot: block_info.slot,
            blockhash,
            rewards,
            block_time: block_info.block_time,
            block_height: block_info.block_height,
            seen_at: seen_at.timestamp_millis(),
        },
    );

    // Finalize buffer and return to caller.
    builder.finish(block_info, None);
    builder
}

/// Serialize a [`EncodedConfirmedTransactionWithStatusMeta`] from RPC into a FlatBuffer.
/// The Transaction must be base58 encoded.
pub fn seralize_encoded_transaction_with_status<'a>(
    mut builder: FlatBufferBuilder<'a>,
    tx: EncodedConfirmedTransactionWithStatusMeta,
) -> Result<FlatBufferBuilder<'a>, PlerkleSerializationError> {
    let meta: UiTransactionStatusMeta =
        tx.transaction
            .meta
            .ok_or(PlerkleSerializationError::SerializationError(
                "Missing meta data for transaction".to_string(),
            ))?;
    // Get `UiTransaction` out of `EncodedTransactionWithStatusMeta`.
    let ui_transaction: VersionedTransaction = tx.transaction.transaction.decode().ok_or(
        PlerkleSerializationError::SerializationError("Transaction cannot be decoded".to_string()),
    )?;
    let msg = ui_transaction.message;
    let atl_keys = msg.address_table_lookups();
    let account_keys = msg.static_account_keys();
    let sig = ui_transaction.signatures[0].to_string();
    let account_keys = {
        let mut account_keys_fb_vec = vec![];
        for key in account_keys.iter() {
            account_keys_fb_vec.push(FBPubkey(key.to_bytes()));
        }
        if atl_keys.is_some() {
            if let OptionSerializer::Some(ad) = meta.loaded_addresses {
                for i in ad.writable {
                    let mut output: [u8; 32] = [0; 32];
                    bs58::decode(i).into(&mut output).map_err(|e| {
                        PlerkleSerializationError::SerializationError(e.to_string())
                    })?;
                    let pubkey = FBPubkey(output);
                    account_keys_fb_vec.push(pubkey);
                }

                for i in ad.readonly {
                    let mut output: [u8; 32] = [0; 32];
                    bs58::decode(i).into(&mut output).map_err(|e| {
                        PlerkleSerializationError::SerializationError(e.to_string())
                    })?;
                    let pubkey = FBPubkey(output);
                    account_keys_fb_vec.push(pubkey);
                }
            }
        }
        if !account_keys_fb_vec.is_empty() {
            Some(builder.create_vector(&account_keys_fb_vec))
        } else {
            None
        }
    };

    // Serialize log messages.
    let log_messages = if let OptionSerializer::Some(log_messages) = &meta.log_messages {
        let mut log_messages_fb_vec = Vec::with_capacity(log_messages.len());
        for message in log_messages {
            log_messages_fb_vec.push(builder.create_string(message));
        }
        Some(builder.create_vector(&log_messages_fb_vec))
    } else {
        None
    };

    // Serialize inner instructions.
    let inner_instructions = if let OptionSerializer::Some(inner_instructions_vec) =
        meta.inner_instructions.as_ref()
    {
        let mut overall_fb_vec = Vec::with_capacity(inner_instructions_vec.len());
        for inner_instructions in inner_instructions_vec.iter() {
            let index = inner_instructions.index;
            let mut instructions_fb_vec = Vec::with_capacity(inner_instructions.instructions.len());
            for ui_instruction in inner_instructions.instructions.iter() {
                if let UiInstruction::Compiled(ui_compiled_instruction) = ui_instruction {
                    let program_id_index = ui_compiled_instruction.program_id_index;
                    let accounts = Some(builder.create_vector(&ui_compiled_instruction.accounts));
                    let data = bs58::decode(&ui_compiled_instruction.data)
                        .into_vec()
                        .map_err(|e| {
                            PlerkleSerializationError::SerializationError(e.to_string())
                        })?;
                    let data = Some(builder.create_vector(&data));
                    let compiled = CompiledInstruction::create(
                        &mut builder,
                        &CompiledInstructionArgs {
                            program_id_index,
                            accounts,
                            data,
                        },
                    );
                    instructions_fb_vec.push(CompiledInnerInstruction::create(
                        &mut builder,
                        &CompiledInnerInstructionArgs {
                            compiled_instruction: Some(compiled),
                            stack_height: 0, // Available since Solana 1.15 but unused by DAS consumers
                        },
                    ));
                }
            }

            let instructions = Some(builder.create_vector(&instructions_fb_vec));
            overall_fb_vec.push(CompiledInnerInstructions::create(
                &mut builder,
                &CompiledInnerInstructionsArgs {
                    index,
                    instructions,
                },
            ));
        }

        Some(builder.create_vector(&overall_fb_vec))
    } else {
        let empty: Vec<WIPOffset<CompiledInnerInstructions>> = Vec::new();
        Some(builder.create_vector(empty.as_slice()))
    };

    // Serialize outer instructions.
    let outer_instructions = &msg.instructions();
    let outer_instructions = if !outer_instructions.is_empty() {
        let mut instructions_fb_vec = Vec::with_capacity(outer_instructions.len());
        for ui_compiled_instruction in outer_instructions.iter() {
            let program_id_index = ui_compiled_instruction.program_id_index;
            let accounts = Some(builder.create_vector(&ui_compiled_instruction.accounts));

            let data = Some(builder.create_vector(&ui_compiled_instruction.data));
            instructions_fb_vec.push(CompiledInstruction::create(
                &mut builder,
                &CompiledInstructionArgs {
                    program_id_index,
                    accounts,
                    data,
                },
            ));
        }
        Some(builder.create_vector(&instructions_fb_vec))
    } else {
        None
    };
    let version = match msg {
        VersionedMessage::Legacy(_) => TransactionVersion::Legacy,
        VersionedMessage::V0(_) => TransactionVersion::V0,
    };

    // Serialize everything into Transaction Info table.

    let sig_db = builder.create_string(&sig);
    let transaction_info = TransactionInfo::create(
        &mut builder,
        &TransactionInfoArgs {
            is_vote: false,
            account_keys,
            log_messages,
            inner_instructions: None,
            outer_instructions,
            slot: tx.slot,
            seen_at: 0,
            slot_index: None,
            signature: Some(sig_db),
            compiled_inner_instructions: inner_instructions,
            version,
        },
    );

    // Finalize buffer and return to caller.
    builder.finish(transaction_info, None);
    Ok(builder)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::root_as_transaction_info;
    use crate::solana_geyser_plugin_interface_shims::ReplicaTransactionInfoV3;
    use solana_message::compiled_instruction::CompiledInstruction as SolanaCompiledInstruction;
    use solana_message::v0::LoadedAddresses;
    use solana_sdk::{
        hash::Hash,
        message::{Message, MessageHeader},
        pubkey::Pubkey,
        signature::Signature,
        transaction::VersionedTransaction,
    };
    use solana_transaction_status::{InnerInstruction, InnerInstructions, TransactionStatusMeta};

    fn make_test_transaction() -> (Signature, Hash, VersionedTransaction, TransactionStatusMeta) {
        let program_id = Pubkey::new_unique();
        let account_a = Pubkey::new_unique();
        let account_b = Pubkey::new_unique();
        let atl_writable = Pubkey::new_unique();
        let atl_readonly = Pubkey::new_unique();

        let message = Message {
            header: MessageHeader {
                num_required_signatures: 1,
                num_readonly_signed_accounts: 0,
                num_readonly_unsigned_accounts: 1,
            },
            account_keys: vec![account_a, account_b, program_id],
            recent_blockhash: Hash::new_unique(),
            instructions: vec![SolanaCompiledInstruction {
                program_id_index: 2,
                accounts: vec![0, 1],
                data: vec![1, 2, 3, 4],
            }],
        };

        let signature = Signature::new_unique();
        let message_hash = Hash::new_unique();
        let tx = VersionedTransaction {
            signatures: vec![signature],
            message: VersionedMessage::Legacy(message),
        };

        let meta = TransactionStatusMeta {
            loaded_addresses: LoadedAddresses {
                writable: vec![atl_writable],
                readonly: vec![atl_readonly],
            },
            log_messages: Some(vec![
                "Program log: hello".to_string(),
                "Program log: world".to_string(),
            ]),
            inner_instructions: Some(vec![InnerInstructions {
                index: 0,
                instructions: vec![InnerInstruction {
                    instruction: SolanaCompiledInstruction {
                        program_id_index: 2,
                        accounts: vec![0],
                        data: vec![5, 6],
                    },
                    stack_height: Some(2),
                }],
            }]),
            ..TransactionStatusMeta::default()
        };

        (signature, message_hash, tx, meta)
    }

    #[test]
    fn test_serialize_transaction_v3_roundtrip() {
        let (signature, message_hash, tx, meta) = make_test_transaction();

        let info = ReplicaTransactionInfoV3 {
            signature: &signature,
            message_hash: &message_hash,
            is_vote: false,
            transaction: &tx,
            transaction_status_meta: &meta,
            index: 42,
        };

        let builder = FlatBufferBuilder::new();
        let builder = serialize_transaction_v3(builder, &info, 12345);
        let buf = builder.finished_data();

        let parsed = root_as_transaction_info(buf).expect("valid flatbuffer");

        assert_eq!(parsed.signature().unwrap(), signature.to_string());
        assert_eq!(parsed.slot(), 12345);
        assert_eq!(parsed.slot_index().unwrap(), "12345_42");
        assert!(!parsed.is_vote());
        assert_eq!(parsed.version(), TransactionVersion::Legacy);

        // 3 static keys + 1 ATL writable + 1 ATL readonly = 5
        let keys = parsed.account_keys().unwrap();
        assert_eq!(keys.len(), 5);
        assert_eq!(
            keys.get(0).0,
            tx.message.static_account_keys()[0].to_bytes()
        );
        assert_eq!(keys.get(3).0, meta.loaded_addresses.writable[0].to_bytes());
        assert_eq!(keys.get(4).0, meta.loaded_addresses.readonly[0].to_bytes());

        let logs = parsed.log_messages().unwrap();
        assert_eq!(logs.len(), 2);
        assert_eq!(logs.get(0), "Program log: hello");
        assert_eq!(logs.get(1), "Program log: world");

        let outer = parsed.outer_instructions().unwrap();
        assert_eq!(outer.len(), 1);
        assert_eq!(outer.get(0).program_id_index(), 2);
        assert_eq!(outer.get(0).accounts().unwrap().bytes(), &[0, 1]);
        assert_eq!(outer.get(0).data().unwrap().bytes(), &[1, 2, 3, 4]);

        let inner = parsed.compiled_inner_instructions().unwrap();
        assert_eq!(inner.len(), 1);
        assert_eq!(inner.get(0).index(), 0);
        let inner_ixs = inner.get(0).instructions().unwrap();
        assert_eq!(inner_ixs.len(), 1);
        let cix = inner_ixs.get(0).compiled_instruction().unwrap();
        assert_eq!(cix.program_id_index(), 2);
        assert_eq!(cix.accounts().unwrap().bytes(), &[0]);
        assert_eq!(cix.data().unwrap().bytes(), &[5, 6]);
    }

    #[test]
    fn test_serialize_transaction_v3_vote_and_version() {
        let (signature, message_hash, tx, meta) = make_test_transaction();

        let info = ReplicaTransactionInfoV3 {
            signature: &signature,
            message_hash: &message_hash,
            is_vote: true,
            transaction: &tx,
            transaction_status_meta: &meta,
            index: 0,
        };

        let builder = FlatBufferBuilder::new();
        let builder = serialize_transaction_v3(builder, &info, 999);
        let buf = builder.finished_data();

        let parsed = root_as_transaction_info(buf).expect("valid flatbuffer");
        assert!(parsed.is_vote());
    }

    #[test]
    fn test_serialize_transaction_v3_v0_message() {
        let program_id = Pubkey::new_unique();
        let account_a = Pubkey::new_unique();
        let account_b = Pubkey::new_unique();
        let atl_writable = Pubkey::new_unique();
        let atl_readonly = Pubkey::new_unique();

        let v0_message = solana_message::v0::Message {
            header: MessageHeader {
                num_required_signatures: 1,
                num_readonly_signed_accounts: 0,
                num_readonly_unsigned_accounts: 1,
            },
            account_keys: vec![account_a.into(), account_b.into(), program_id.into()],
            recent_blockhash: Hash::new_unique(),
            instructions: vec![SolanaCompiledInstruction {
                program_id_index: 2,
                accounts: vec![0, 1],
                data: vec![10, 20],
            }],
            address_table_lookups: vec![],
        };

        let signature = Signature::new_unique();
        let message_hash = Hash::new_unique();
        let tx = VersionedTransaction {
            signatures: vec![signature],
            message: VersionedMessage::V0(v0_message),
        };

        let meta = TransactionStatusMeta {
            loaded_addresses: LoadedAddresses {
                writable: vec![atl_writable],
                readonly: vec![atl_readonly],
            },
            ..TransactionStatusMeta::default()
        };

        let info = ReplicaTransactionInfoV3 {
            signature: &signature,
            message_hash: &message_hash,
            is_vote: false,
            transaction: &tx,
            transaction_status_meta: &meta,
            index: 7,
        };

        let builder = FlatBufferBuilder::new();
        let builder = serialize_transaction_v3(builder, &info, 500);
        let buf = builder.finished_data();

        let parsed = root_as_transaction_info(buf).expect("valid flatbuffer");
        assert_eq!(parsed.version(), TransactionVersion::V0);

        let keys = parsed.account_keys().unwrap();
        assert_eq!(keys.len(), 5);
        assert_eq!(keys.get(0).0, account_a.to_bytes());
        assert_eq!(keys.get(3).0, atl_writable.to_bytes());
        assert_eq!(keys.get(4).0, atl_readonly.to_bytes());

        let outer = parsed.outer_instructions().unwrap();
        assert_eq!(outer.len(), 1);
        assert_eq!(outer.get(0).data().unwrap().bytes(), &[10, 20]);
    }
}
