use crate::StoredExtendedRewards;
use solana_account_decoder::parse_token::UiTokenAmount;
use solana_sdk::{
    hash::Hash,
    instruction::CompiledInstruction,
    instruction::InstructionError,
    message::{Message, MessageHeader},
    pubkey::Pubkey,
    signature::Signature,
    transaction::Transaction,
    transaction::TransactionError,
};
use solana_transaction_status::{
    ConfirmedBlock, InnerInstructions, Reward, RewardType, TransactionByAddrInfo,
    TransactionStatusMeta, TransactionTokenBalance, TransactionWithStatusMeta,
};
use std::convert::{TryFrom, TryInto};

pub mod generated {
    include!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        concat!("/proto/solana.storage.confirmed_block.rs")
    ));
}

pub mod tx_by_addr {
    include!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        concat!("/proto/solana.storage.transaction_by_addr.rs")
    ));
}

impl From<Vec<Reward>> for generated::Rewards {
    fn from(rewards: Vec<Reward>) -> Self {
        Self {
            rewards: rewards.into_iter().map(|r| r.into()).collect(),
        }
    }
}

impl From<generated::Rewards> for Vec<Reward> {
    fn from(rewards: generated::Rewards) -> Self {
        rewards.rewards.into_iter().map(|r| r.into()).collect()
    }
}

impl From<StoredExtendedRewards> for generated::Rewards {
    fn from(rewards: StoredExtendedRewards) -> Self {
        Self {
            rewards: rewards
                .into_iter()
                .map(|r| {
                    let r: Reward = r.into();
                    r.into()
                })
                .collect(),
        }
    }
}

impl From<generated::Rewards> for StoredExtendedRewards {
    fn from(rewards: generated::Rewards) -> Self {
        rewards
            .rewards
            .into_iter()
            .map(|r| {
                let r: Reward = r.into();
                r.into()
            })
            .collect()
    }
}

impl From<Reward> for generated::Reward {
    fn from(reward: Reward) -> Self {
        Self {
            pubkey: reward.pubkey,
            lamports: reward.lamports,
            post_balance: reward.post_balance,
            reward_type: match reward.reward_type {
                None => generated::RewardType::Unspecified,
                Some(RewardType::Fee) => generated::RewardType::Fee,
                Some(RewardType::Rent) => generated::RewardType::Rent,
                Some(RewardType::Staking) => generated::RewardType::Staking,
                Some(RewardType::Voting) => generated::RewardType::Voting,
            } as i32,
        }
    }
}

impl From<generated::Reward> for Reward {
    fn from(reward: generated::Reward) -> Self {
        Self {
            pubkey: reward.pubkey,
            lamports: reward.lamports,
            post_balance: reward.post_balance,
            reward_type: match reward.reward_type {
                0 => None,
                1 => Some(RewardType::Fee),
                2 => Some(RewardType::Rent),
                3 => Some(RewardType::Staking),
                4 => Some(RewardType::Voting),
                _ => None,
            },
        }
    }
}

impl From<ConfirmedBlock> for generated::ConfirmedBlock {
    fn from(confirmed_block: ConfirmedBlock) -> Self {
        let ConfirmedBlock {
            previous_blockhash,
            blockhash,
            parent_slot,
            transactions,
            rewards,
            block_time,
        } = confirmed_block;

        Self {
            previous_blockhash,
            blockhash,
            parent_slot,
            transactions: transactions.into_iter().map(|tx| tx.into()).collect(),
            rewards: rewards.into_iter().map(|r| r.into()).collect(),
            block_time: block_time.map(|timestamp| generated::UnixTimestamp { timestamp }),
        }
    }
}

impl TryFrom<generated::ConfirmedBlock> for ConfirmedBlock {
    type Error = bincode::Error;
    fn try_from(
        confirmed_block: generated::ConfirmedBlock,
    ) -> std::result::Result<Self, Self::Error> {
        let generated::ConfirmedBlock {
            previous_blockhash,
            blockhash,
            parent_slot,
            transactions,
            rewards,
            block_time,
        } = confirmed_block;

        Ok(Self {
            previous_blockhash,
            blockhash,
            parent_slot,
            transactions: transactions
                .into_iter()
                .map(|tx| tx.try_into())
                .collect::<std::result::Result<Vec<TransactionWithStatusMeta>, Self::Error>>()?,
            rewards: rewards.into_iter().map(|r| r.into()).collect(),
            block_time: block_time.map(|generated::UnixTimestamp { timestamp }| timestamp),
        })
    }
}

impl From<TransactionWithStatusMeta> for generated::ConfirmedTransaction {
    fn from(value: TransactionWithStatusMeta) -> Self {
        let meta = if let Some(meta) = value.meta {
            Some(meta.into())
        } else {
            None
        };
        Self {
            transaction: Some(value.transaction.into()),
            meta,
        }
    }
}

impl TryFrom<generated::ConfirmedTransaction> for TransactionWithStatusMeta {
    type Error = bincode::Error;
    fn try_from(value: generated::ConfirmedTransaction) -> std::result::Result<Self, Self::Error> {
        let meta = if let Some(meta) = value.meta {
            Some(meta.try_into()?)
        } else {
            None
        };
        Ok(Self {
            transaction: value.transaction.expect("transaction is required").into(),
            meta,
        })
    }
}

impl From<Transaction> for generated::Transaction {
    fn from(value: Transaction) -> Self {
        Self {
            signatures: value
                .signatures
                .into_iter()
                .map(|signature| <Signature as AsRef<[u8]>>::as_ref(&signature).into())
                .collect(),
            message: Some(value.message.into()),
        }
    }
}

impl From<generated::Transaction> for Transaction {
    fn from(value: generated::Transaction) -> Self {
        Self {
            signatures: value
                .signatures
                .into_iter()
                .map(|x| Signature::new(&x))
                .collect(),
            message: value.message.expect("message is required").into(),
        }
    }
}

impl From<Message> for generated::Message {
    fn from(value: Message) -> Self {
        Self {
            header: Some(value.header.into()),
            account_keys: value
                .account_keys
                .into_iter()
                .map(|key| <Pubkey as AsRef<[u8]>>::as_ref(&key).into())
                .collect(),
            recent_blockhash: value.recent_blockhash.to_bytes().into(),
            instructions: value.instructions.into_iter().map(|ix| ix.into()).collect(),
        }
    }
}

impl From<generated::Message> for Message {
    fn from(value: generated::Message) -> Self {
        Self {
            header: value.header.expect("header is required").into(),
            account_keys: value
                .account_keys
                .into_iter()
                .map(|key| Pubkey::new(&key))
                .collect(),
            recent_blockhash: Hash::new(&value.recent_blockhash),
            instructions: value.instructions.into_iter().map(|ix| ix.into()).collect(),
        }
    }
}

impl From<MessageHeader> for generated::MessageHeader {
    fn from(value: MessageHeader) -> Self {
        Self {
            num_required_signatures: value.num_required_signatures as u32,
            num_readonly_signed_accounts: value.num_readonly_signed_accounts as u32,
            num_readonly_unsigned_accounts: value.num_readonly_unsigned_accounts as u32,
        }
    }
}

impl From<generated::MessageHeader> for MessageHeader {
    fn from(value: generated::MessageHeader) -> Self {
        Self {
            num_required_signatures: value.num_required_signatures as u8,
            num_readonly_signed_accounts: value.num_readonly_signed_accounts as u8,
            num_readonly_unsigned_accounts: value.num_readonly_unsigned_accounts as u8,
        }
    }
}

impl From<TransactionStatusMeta> for generated::TransactionStatusMeta {
    fn from(value: TransactionStatusMeta) -> Self {
        let TransactionStatusMeta {
            status,
            fee,
            pre_balances,
            post_balances,
            inner_instructions,
            log_messages,
            pre_token_balances,
            post_token_balances,
        } = value;
        let err = match status {
            Ok(()) => None,
            Err(err) => Some(generated::TransactionError {
                err: bincode::serialize(&err).expect("transaction error to serialize to bytes"),
            }),
        };
        let inner_instructions = inner_instructions
            .unwrap_or_default()
            .into_iter()
            .map(|ii| ii.into())
            .collect();
        let log_messages = log_messages.unwrap_or_default();
        let pre_token_balances = pre_token_balances
            .unwrap_or_default()
            .into_iter()
            .map(|balance| balance.into())
            .collect();
        let post_token_balances = post_token_balances
            .unwrap_or_default()
            .into_iter()
            .map(|balance| balance.into())
            .collect();

        Self {
            err,
            fee,
            pre_balances,
            post_balances,
            inner_instructions,
            log_messages,
            pre_token_balances,
            post_token_balances,
        }
    }
}

impl TryFrom<generated::TransactionStatusMeta> for TransactionStatusMeta {
    type Error = bincode::Error;

    fn try_from(value: generated::TransactionStatusMeta) -> std::result::Result<Self, Self::Error> {
        let generated::TransactionStatusMeta {
            err,
            fee,
            pre_balances,
            post_balances,
            inner_instructions,
            log_messages,
            pre_token_balances,
            post_token_balances,
        } = value;
        let status = match &err {
            None => Ok(()),
            Some(tx_error) => Err(bincode::deserialize(&tx_error.err)?),
        };
        let inner_instructions = Some(
            inner_instructions
                .into_iter()
                .map(|inner| inner.into())
                .collect(),
        );
        let log_messages = Some(log_messages);
        let pre_token_balances = Some(
            pre_token_balances
                .into_iter()
                .map(|balance| balance.into())
                .collect(),
        );
        let post_token_balances = Some(
            post_token_balances
                .into_iter()
                .map(|balance| balance.into())
                .collect(),
        );
        Ok(Self {
            status,
            fee,
            pre_balances,
            post_balances,
            inner_instructions,
            log_messages,
            pre_token_balances,
            post_token_balances,
        })
    }
}

impl From<InnerInstructions> for generated::InnerInstructions {
    fn from(value: InnerInstructions) -> Self {
        Self {
            index: value.index as u32,
            instructions: value.instructions.into_iter().map(|i| i.into()).collect(),
        }
    }
}

impl From<generated::InnerInstructions> for InnerInstructions {
    fn from(value: generated::InnerInstructions) -> Self {
        Self {
            index: value.index as u8,
            instructions: value.instructions.into_iter().map(|i| i.into()).collect(),
        }
    }
}

impl From<TransactionTokenBalance> for generated::TokenBalance {
    fn from(value: TransactionTokenBalance) -> Self {
        Self {
            account_index: value.account_index as u32,
            mint: value.mint,
            ui_token_amount: Some(generated::UiTokenAmount {
                ui_amount: value.ui_token_amount.ui_amount,
                decimals: value.ui_token_amount.decimals as u32,
                amount: value.ui_token_amount.amount,
            }),
        }
    }
}

impl From<generated::TokenBalance> for TransactionTokenBalance {
    fn from(value: generated::TokenBalance) -> Self {
        let ui_token_amount = value.ui_token_amount.unwrap_or_default();
        Self {
            account_index: value.account_index as u8,
            mint: value.mint,
            ui_token_amount: UiTokenAmount {
                ui_amount: ui_token_amount.ui_amount,
                decimals: ui_token_amount.decimals as u8,
                amount: ui_token_amount.amount,
            },
        }
    }
}

impl From<CompiledInstruction> for generated::CompiledInstruction {
    fn from(value: CompiledInstruction) -> Self {
        Self {
            program_id_index: value.program_id_index as u32,
            accounts: value.accounts,
            data: value.data,
        }
    }
}

impl From<generated::CompiledInstruction> for CompiledInstruction {
    fn from(value: generated::CompiledInstruction) -> Self {
        Self {
            program_id_index: value.program_id_index as u8,
            accounts: value.accounts,
            data: value.data,
        }
    }
}

impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
    type Error = &'static str;

    fn try_from(transaction_error: tx_by_addr::TransactionError) -> Result<Self, Self::Error> {
        if transaction_error.transaction_error == 8 {
            if let Some(instruction_error) = transaction_error.instruction_error {
                if let Some(custom) = instruction_error.custom {
                    return Ok(TransactionError::InstructionError(
                        instruction_error.index as u8,
                        InstructionError::Custom(custom.custom),
                    ));
                }

                let ie = match instruction_error.error {
                    0 => InstructionError::GenericError,
                    1 => InstructionError::InvalidArgument,
                    2 => InstructionError::InvalidInstructionData,
                    3 => InstructionError::InvalidAccountData,
                    4 => InstructionError::AccountDataTooSmall,
                    5 => InstructionError::InsufficientFunds,
                    6 => InstructionError::IncorrectProgramId,
                    7 => InstructionError::MissingRequiredSignature,
                    8 => InstructionError::AccountAlreadyInitialized,
                    9 => InstructionError::UninitializedAccount,
                    10 => InstructionError::UnbalancedInstruction,
                    11 => InstructionError::ModifiedProgramId,
                    12 => InstructionError::ExternalAccountLamportSpend,
                    13 => InstructionError::ExternalAccountDataModified,
                    14 => InstructionError::ReadonlyLamportChange,
                    15 => InstructionError::ReadonlyDataModified,
                    16 => InstructionError::DuplicateAccountIndex,
                    17 => InstructionError::ExecutableModified,
                    18 => InstructionError::RentEpochModified,
                    19 => InstructionError::NotEnoughAccountKeys,
                    20 => InstructionError::AccountDataSizeChanged,
                    21 => InstructionError::AccountNotExecutable,
                    22 => InstructionError::AccountBorrowFailed,
                    23 => InstructionError::AccountBorrowOutstanding,
                    24 => InstructionError::DuplicateAccountOutOfSync,
                    26 => InstructionError::InvalidError,
                    27 => InstructionError::ExecutableDataModified,
                    28 => InstructionError::ExecutableLamportChange,
                    29 => InstructionError::ExecutableAccountNotRentExempt,
                    30 => InstructionError::UnsupportedProgramId,
                    31 => InstructionError::CallDepth,
                    32 => InstructionError::MissingAccount,
                    33 => InstructionError::ReentrancyNotAllowed,
                    34 => InstructionError::MaxSeedLengthExceeded,
                    35 => InstructionError::InvalidSeeds,
                    36 => InstructionError::InvalidRealloc,
                    37 => InstructionError::ComputationalBudgetExceeded,
                    38 => InstructionError::PrivilegeEscalation,
                    39 => InstructionError::ProgramEnvironmentSetupFailure,
                    40 => InstructionError::ProgramFailedToComplete,
                    41 => InstructionError::ProgramFailedToCompile,
                    42 => InstructionError::Immutable,
                    43 => InstructionError::IncorrectAuthority,
                    44 => InstructionError::BorshIoError(String::new()),
                    45 => InstructionError::AccountNotRentExempt,
                    46 => InstructionError::InvalidAccountOwner,
                    _ => return Err("Invalid InstructionError"),
                };

                return Ok(TransactionError::InstructionError(
                    instruction_error.index as u8,
                    ie,
                ));
            }
        }

        Ok(match transaction_error.transaction_error {
            0 => TransactionError::AccountInUse,
            1 => TransactionError::AccountLoadedTwice,
            2 => TransactionError::AccountNotFound,
            3 => TransactionError::ProgramAccountNotFound,
            4 => TransactionError::InsufficientFundsForFee,
            5 => TransactionError::InvalidAccountForFee,
            6 => TransactionError::DuplicateSignature,
            7 => TransactionError::BlockhashNotFound,
            9 => TransactionError::CallChainTooDeep,
            10 => TransactionError::MissingSignatureForFee,
            11 => TransactionError::InvalidAccountIndex,
            12 => TransactionError::SignatureFailure,
            13 => TransactionError::InvalidProgramForExecution,
            14 => TransactionError::SanitizeFailure,
            15 => TransactionError::ClusterMaintenance,
            _ => return Err("Invalid TransactionError"),
        })
    }
}

impl From<TransactionError> for tx_by_addr::TransactionError {
    fn from(transaction_error: TransactionError) -> Self {
        Self {
            transaction_error: match transaction_error {
                TransactionError::AccountInUse => tx_by_addr::TransactionErrorType::AccountInUse,
                TransactionError::AccountLoadedTwice => {
                    tx_by_addr::TransactionErrorType::AccountLoadedTwice
                }
                TransactionError::AccountNotFound => {
                    tx_by_addr::TransactionErrorType::AccountNotFound
                }
                TransactionError::ProgramAccountNotFound => {
                    tx_by_addr::TransactionErrorType::ProgramAccountNotFound
                }
                TransactionError::InsufficientFundsForFee => {
                    tx_by_addr::TransactionErrorType::InsufficientFundsForFee
                }
                TransactionError::InvalidAccountForFee => {
                    tx_by_addr::TransactionErrorType::InvalidAccountForFee
                }
                TransactionError::DuplicateSignature => {
                    tx_by_addr::TransactionErrorType::DuplicateSignature
                }
                TransactionError::BlockhashNotFound => {
                    tx_by_addr::TransactionErrorType::BlockhashNotFound
                }
                TransactionError::CallChainTooDeep => {
                    tx_by_addr::TransactionErrorType::CallChainTooDeep
                }
                TransactionError::MissingSignatureForFee => {
                    tx_by_addr::TransactionErrorType::MissingSignatureForFee
                }
                TransactionError::InvalidAccountIndex => {
                    tx_by_addr::TransactionErrorType::InvalidAccountIndex
                }
                TransactionError::SignatureFailure => {
                    tx_by_addr::TransactionErrorType::SignatureFailure
                }
                TransactionError::InvalidProgramForExecution => {
                    tx_by_addr::TransactionErrorType::InvalidProgramForExecution
                }
                TransactionError::SanitizeFailure => {
                    tx_by_addr::TransactionErrorType::SanitizeFailure
                }
                TransactionError::ClusterMaintenance => {
                    tx_by_addr::TransactionErrorType::ClusterMaintenance
                }
                TransactionError::InstructionError(_, _) => {
                    tx_by_addr::TransactionErrorType::InstructionError
                }
            } as i32,
            instruction_error: match transaction_error {
                TransactionError::InstructionError(index, ref instruction_error) => {
                    Some(tx_by_addr::InstructionError {
                        index: index as u32,
                        error: match instruction_error {
                            InstructionError::GenericError => {
                                tx_by_addr::InstructionErrorType::GenericError
                            }
                            InstructionError::InvalidArgument => {
                                tx_by_addr::InstructionErrorType::InvalidArgument
                            }
                            InstructionError::InvalidInstructionData => {
                                tx_by_addr::InstructionErrorType::InvalidInstructionData
                            }
                            InstructionError::InvalidAccountData => {
                                tx_by_addr::InstructionErrorType::InvalidAccountData
                            }
                            InstructionError::AccountDataTooSmall => {
                                tx_by_addr::InstructionErrorType::AccountDataTooSmall
                            }
                            InstructionError::InsufficientFunds => {
                                tx_by_addr::InstructionErrorType::InsufficientFunds
                            }
                            InstructionError::IncorrectProgramId => {
                                tx_by_addr::InstructionErrorType::IncorrectProgramId
                            }
                            InstructionError::MissingRequiredSignature => {
                                tx_by_addr::InstructionErrorType::MissingRequiredSignature
                            }
                            InstructionError::AccountAlreadyInitialized => {
                                tx_by_addr::InstructionErrorType::AccountAlreadyInitialized
                            }
                            InstructionError::UninitializedAccount => {
                                tx_by_addr::InstructionErrorType::UninitializedAccount
                            }
                            InstructionError::UnbalancedInstruction => {
                                tx_by_addr::InstructionErrorType::UnbalancedInstruction
                            }
                            InstructionError::ModifiedProgramId => {
                                tx_by_addr::InstructionErrorType::ModifiedProgramId
                            }
                            InstructionError::ExternalAccountLamportSpend => {
                                tx_by_addr::InstructionErrorType::ExternalAccountLamportSpend
                            }
                            InstructionError::ExternalAccountDataModified => {
                                tx_by_addr::InstructionErrorType::ExternalAccountDataModified
                            }
                            InstructionError::ReadonlyLamportChange => {
                                tx_by_addr::InstructionErrorType::ReadonlyLamportChange
                            }
                            InstructionError::ReadonlyDataModified => {
                                tx_by_addr::InstructionErrorType::ReadonlyDataModified
                            }
                            InstructionError::DuplicateAccountIndex => {
                                tx_by_addr::InstructionErrorType::DuplicateAccountIndex
                            }
                            InstructionError::ExecutableModified => {
                                tx_by_addr::InstructionErrorType::ExecutableModified
                            }
                            InstructionError::RentEpochModified => {
                                tx_by_addr::InstructionErrorType::RentEpochModified
                            }
                            InstructionError::NotEnoughAccountKeys => {
                                tx_by_addr::InstructionErrorType::NotEnoughAccountKeys
                            }
                            InstructionError::AccountDataSizeChanged => {
                                tx_by_addr::InstructionErrorType::AccountDataSizeChanged
                            }
                            InstructionError::AccountNotExecutable => {
                                tx_by_addr::InstructionErrorType::AccountNotExecutable
                            }
                            InstructionError::AccountBorrowFailed => {
                                tx_by_addr::InstructionErrorType::AccountBorrowFailed
                            }
                            InstructionError::AccountBorrowOutstanding => {
                                tx_by_addr::InstructionErrorType::AccountBorrowOutstanding
                            }
                            InstructionError::DuplicateAccountOutOfSync => {
                                tx_by_addr::InstructionErrorType::DuplicateAccountOutOfSync
                            }
                            InstructionError::Custom(_) => tx_by_addr::InstructionErrorType::Custom,
                            InstructionError::InvalidError => {
                                tx_by_addr::InstructionErrorType::InvalidError
                            }
                            InstructionError::ExecutableDataModified => {
                                tx_by_addr::InstructionErrorType::ExecutableDataModified
                            }
                            InstructionError::ExecutableLamportChange => {
                                tx_by_addr::InstructionErrorType::ExecutableLamportChange
                            }
                            InstructionError::ExecutableAccountNotRentExempt => {
                                tx_by_addr::InstructionErrorType::ExecutableAccountNotRentExempt
                            }
                            InstructionError::UnsupportedProgramId => {
                                tx_by_addr::InstructionErrorType::UnsupportedProgramId
                            }
                            InstructionError::CallDepth => {
                                tx_by_addr::InstructionErrorType::CallDepth
                            }
                            InstructionError::MissingAccount => {
                                tx_by_addr::InstructionErrorType::MissingAccount
                            }
                            InstructionError::ReentrancyNotAllowed => {
                                tx_by_addr::InstructionErrorType::ReentrancyNotAllowed
                            }
                            InstructionError::MaxSeedLengthExceeded => {
                                tx_by_addr::InstructionErrorType::MaxSeedLengthExceeded
                            }
                            InstructionError::InvalidSeeds => {
                                tx_by_addr::InstructionErrorType::InvalidSeeds
                            }
                            InstructionError::InvalidRealloc => {
                                tx_by_addr::InstructionErrorType::InvalidRealloc
                            }
                            InstructionError::ComputationalBudgetExceeded => {
                                tx_by_addr::InstructionErrorType::ComputationalBudgetExceeded
                            }
                            InstructionError::PrivilegeEscalation => {
                                tx_by_addr::InstructionErrorType::PrivilegeEscalation
                            }
                            InstructionError::ProgramEnvironmentSetupFailure => {
                                tx_by_addr::InstructionErrorType::ProgramEnvironmentSetupFailure
                            }
                            InstructionError::ProgramFailedToComplete => {
                                tx_by_addr::InstructionErrorType::ProgramFailedToComplete
                            }
                            InstructionError::ProgramFailedToCompile => {
                                tx_by_addr::InstructionErrorType::ProgramFailedToCompile
                            }
                            InstructionError::Immutable => {
                                tx_by_addr::InstructionErrorType::Immutable
                            }
                            InstructionError::IncorrectAuthority => {
                                tx_by_addr::InstructionErrorType::IncorrectAuthority
                            }
                            InstructionError::BorshIoError(_) => {
                                tx_by_addr::InstructionErrorType::BorshIoError
                            }
                            InstructionError::AccountNotRentExempt => {
                                tx_by_addr::InstructionErrorType::AccountNotRentExempt
                            }
                            InstructionError::InvalidAccountOwner => {
                                tx_by_addr::InstructionErrorType::InvalidAccountOwner
                            }
                        } as i32,
                        custom: match instruction_error {
                            InstructionError::Custom(custom) => {
                                Some(tx_by_addr::CustomError { custom: *custom })
                            }
                            _ => None,
                        },
                    })
                }
                _ => None,
            },
        }
    }
}

impl From<TransactionByAddrInfo> for tx_by_addr::TransactionByAddrInfo {
    fn from(by_addr: TransactionByAddrInfo) -> Self {
        let TransactionByAddrInfo {
            signature,
            err,
            index,
            memo,
            block_time,
        } = by_addr;

        Self {
            signature: <Signature as AsRef<[u8]>>::as_ref(&signature).into(),
            err: match err {
                None => None,
                Some(e) => Some(e.into()),
            },
            index,
            memo: memo.map(|memo| tx_by_addr::Memo { memo }),
            block_time: block_time.map(|timestamp| tx_by_addr::UnixTimestamp { timestamp }),
        }
    }
}

impl TryFrom<tx_by_addr::TransactionByAddrInfo> for TransactionByAddrInfo {
    type Error = &'static str;

    fn try_from(
        transaction_by_addr: tx_by_addr::TransactionByAddrInfo,
    ) -> Result<Self, Self::Error> {
        let err = if let Some(err) = transaction_by_addr.err {
            Some(err.try_into()?)
        } else {
            None
        };

        Ok(Self {
            signature: Signature::new(&transaction_by_addr.signature),
            err,
            index: transaction_by_addr.index,
            memo: transaction_by_addr
                .memo
                .map(|tx_by_addr::Memo { memo }| memo),
            block_time: transaction_by_addr
                .block_time
                .map(|tx_by_addr::UnixTimestamp { timestamp }| timestamp),
        })
    }
}

impl TryFrom<tx_by_addr::TransactionByAddr> for Vec<TransactionByAddrInfo> {
    type Error = &'static str;

    fn try_from(collection: tx_by_addr::TransactionByAddr) -> Result<Self, Self::Error> {
        collection
            .tx_by_addrs
            .into_iter()
            .map(|tx_by_addr| tx_by_addr.try_into())
            .collect::<Result<Vec<TransactionByAddrInfo>, Self::Error>>()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_reward_type_encode() {
        let mut reward = Reward {
            pubkey: "invalid".to_string(),
            lamports: 123,
            post_balance: 321,
            reward_type: None,
        };
        let gen_reward: generated::Reward = reward.clone().into();
        assert_eq!(reward, gen_reward.into());

        reward.reward_type = Some(RewardType::Fee);
        let gen_reward: generated::Reward = reward.clone().into();
        assert_eq!(reward, gen_reward.into());

        reward.reward_type = Some(RewardType::Rent);
        let gen_reward: generated::Reward = reward.clone().into();
        assert_eq!(reward, gen_reward.into());

        reward.reward_type = Some(RewardType::Voting);
        let gen_reward: generated::Reward = reward.clone().into();
        assert_eq!(reward, gen_reward.into());

        reward.reward_type = Some(RewardType::Staking);
        let gen_reward: generated::Reward = reward.clone().into();
        assert_eq!(reward, gen_reward.into());
    }

    #[test]
    fn test_transaction_by_addr_encode() {
        let info = TransactionByAddrInfo {
            signature: Signature::new(&bs58::decode("Nfo6rgemG1KLbk1xuNwfrQTsdxaGfLuWURHNRy9LYnDrubG7LFQZaA5obPNas9LQ6DdorJqxh2LxA3PsnWdkSrL").into_vec().unwrap()),
            err: None,
            index: 5,
            memo: Some("string".to_string()),
            block_time: Some(1610674861)
        };

        let tx_by_addr_transaction_info: tx_by_addr::TransactionByAddrInfo = info.clone().into();
        assert_eq!(info, tx_by_addr_transaction_info.try_into().unwrap());
    }

    #[test]
    fn test_transaction_error_encode() {
        let transaction_error = TransactionError::AccountInUse;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::AccountLoadedTwice;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::AccountNotFound;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::BlockhashNotFound;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::CallChainTooDeep;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::ClusterMaintenance;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::DuplicateSignature;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::InsufficientFundsForFee;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::InvalidAccountForFee;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::InvalidAccountIndex;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::InvalidProgramForExecution;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::MissingSignatureForFee;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::ProgramAccountNotFound;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::SanitizeFailure;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::SignatureFailure;
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::AccountAlreadyInitialized);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::AccountBorrowFailed);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::AccountBorrowOutstanding);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::AccountDataSizeChanged);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::AccountDataTooSmall);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::AccountNotExecutable);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::InstructionError(10, InstructionError::CallDepth);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ComputationalBudgetExceeded);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::DuplicateAccountIndex);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::DuplicateAccountOutOfSync);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::InstructionError(
            10,
            InstructionError::ExecutableAccountNotRentExempt,
        );
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ExecutableDataModified);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ExecutableLamportChange);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ExecutableModified);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ExternalAccountDataModified);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ExternalAccountLamportSpend);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::GenericError);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::InstructionError(10, InstructionError::Immutable);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::IncorrectAuthority);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::IncorrectProgramId);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::InsufficientFunds);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::InvalidAccountData);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::InvalidArgument);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::InvalidError);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::InvalidInstructionData);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::InvalidRealloc);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::InvalidSeeds);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::MaxSeedLengthExceeded);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::MissingAccount);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::MissingRequiredSignature);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ModifiedProgramId);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::NotEnoughAccountKeys);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::PrivilegeEscalation);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error = TransactionError::InstructionError(
            10,
            InstructionError::ProgramEnvironmentSetupFailure,
        );
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ProgramFailedToCompile);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ProgramFailedToComplete);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ReadonlyDataModified);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ReadonlyLamportChange);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::ReentrancyNotAllowed);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::RentEpochModified);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::UnbalancedInstruction);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::UninitializedAccount);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::UnsupportedProgramId);
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );

        let transaction_error =
            TransactionError::InstructionError(10, InstructionError::Custom(10));
        let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
            transaction_error.clone().into();
        assert_eq!(
            transaction_error,
            tx_by_addr_transaction_error.try_into().unwrap()
        );
    }
}
