#![allow(clippy::too_many_arguments)]
use derive_more::{From, Into};
extern crate base64;
use pythonize::{depythonize, pythonize};
use solders_traits::{
    handle_py_value_err, CommonMethods, PyBytesBincode, PyFromBytesBincode, RichcmpEqualityOnly,
};
use std::fmt::Display;
use std::str::FromStr;

use crate::{
    account_decoder::UiTokenAmount,
    commitment_config::CommitmentConfig,
    message::MessageHeader,
    pubkey::Pubkey,
    signature::Signature,
    tmp_transaction_status::{
        EncodedConfirmedTransactionWithStatusMeta as EncodedConfirmedTransactionWithStatusMetaOriginal,
        EncodedTransaction as EncodedTransactionOriginal,
        EncodedTransactionWithStatusMeta as EncodedTransactionWithStatusMetaOriginal,
        ParsedAccount as ParsedAccountOriginal, ParsedInstruction as ParsedInstructionOriginal,
        Reward as RewardOriginal, RewardType as RewardTypeOriginal,
        TransactionBinaryEncoding as TransactionBinaryEncodingOriginal,
        TransactionConfirmationStatus as TransactionConfirmationStatusOriginal,
        TransactionStatus as TransactionStatusOriginal,
        UiAddressTableLookup as UiAddressTableLookupOriginal,
        UiCompiledInstruction as UiCompiledInstructionOriginal,
        UiConfirmedBlock as UiConfirmedBlockOriginal,
        UiInnerInstructions as UiInnerInstructionsOriginal, UiInstruction as UiInstructionOriginal,
        UiLoadedAddresses as UiLoadedAddressesOriginal, UiMessage as UiMessageOriginal,
        UiParsedInstruction as UiParsedInstructionOriginal,
        UiParsedMessage as UiParsedMessageOriginal,
        UiPartiallyDecodedInstruction as UiPartiallyDecodedInstructionOriginal,
        UiRawMessage as UiRawMessageOriginal, UiTransaction as UiTransactionOriginal,
        UiTransactionStatusMeta as UiTransactionStatusMetaOriginal,
        UiTransactionTokenBalance as UiTransactionTokenBalanceOriginal,
    },
    transaction::{TransactionVersion, VersionedTransaction},
    SolderHash,
};
use pyo3::{
    prelude::*,
    types::{PyBytes, PyTuple},
    PyTypeInfo,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use solana_sdk::{
    clock::UnixTimestamp, instruction::InstructionError as InstructionErrorOriginal,
    slot_history::Slot, transaction::TransactionError as TransactionErrorOriginal,
    transaction_context::TransactionReturnData as TransactionReturnDataOriginal,
};
use solders_macros::{common_methods, enum_original_mapping, richcmp_eq_only};

macro_rules! transaction_status_boilerplate {
    ($name:ident) => {
        impl RichcmpEqualityOnly for $name {}
        impl Display for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "{:?}", self)
            }
        }
        solders_traits::pybytes_general_via_bincode!($name);
        solders_traits::py_from_bytes_general_via_bincode!($name);
        impl<'a> CommonMethods<'a> for $name {}
    };
}

pub(crate) use transaction_status_boilerplate;

/// Encoding options for transaction data.
#[pyclass(module = "solders.transaction_status")]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum UiTransactionEncoding {
    Binary, // Legacy. Retained for RPC backwards compatibility
    Base64,
    Base58,
    Json,
    JsonParsed,
}

impl Default for UiTransactionEncoding {
    fn default() -> Self {
        Self::Base64
    }
}

/// Levels of transaction detail to return in RPC requests.
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[pyclass(module = "solders.transaction_status")]
pub enum TransactionDetails {
    Full,
    Signatures,
    #[serde(rename = "none")]
    None_,
}

impl Default for TransactionDetails {
    fn default() -> Self {
        Self::Full
    }
}

#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[serde(rename_all = "camelCase")]
#[enum_original_mapping(TransactionBinaryEncodingOriginal)]
#[pyclass(module = "solders.transaction_status")]
pub enum TransactionBinaryEncoding {
    Base58,
    Base64,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiCompiledInstruction(UiCompiledInstructionOriginal);

transaction_status_boilerplate!(UiCompiledInstruction);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiCompiledInstruction {
    #[new]
    fn new(program_id_index: u8, accounts: Vec<u8>, data: String) -> Self {
        UiCompiledInstructionOriginal {
            program_id_index,
            accounts,
            data,
        }
        .into()
    }

    #[getter]
    pub fn program_id_index(&self) -> u8 {
        self.0.program_id_index
    }

    #[getter]
    pub fn accounts<'a>(&self, py: Python<'a>) -> &'a PyBytes {
        PyBytes::new(py, &self.0.accounts)
    }

    #[getter]
    pub fn data(&self) -> String {
        self.0.data.clone()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiAddressTableLookup(UiAddressTableLookupOriginal);

transaction_status_boilerplate!(UiAddressTableLookup);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiAddressTableLookup {
    #[new]
    fn new(account_key: Pubkey, writable_indexes: Vec<u8>, readonly_indexes: Vec<u8>) -> Self {
        UiAddressTableLookupOriginal {
            account_key: account_key.to_string(),
            writable_indexes,
            readonly_indexes,
        }
        .into()
    }

    #[getter]
    pub fn account_key(&self) -> Pubkey {
        Pubkey::from_str(&self.0.account_key).unwrap()
    }

    #[getter]
    pub fn writable_indexes<'a>(&self, py: Python<'a>) -> &'a PyBytes {
        PyBytes::new(py, &self.0.writable_indexes)
    }

    #[getter]
    pub fn readonly_indexes<'a>(&self, py: Python<'a>) -> &'a PyBytes {
        PyBytes::new(py, &self.0.readonly_indexes)
    }
}

/// A duplicate representation of a Message, in raw format, for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiRawMessage(UiRawMessageOriginal);

transaction_status_boilerplate!(UiRawMessage);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiRawMessage {
    #[new]
    fn new(
        header: MessageHeader,
        account_keys: Vec<Pubkey>,
        recent_blockhash: SolderHash,
        instructions: Vec<UiCompiledInstruction>,
        address_table_lookups: Option<Vec<UiAddressTableLookup>>,
    ) -> Self {
        UiRawMessageOriginal {
            header: header.into(),
            account_keys: account_keys.into_iter().map(|p| p.to_string()).collect(),
            recent_blockhash: recent_blockhash.to_string(),
            instructions: instructions.into_iter().map(|ix| ix.into()).collect(),
            address_table_lookups: address_table_lookups
                .map(|v| v.into_iter().map(|a| a.into()).collect()),
        }
        .into()
    }

    #[getter]
    pub fn header(&self) -> MessageHeader {
        self.0.header.into()
    }

    #[getter]
    pub fn account_keys(&self) -> Vec<Pubkey> {
        self.0
            .account_keys
            .iter()
            .map(|s| Pubkey::from_str(s).unwrap())
            .collect()
    }

    #[getter]
    pub fn recent_blockhash(&self) -> SolderHash {
        SolderHash::from_str(&self.0.recent_blockhash).unwrap()
    }

    #[getter]
    pub fn instructions(&self) -> Vec<UiCompiledInstruction> {
        self.0
            .instructions
            .clone()
            .into_iter()
            .map(|ix| ix.into())
            .collect()
    }

    #[getter]
    pub fn address_table_lookups(&self) -> Option<Vec<UiAddressTableLookup>> {
        self.0
            .address_table_lookups
            .clone()
            .map(|v| v.into_iter().map(UiAddressTableLookup::from).collect())
    }
}

/// A duplicate representation of a Message, in raw format, for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct ParsedAccount(ParsedAccountOriginal);

transaction_status_boilerplate!(ParsedAccount);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl ParsedAccount {
    #[new]
    fn new(pubkey: Pubkey, writable: bool, signer: bool) -> Self {
        ParsedAccountOriginal {
            pubkey: pubkey.to_string(),
            writable,
            signer,
        }
        .into()
    }

    #[getter]
    pub fn pubkey(&self) -> Pubkey {
        Pubkey::from_str(&self.0.pubkey).unwrap()
    }

    #[getter]
    pub fn writable(&self) -> bool {
        self.0.writable
    }

    #[getter]
    pub fn signer(&self) -> bool {
        self.0.signer
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct ParsedInstruction(ParsedInstructionOriginal);

transaction_status_boilerplate!(ParsedInstruction);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl ParsedInstruction {
    #[new]
    fn new(program: String, program_id: Pubkey, parsed: &PyAny) -> PyResult<Self> {
        let value = handle_py_value_err(depythonize::<Value>(parsed))?;
        Ok(ParsedInstructionOriginal {
            program,
            program_id: program_id.to_string(),
            parsed: value,
        }
        .into())
    }

    #[getter]
    pub fn program(&self) -> String {
        self.0.program.clone()
    }

    #[getter]
    pub fn program_id(&self) -> Pubkey {
        Pubkey::from_str(&self.0.program_id).unwrap()
    }

    #[getter]
    pub fn parsed(&self, py: Python<'_>) -> PyResult<PyObject> {
        handle_py_value_err(pythonize(py, &self.0.parsed))
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiPartiallyDecodedInstruction(UiPartiallyDecodedInstructionOriginal);

transaction_status_boilerplate!(UiPartiallyDecodedInstruction);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiPartiallyDecodedInstruction {
    #[new]
    fn new(program_id: Pubkey, accounts: Vec<Pubkey>, data: String) -> Self {
        UiPartiallyDecodedInstructionOriginal {
            program_id: program_id.to_string(),
            accounts: accounts.into_iter().map(|a| a.to_string()).collect(),
            data,
        }
        .into()
    }

    #[getter]
    pub fn program_id(&self) -> Pubkey {
        Pubkey::from_str(&self.0.program_id).unwrap()
    }

    #[getter]
    pub fn accounts(&self) -> Vec<Pubkey> {
        self.0
            .accounts
            .clone()
            .into_iter()
            .map(|a| Pubkey::from_str(&a).unwrap())
            .collect()
    }

    #[getter]
    pub fn data(&self) -> String {
        self.0.data.clone()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromPyObject)]
#[serde(rename_all = "camelCase", untagged)]
pub enum UiParsedInstruction {
    Parsed(ParsedInstruction),
    PartiallyDecoded(UiPartiallyDecodedInstruction),
}

impl IntoPy<PyObject> for UiParsedInstruction {
    fn into_py(self, py: Python<'_>) -> PyObject {
        match self {
            Self::Parsed(m) => m.into_py(py),
            Self::PartiallyDecoded(m) => m.into_py(py),
        }
    }
}

impl From<UiParsedInstruction> for UiParsedInstructionOriginal {
    fn from(ix: UiParsedInstruction) -> Self {
        match ix {
            UiParsedInstruction::Parsed(p) => Self::Parsed(p.into()),
            UiParsedInstruction::PartiallyDecoded(p) => Self::PartiallyDecoded(p.into()),
        }
    }
}

impl From<UiParsedInstructionOriginal> for UiParsedInstruction {
    fn from(ix: UiParsedInstructionOriginal) -> Self {
        match ix {
            UiParsedInstructionOriginal::Parsed(p) => Self::Parsed(p.into()),
            UiParsedInstructionOriginal::PartiallyDecoded(p) => Self::PartiallyDecoded(p.into()),
        }
    }
}

/// A duplicate representation of an Instruction for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromPyObject)]
#[serde(rename_all = "camelCase", untagged)]
pub enum UiInstruction {
    Compiled(UiCompiledInstruction),
    Parsed(UiParsedInstruction),
}

impl IntoPy<PyObject> for UiInstruction {
    fn into_py(self, py: Python<'_>) -> PyObject {
        match self {
            Self::Compiled(m) => m.into_py(py),
            Self::Parsed(m) => m.into_py(py),
        }
    }
}

impl From<UiInstruction> for UiInstructionOriginal {
    fn from(ix: UiInstruction) -> Self {
        match ix {
            UiInstruction::Compiled(c) => Self::Compiled(c.into()),
            UiInstruction::Parsed(p) => Self::Parsed(p.into()),
        }
    }
}

impl From<UiInstructionOriginal> for UiInstruction {
    fn from(ix: UiInstructionOriginal) -> Self {
        match ix {
            UiInstructionOriginal::Compiled(c) => Self::Compiled(c.into()),
            UiInstructionOriginal::Parsed(p) => Self::Parsed(p.into()),
        }
    }
}

/// A duplicate representation of a Message, in raw format, for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiParsedMessage(UiParsedMessageOriginal);

transaction_status_boilerplate!(UiParsedMessage);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiParsedMessage {
    #[new]
    fn new(
        account_keys: Vec<ParsedAccount>,
        recent_blockhash: SolderHash,
        instructions: Vec<UiInstruction>,
        address_table_lookups: Option<Vec<UiAddressTableLookup>>,
    ) -> Self {
        UiParsedMessageOriginal {
            account_keys: account_keys.into_iter().map(|p| p.into()).collect(),
            recent_blockhash: recent_blockhash.to_string(),
            instructions: instructions.into_iter().map(|ix| ix.into()).collect(),
            address_table_lookups: address_table_lookups
                .map(|v| v.into_iter().map(|a| a.into()).collect()),
        }
        .into()
    }

    #[getter]
    pub fn account_keys(&self) -> Vec<ParsedAccount> {
        self.0
            .account_keys
            .clone()
            .into_iter()
            .map(|p| p.into())
            .collect()
    }

    #[getter]
    pub fn recent_blockhash(&self) -> SolderHash {
        SolderHash::from_str(&self.0.recent_blockhash).unwrap()
    }

    #[getter]
    pub fn instructions(&self) -> Vec<UiInstruction> {
        self.0
            .instructions
            .clone()
            .into_iter()
            .map(|ix| ix.into())
            .collect()
    }

    #[getter]
    pub fn address_table_lookups(&self) -> Option<Vec<UiAddressTableLookup>> {
        self.0
            .address_table_lookups
            .clone()
            .map(|v| v.into_iter().map(|a| a.into()).collect())
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromPyObject)]
#[serde(rename_all = "camelCase", untagged)]
pub enum UiMessage {
    Parsed(UiParsedMessage),
    Raw(UiRawMessage),
}

impl From<UiMessageOriginal> for UiMessage {
    fn from(m: UiMessageOriginal) -> Self {
        match m {
            UiMessageOriginal::Parsed(msg) => Self::Parsed(msg.into()),
            UiMessageOriginal::Raw(msg) => Self::Raw(msg.into()),
        }
    }
}

impl From<UiMessage> for UiMessageOriginal {
    fn from(m: UiMessage) -> Self {
        match m {
            UiMessage::Parsed(msg) => Self::Parsed(msg.into()),
            UiMessage::Raw(msg) => Self::Raw(msg.into()),
        }
    }
}

impl IntoPy<PyObject> for UiMessage {
    fn into_py(self, py: Python<'_>) -> PyObject {
        match self {
            Self::Parsed(p) => p.into_py(py),
            Self::Raw(r) => r.into_py(py),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiTransaction(UiTransactionOriginal);

transaction_status_boilerplate!(UiTransaction);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiTransaction {
    #[new]
    fn new(signatures: Vec<Signature>, message: UiMessage) -> Self {
        UiTransactionOriginal {
            signatures: signatures.into_iter().map(|s| s.to_string()).collect(),
            message: message.into(),
        }
        .into()
    }

    #[getter]
    pub fn signatures(&self) -> Vec<Signature> {
        self.0
            .signatures
            .iter()
            .map(|s| Signature::from_str(s).unwrap())
            .collect()
    }

    #[getter]
    pub fn message(&self) -> UiMessage {
        self.0.message.clone().into()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromPyObject)]
#[serde(rename_all = "camelCase", untagged)]
pub enum EncodedVersionedTransaction {
    Binary(VersionedTransaction),
    Json(UiTransaction),
}

impl From<EncodedTransaction> for EncodedVersionedTransaction {
    fn from(e: EncodedTransaction) -> Self {
        match e {
            EncodedTransaction::LegacyBinary(..) | EncodedTransaction::Binary(..) => Self::Binary(
                VersionedTransaction::from(EncodedTransactionOriginal::from(e).decode().unwrap()),
            ),
            EncodedTransaction::Json(u) => Self::Json(u),
        }
    }
}

impl From<EncodedVersionedTransaction> for EncodedTransaction {
    fn from(e: EncodedVersionedTransaction) -> Self {
        match e {
            EncodedVersionedTransaction::Binary(v) => Self::Binary(
                base64::encode(bincode::serialize(&v).unwrap()),
                TransactionBinaryEncoding::Base64,
            ),
            EncodedVersionedTransaction::Json(u) => Self::Json(u),
        }
    }
}

impl From<EncodedVersionedTransaction> for EncodedTransactionOriginal {
    fn from(e: EncodedVersionedTransaction) -> Self {
        EncodedTransaction::from(e).into()
    }
}

impl From<EncodedTransactionOriginal> for EncodedVersionedTransaction {
    fn from(e: EncodedTransactionOriginal) -> Self {
        EncodedTransaction::from(e).into()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromPyObject)]
#[serde(rename_all = "camelCase", untagged)]
pub enum EncodedTransaction {
    LegacyBinary(String), // Old way of expressing base-58, retained for RPC backwards compatibility
    Binary(String, TransactionBinaryEncoding),
    Json(UiTransaction),
}

impl IntoPy<PyObject> for EncodedVersionedTransaction {
    fn into_py(self, py: Python<'_>) -> PyObject {
        match self {
            Self::Binary(b) => b.into_py(py),
            Self::Json(u) => u.into_py(py),
        }
    }
}

impl From<EncodedTransactionOriginal> for EncodedTransaction {
    fn from(e: EncodedTransactionOriginal) -> Self {
        match e {
            EncodedTransactionOriginal::LegacyBinary(s) => Self::LegacyBinary(s),
            EncodedTransactionOriginal::Binary(s, b) => Self::Binary(s, b.into()),
            EncodedTransactionOriginal::Json(t) => Self::Json(t.into()),
        }
    }
}

impl From<EncodedTransaction> for EncodedTransactionOriginal {
    fn from(e: EncodedTransaction) -> Self {
        match e {
            EncodedTransaction::LegacyBinary(s) => Self::LegacyBinary(s),
            EncodedTransaction::Binary(s, b) => Self::Binary(s, b.into()),
            EncodedTransaction::Json(t) => Self::Json(t.into()),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiInnerInstructions(UiInnerInstructionsOriginal);

transaction_status_boilerplate!(UiInnerInstructions);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiInnerInstructions {
    #[new]
    pub fn new(index: u8, instructions: Vec<UiInstruction>) -> Self {
        UiInnerInstructionsOriginal {
            index,
            instructions: instructions.into_iter().map(|ix| ix.into()).collect(),
        }
        .into()
    }

    #[getter]
    pub fn index(&self) -> u8 {
        self.0.index
    }

    #[getter]
    pub fn instructions(&self) -> Vec<UiInstruction> {
        self.0
            .instructions
            .clone()
            .into_iter()
            .map(|ix| ix.into())
            .collect()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiLoadedAddresses(UiLoadedAddressesOriginal);

transaction_status_boilerplate!(UiLoadedAddresses);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiLoadedAddresses {
    #[new]
    pub fn new(writable: Vec<Pubkey>, readonly: Vec<Pubkey>) -> Self {
        UiLoadedAddressesOriginal {
            writable: writable.iter().map(|x| x.to_string()).collect(),
            readonly: readonly.iter().map(|x| x.to_string()).collect(),
        }
        .into()
    }

    #[getter]
    pub fn writable(&self) -> Vec<Pubkey> {
        self.0
            .writable
            .iter()
            .map(|x| Pubkey::from_str(x).unwrap())
            .collect()
    }

    #[getter]
    pub fn readonly(&self) -> Vec<Pubkey> {
        self.0
            .readonly
            .iter()
            .map(|x| Pubkey::from_str(x).unwrap())
            .collect()
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiTransactionTokenBalance(UiTransactionTokenBalanceOriginal);

transaction_status_boilerplate!(UiTransactionTokenBalance);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiTransactionTokenBalance {
    #[new]
    pub fn new(
        account_index: u8,
        mint: Pubkey,
        ui_token_amount: UiTokenAmount,
        owner: Option<Pubkey>,
        program_id: Option<Pubkey>,
    ) -> Self {
        UiTransactionTokenBalanceOriginal {
            account_index,
            mint: mint.to_string(),
            ui_token_amount: ui_token_amount.into(),
            owner: owner.map(|x| x.to_string()),
            program_id: program_id.map(|x| x.to_string()),
        }
        .into()
    }

    #[getter]
    pub fn account_index(&self) -> u8 {
        self.0.account_index
    }

    #[getter]
    pub fn mint(&self) -> Pubkey {
        Pubkey::from_str(&self.0.mint).unwrap()
    }

    #[getter]
    pub fn ui_token_amount(&self) -> UiTokenAmount {
        self.0.ui_token_amount.clone().into()
    }

    #[getter]
    pub fn owner(&self) -> Option<Pubkey> {
        self.0.owner.clone().map(|x| Pubkey::from_str(&x).unwrap())
    }

    #[getter]
    pub fn program_id(&self) -> Option<Pubkey> {
        self.0
            .clone()
            .program_id
            .map(|x| Pubkey::from_str(&x).unwrap())
    }
}

#[pyclass(module = "solders.transaction_status")]
#[enum_original_mapping(RewardTypeOriginal)]
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub enum RewardType {
    Fee,
    Rent,
    Staking,
    Voting,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct TransactionReturnData(TransactionReturnDataOriginal);
transaction_status_boilerplate!(TransactionReturnData);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl TransactionReturnData {
    #[new]
    pub fn new(program_id: Pubkey, data: Vec<u8>) -> Self {
        TransactionReturnDataOriginal {
            program_id: program_id.into(),
            data,
        }
        .into()
    }

    #[getter]
    pub fn program_id(&self) -> Pubkey {
        self.0.program_id.into()
    }

    #[getter]
    pub fn data<'a>(&self, py: Python<'a>) -> &'a PyBytes {
        PyBytes::new(py, &self.0.data)
    }
}

/// A duplicate representation of TransactionStatusMeta with `err` field
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiTransactionStatusMeta(UiTransactionStatusMetaOriginal);
transaction_status_boilerplate!(UiTransactionStatusMeta);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiTransactionStatusMeta {
    #[new]
    pub fn new(
        err: Option<TransactionErrorType>,
        fee: u64,
        pre_balances: Vec<u64>,
        post_balances: Vec<u64>,
        inner_instructions: Option<Vec<UiInnerInstructions>>,
        log_messages: Option<Vec<String>>,
        pre_token_balances: Option<Vec<UiTransactionTokenBalance>>,
        post_token_balances: Option<Vec<UiTransactionTokenBalance>>,
        rewards: Option<Rewards>,
        loaded_addresses: Option<UiLoadedAddresses>,
        return_data: Option<TransactionReturnData>,
    ) -> Self {
        UiTransactionStatusMetaOriginal {
            err: err.map(|e| e.into()),
            status: Ok(()),
            fee,
            pre_balances,
            post_balances,
            inner_instructions: inner_instructions
                .map(|v| v.into_iter().map(|ix| ix.into()).collect()),
            log_messages,
            pre_token_balances: pre_token_balances
                .map(|v| v.into_iter().map(|bal| bal.into()).collect()),
            post_token_balances: post_token_balances
                .map(|v| v.into_iter().map(|bal| bal.into()).collect()),
            rewards: rewards.map(|v| v.into_iter().map(|r| r.into()).collect()),
            loaded_addresses: loaded_addresses.map(|a| a.into()),
            return_data: return_data.map(|r| r.into()),
        }
        .into()
    }

    #[getter]
    pub fn err(&self) -> Option<TransactionErrorType> {
        self.0.err.clone().map(|e| e.into())
    }
    #[getter]
    pub fn fee(&self) -> u64 {
        self.0.fee
    }
    #[getter]
    pub fn pre_balances(&self) -> Vec<u64> {
        self.0.pre_balances.clone()
    }
    #[getter]
    pub fn post_balances(&self) -> Vec<u64> {
        self.0.post_balances.clone()
    }
    #[getter]
    pub fn inner_instructions(&self) -> Option<Vec<UiInnerInstructions>> {
        self.0
            .inner_instructions
            .clone()
            .map(|v| v.into_iter().map(|ix| ix.into()).collect())
    }
    #[getter]
    pub fn log_messages(&self) -> Option<Vec<String>> {
        self.0.log_messages.clone()
    }
    #[getter]
    pub fn pre_token_balances(&self) -> Option<Vec<UiTransactionTokenBalance>> {
        self.0
            .pre_token_balances
            .clone()
            .map(|v| v.into_iter().map(|bal| bal.into()).collect())
    }
    #[getter]
    pub fn post_token_balances(&self) -> Option<Vec<UiTransactionTokenBalance>> {
        self.0
            .post_token_balances
            .clone()
            .map(|v| v.into_iter().map(|bal| bal.into()).collect())
    }
    #[getter]
    pub fn rewards(&self) -> Option<Rewards> {
        self.0
            .rewards
            .clone()
            .map(|v| v.into_iter().map(|r| r.into()).collect())
    }
    #[getter]
    pub fn loaded_addresses(&self) -> Option<UiLoadedAddresses> {
        self.0.loaded_addresses.clone().map(|a| a.into())
    }
    #[getter]
    pub fn return_data(&self) -> Option<TransactionReturnData> {
        self.0.return_data.clone().map(|r| r.into())
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct EncodedTransactionWithStatusMeta(EncodedTransactionWithStatusMetaOriginal);

transaction_status_boilerplate!(EncodedTransactionWithStatusMeta);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl EncodedTransactionWithStatusMeta {
    #[new]
    pub fn new(
        transaction: EncodedVersionedTransaction,
        meta: Option<UiTransactionStatusMeta>,
        version: Option<TransactionVersion>,
    ) -> Self {
        EncodedTransactionWithStatusMetaOriginal {
            transaction: transaction.into(),
            meta: meta.map(|m| m.into()),
            version: version.map(|v| v.into()),
        }
        .into()
    }

    #[getter]
    pub fn transaction(&self) -> EncodedVersionedTransaction {
        self.0.transaction.clone().into()
    }

    #[getter]
    pub fn meta(&self) -> Option<UiTransactionStatusMeta> {
        self.0.meta.clone().map(|t| t.into())
    }

    #[getter]
    pub fn version(&self) -> Option<TransactionVersion> {
        self.0.version.clone().map(|v| v.into())
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct InstructionErrorCustom(pub u32);

transaction_status_boilerplate!(InstructionErrorCustom);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl InstructionErrorCustom {
    #[new]
    pub fn new(code: u32) -> Self {
        Self(code)
    }

    #[getter]
    pub fn code(&self) -> u32 {
        self.0
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct InstructionErrorBorshIO(pub String);
transaction_status_boilerplate!(InstructionErrorBorshIO);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl InstructionErrorBorshIO {
    #[new]
    pub fn new(value: String) -> Self {
        Self(value)
    }

    #[getter]
    pub fn value(&self) -> String {
        self.0.clone()
    }
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
#[pyclass(module = "solders.transaction_status")]
pub enum InstructionErrorFieldless {
    GenericError,
    InvalidArgument,
    InvalidInstructionData,
    InvalidAccountData,
    AccountDataTooSmall,
    InsufficientFunds,
    IncorrectProgramId,
    MissingRequiredSignature,
    AccountAlreadyInitialized,
    UninitializedAccount,
    UnbalancedInstruction,
    ModifiedProgramId,
    ExternalAccountLamportSpend,
    ExternalAccountDataModified,
    ReadonlyLamportChange,
    ReadonlyDataModified,
    DuplicateAccountIndex,
    ExecutableModified,
    RentEpochModified,
    NotEnoughAccountKeys,
    AccountDataSizeChanged,
    AccountNotExecutable,
    AccountBorrowFailed,
    AccountBorrowOutstanding,
    DuplicateAccountOutOfSync,
    InvalidError,
    ExecutableDataModified,
    ExecutableLamportChange,
    ExecutableAccountNotRentExempt,
    UnsupportedProgramId,
    CallDepth,
    MissingAccount,
    ReentrancyNotAllowed,
    MaxSeedLengthExceeded,
    InvalidSeeds,
    InvalidRealloc,
    ComputationalBudgetExceeded,
    PrivilegeEscalation,
    ProgramEnvironmentSetupFailure,
    ProgramFailedToComplete,
    ProgramFailedToCompile,
    Immutable,
    IncorrectAuthority,
    AccountNotRentExempt,
    InvalidAccountOwner,
    ArithmeticOverflow,
    UnsupportedSysvar,
    IllegalOwner,
    MaxAccountsDataSizeExceeded,
    MaxAccountsExceeded,
}

#[derive(FromPyObject, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum InstructionErrorTagged {
    Custom(InstructionErrorCustom),
    BorshIoError(InstructionErrorBorshIO),
}

impl IntoPy<PyObject> for InstructionErrorTagged {
    fn into_py(self, py: Python<'_>) -> PyObject {
        match self {
            Self::Custom(x) => x.into_py(py),
            Self::BorshIoError(x) => x.into_py(py),
        }
    }
}

#[derive(FromPyObject, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum InstructionErrorType {
    Fieldless(InstructionErrorFieldless),
    Tagged(InstructionErrorTagged),
}

impl Default for InstructionErrorType {
    fn default() -> Self {
        Self::Fieldless(InstructionErrorFieldless::GenericError)
    }
}

impl From<InstructionErrorType> for InstructionErrorOriginal {
    fn from(w: InstructionErrorType) -> Self {
        match w {
            InstructionErrorType::Tagged(t) => match t {
                InstructionErrorTagged::Custom(custom) => Self::Custom(custom.0),
                InstructionErrorTagged::BorshIoError(borsh_io) => Self::BorshIoError(borsh_io.0),
            },
            InstructionErrorType::Fieldless(f) => match f {
                InstructionErrorFieldless::GenericError => Self::GenericError,
                InstructionErrorFieldless::InvalidArgument => Self::InvalidArgument,
                InstructionErrorFieldless::InvalidInstructionData => Self::InvalidInstructionData,
                InstructionErrorFieldless::InvalidAccountData => Self::InvalidAccountData,
                InstructionErrorFieldless::AccountDataTooSmall => Self::AccountDataTooSmall,
                InstructionErrorFieldless::InsufficientFunds => Self::InsufficientFunds,
                InstructionErrorFieldless::IncorrectProgramId => Self::IncorrectProgramId,
                InstructionErrorFieldless::MissingRequiredSignature => {
                    Self::MissingRequiredSignature
                }
                InstructionErrorFieldless::AccountAlreadyInitialized => {
                    Self::AccountAlreadyInitialized
                }
                InstructionErrorFieldless::UninitializedAccount => Self::UninitializedAccount,
                InstructionErrorFieldless::UnbalancedInstruction => Self::UnbalancedInstruction,
                InstructionErrorFieldless::ModifiedProgramId => Self::ModifiedProgramId,
                InstructionErrorFieldless::ExternalAccountLamportSpend => {
                    Self::ExternalAccountLamportSpend
                }
                InstructionErrorFieldless::ExternalAccountDataModified => {
                    Self::ExternalAccountDataModified
                }
                InstructionErrorFieldless::ReadonlyLamportChange => Self::ReadonlyLamportChange,
                InstructionErrorFieldless::ReadonlyDataModified => Self::ReadonlyDataModified,
                InstructionErrorFieldless::DuplicateAccountIndex => Self::DuplicateAccountIndex,
                InstructionErrorFieldless::ExecutableModified => Self::ExecutableModified,
                InstructionErrorFieldless::RentEpochModified => Self::RentEpochModified,
                InstructionErrorFieldless::NotEnoughAccountKeys => Self::NotEnoughAccountKeys,
                InstructionErrorFieldless::AccountDataSizeChanged => Self::AccountDataSizeChanged,
                InstructionErrorFieldless::AccountNotExecutable => Self::AccountNotExecutable,
                InstructionErrorFieldless::AccountBorrowFailed => Self::AccountBorrowFailed,
                InstructionErrorFieldless::AccountBorrowOutstanding => {
                    Self::AccountBorrowOutstanding
                }
                InstructionErrorFieldless::DuplicateAccountOutOfSync => {
                    Self::DuplicateAccountOutOfSync
                }
                InstructionErrorFieldless::InvalidError => Self::InvalidError,
                InstructionErrorFieldless::ExecutableDataModified => Self::ExecutableDataModified,
                InstructionErrorFieldless::ExecutableLamportChange => Self::ExecutableLamportChange,
                InstructionErrorFieldless::ExecutableAccountNotRentExempt => {
                    Self::ExecutableAccountNotRentExempt
                }
                InstructionErrorFieldless::UnsupportedProgramId => Self::UnsupportedProgramId,
                InstructionErrorFieldless::CallDepth => Self::CallDepth,
                InstructionErrorFieldless::MissingAccount => Self::MissingAccount,
                InstructionErrorFieldless::ReentrancyNotAllowed => Self::ReentrancyNotAllowed,
                InstructionErrorFieldless::MaxSeedLengthExceeded => Self::MaxSeedLengthExceeded,
                InstructionErrorFieldless::InvalidSeeds => Self::InvalidSeeds,
                InstructionErrorFieldless::InvalidRealloc => Self::InvalidRealloc,
                InstructionErrorFieldless::ComputationalBudgetExceeded => {
                    Self::ComputationalBudgetExceeded
                }
                InstructionErrorFieldless::PrivilegeEscalation => Self::PrivilegeEscalation,
                InstructionErrorFieldless::ProgramEnvironmentSetupFailure => {
                    Self::ProgramEnvironmentSetupFailure
                }
                InstructionErrorFieldless::ProgramFailedToComplete => Self::ProgramFailedToComplete,
                InstructionErrorFieldless::ProgramFailedToCompile => Self::ProgramFailedToCompile,
                InstructionErrorFieldless::Immutable => Self::Immutable,
                InstructionErrorFieldless::IncorrectAuthority => Self::IncorrectAuthority,
                InstructionErrorFieldless::AccountNotRentExempt => Self::AccountNotRentExempt,
                InstructionErrorFieldless::InvalidAccountOwner => Self::InvalidAccountOwner,
                InstructionErrorFieldless::ArithmeticOverflow => Self::ArithmeticOverflow,
                InstructionErrorFieldless::UnsupportedSysvar => Self::UnsupportedSysvar,
                InstructionErrorFieldless::IllegalOwner => Self::IllegalOwner,
                InstructionErrorFieldless::MaxAccountsDataSizeExceeded => {
                    Self::MaxAccountsDataSizeExceeded
                }
                InstructionErrorFieldless::MaxAccountsExceeded => Self::MaxAccountsExceeded,
            },
        }
    }
}

impl From<InstructionErrorOriginal> for InstructionErrorType {
    fn from(e: InstructionErrorOriginal) -> Self {
        match e {
            InstructionErrorOriginal::Custom(code) => {
                Self::Tagged(InstructionErrorTagged::Custom(InstructionErrorCustom(code)))
            }
            InstructionErrorOriginal::BorshIoError(val) => Self::Tagged(
                InstructionErrorTagged::BorshIoError(InstructionErrorBorshIO(val)),
            ),
            InstructionErrorOriginal::GenericError => {
                Self::Fieldless(InstructionErrorFieldless::GenericError)
            }
            InstructionErrorOriginal::InvalidArgument => {
                Self::Fieldless(InstructionErrorFieldless::InvalidArgument)
            }
            InstructionErrorOriginal::InvalidInstructionData => {
                Self::Fieldless(InstructionErrorFieldless::InvalidInstructionData)
            }
            InstructionErrorOriginal::InvalidAccountData => {
                Self::Fieldless(InstructionErrorFieldless::InvalidAccountData)
            }
            InstructionErrorOriginal::AccountDataTooSmall => {
                Self::Fieldless(InstructionErrorFieldless::AccountDataTooSmall)
            }
            InstructionErrorOriginal::InsufficientFunds => {
                Self::Fieldless(InstructionErrorFieldless::InsufficientFunds)
            }
            InstructionErrorOriginal::IncorrectProgramId => {
                Self::Fieldless(InstructionErrorFieldless::IncorrectProgramId)
            }
            InstructionErrorOriginal::MissingRequiredSignature => {
                Self::Fieldless(InstructionErrorFieldless::MissingRequiredSignature)
            }
            InstructionErrorOriginal::AccountAlreadyInitialized => {
                Self::Fieldless(InstructionErrorFieldless::AccountAlreadyInitialized)
            }
            InstructionErrorOriginal::UninitializedAccount => {
                Self::Fieldless(InstructionErrorFieldless::UninitializedAccount)
            }
            InstructionErrorOriginal::UnbalancedInstruction => {
                Self::Fieldless(InstructionErrorFieldless::UnbalancedInstruction)
            }
            InstructionErrorOriginal::ModifiedProgramId => {
                Self::Fieldless(InstructionErrorFieldless::ModifiedProgramId)
            }
            InstructionErrorOriginal::ExternalAccountLamportSpend => {
                Self::Fieldless(InstructionErrorFieldless::ExternalAccountLamportSpend)
            }
            InstructionErrorOriginal::ExternalAccountDataModified => {
                Self::Fieldless(InstructionErrorFieldless::ExternalAccountDataModified)
            }
            InstructionErrorOriginal::ReadonlyLamportChange => {
                Self::Fieldless(InstructionErrorFieldless::ReadonlyLamportChange)
            }
            InstructionErrorOriginal::ReadonlyDataModified => {
                Self::Fieldless(InstructionErrorFieldless::ReadonlyDataModified)
            }
            InstructionErrorOriginal::DuplicateAccountIndex => {
                Self::Fieldless(InstructionErrorFieldless::DuplicateAccountIndex)
            }
            InstructionErrorOriginal::ExecutableModified => {
                Self::Fieldless(InstructionErrorFieldless::ExecutableModified)
            }
            InstructionErrorOriginal::RentEpochModified => {
                Self::Fieldless(InstructionErrorFieldless::RentEpochModified)
            }
            InstructionErrorOriginal::NotEnoughAccountKeys => {
                Self::Fieldless(InstructionErrorFieldless::NotEnoughAccountKeys)
            }
            InstructionErrorOriginal::AccountDataSizeChanged => {
                Self::Fieldless(InstructionErrorFieldless::AccountDataSizeChanged)
            }
            InstructionErrorOriginal::AccountNotExecutable => {
                Self::Fieldless(InstructionErrorFieldless::AccountNotExecutable)
            }
            InstructionErrorOriginal::AccountBorrowFailed => {
                Self::Fieldless(InstructionErrorFieldless::AccountBorrowFailed)
            }
            InstructionErrorOriginal::AccountBorrowOutstanding => {
                Self::Fieldless(InstructionErrorFieldless::AccountBorrowOutstanding)
            }
            InstructionErrorOriginal::DuplicateAccountOutOfSync => {
                Self::Fieldless(InstructionErrorFieldless::DuplicateAccountOutOfSync)
            }
            InstructionErrorOriginal::InvalidError => {
                Self::Fieldless(InstructionErrorFieldless::InvalidError)
            }
            InstructionErrorOriginal::ExecutableDataModified => {
                Self::Fieldless(InstructionErrorFieldless::ExecutableDataModified)
            }
            InstructionErrorOriginal::ExecutableLamportChange => {
                Self::Fieldless(InstructionErrorFieldless::ExecutableLamportChange)
            }
            InstructionErrorOriginal::ExecutableAccountNotRentExempt => {
                Self::Fieldless(InstructionErrorFieldless::ExecutableAccountNotRentExempt)
            }
            InstructionErrorOriginal::UnsupportedProgramId => {
                Self::Fieldless(InstructionErrorFieldless::UnsupportedProgramId)
            }
            InstructionErrorOriginal::CallDepth => {
                Self::Fieldless(InstructionErrorFieldless::CallDepth)
            }
            InstructionErrorOriginal::MissingAccount => {
                Self::Fieldless(InstructionErrorFieldless::MissingAccount)
            }
            InstructionErrorOriginal::ReentrancyNotAllowed => {
                Self::Fieldless(InstructionErrorFieldless::ReentrancyNotAllowed)
            }
            InstructionErrorOriginal::MaxSeedLengthExceeded => {
                Self::Fieldless(InstructionErrorFieldless::MaxSeedLengthExceeded)
            }
            InstructionErrorOriginal::InvalidSeeds => {
                Self::Fieldless(InstructionErrorFieldless::InvalidSeeds)
            }
            InstructionErrorOriginal::InvalidRealloc => {
                Self::Fieldless(InstructionErrorFieldless::InvalidRealloc)
            }
            InstructionErrorOriginal::ComputationalBudgetExceeded => {
                Self::Fieldless(InstructionErrorFieldless::ComputationalBudgetExceeded)
            }
            InstructionErrorOriginal::PrivilegeEscalation => {
                Self::Fieldless(InstructionErrorFieldless::PrivilegeEscalation)
            }
            InstructionErrorOriginal::ProgramEnvironmentSetupFailure => {
                Self::Fieldless(InstructionErrorFieldless::ProgramEnvironmentSetupFailure)
            }
            InstructionErrorOriginal::ProgramFailedToComplete => {
                Self::Fieldless(InstructionErrorFieldless::ProgramFailedToComplete)
            }
            InstructionErrorOriginal::ProgramFailedToCompile => {
                Self::Fieldless(InstructionErrorFieldless::ProgramFailedToCompile)
            }
            InstructionErrorOriginal::Immutable => {
                Self::Fieldless(InstructionErrorFieldless::Immutable)
            }
            InstructionErrorOriginal::IncorrectAuthority => {
                Self::Fieldless(InstructionErrorFieldless::IncorrectAuthority)
            }
            InstructionErrorOriginal::AccountNotRentExempt => {
                Self::Fieldless(InstructionErrorFieldless::AccountNotRentExempt)
            }
            InstructionErrorOriginal::InvalidAccountOwner => {
                Self::Fieldless(InstructionErrorFieldless::InvalidAccountOwner)
            }
            InstructionErrorOriginal::ArithmeticOverflow => {
                Self::Fieldless(InstructionErrorFieldless::ArithmeticOverflow)
            }
            InstructionErrorOriginal::UnsupportedSysvar => {
                Self::Fieldless(InstructionErrorFieldless::UnsupportedSysvar)
            }
            InstructionErrorOriginal::IllegalOwner => {
                Self::Fieldless(InstructionErrorFieldless::IllegalOwner)
            }
            InstructionErrorOriginal::MaxAccountsDataSizeExceeded => {
                Self::Fieldless(InstructionErrorFieldless::MaxAccountsDataSizeExceeded)
            }
            InstructionErrorOriginal::MaxAccountsExceeded => {
                Self::Fieldless(InstructionErrorFieldless::MaxAccountsExceeded)
            }
        }
    }
}

impl IntoPy<PyObject> for InstructionErrorType {
    fn into_py(self, py: Python<'_>) -> PyObject {
        match self {
            Self::Fieldless(f) => f.into_py(py),
            Self::Tagged(t) => t.into_py(py),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct TransactionErrorInstructionError(pub (u8, InstructionErrorType));
transaction_status_boilerplate!(TransactionErrorInstructionError);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl TransactionErrorInstructionError {
    #[new]
    pub fn new(index: u8, err: InstructionErrorType) -> Self {
        Self((index, err))
    }

    #[getter]
    pub fn index(&self) -> u8 {
        self.0 .0
    }

    #[getter]
    pub fn err(&self) -> InstructionErrorType {
        self.0 .1.clone()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct TransactionErrorDuplicateInstruction(pub u8);
transaction_status_boilerplate!(TransactionErrorDuplicateInstruction);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl TransactionErrorDuplicateInstruction {
    #[new]
    pub fn new(index: u8) -> Self {
        Self(index)
    }

    #[getter]
    pub fn index(&self) -> u8 {
        self.0
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct TransactionErrorInsufficientFundsForRent {
    #[pyo3(get)]
    account_index: u8,
}
transaction_status_boilerplate!(TransactionErrorInsufficientFundsForRent);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl TransactionErrorInsufficientFundsForRent {
    #[new]
    pub fn new(account_index: u8) -> Self {
        Self { account_index }
    }
}

#[pyclass(module = "solders.transaction_status")]
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TransactionErrorFieldless {
    AccountInUse,
    AccountLoadedTwice,
    AccountNotFound,
    ProgramAccountNotFound,
    InsufficientFundsForFee,
    InvalidAccountForFee,
    AlreadyProcessed,
    BlockhashNotFound,
    CallChainTooDeep,
    MissingSignatureForFee,
    InvalidAccountIndex,
    SignatureFailure,
    InvalidProgramForExecution,
    SanitizeFailure,
    ClusterMaintenance,
    AccountBorrowOutstanding,
    WouldExceedMaxBlockCostLimit,
    UnsupportedVersion,
    InvalidWritableAccount,
    WouldExceedMaxAccountCostLimit,
    WouldExceedAccountDataBlockLimit,
    TooManyAccountLocks,
    AddressLookupTableNotFound,
    InvalidAddressLookupTableOwner,
    InvalidAddressLookupTableData,
    InvalidAddressLookupTableIndex,
    InvalidRentPayingAccount,
    WouldExceedMaxVoteCostLimit,
    WouldExceedAccountDataTotalLimit,
}

#[derive(FromPyObject, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum TransactionErrorTypeTagged {
    InstructionError(TransactionErrorInstructionError),
    DuplicateInstruction(TransactionErrorDuplicateInstruction),
    InsufficientFundsForRent(TransactionErrorInsufficientFundsForRent),
}

impl IntoPy<PyObject> for TransactionErrorTypeTagged {
    fn into_py(self, py: Python<'_>) -> PyObject {
        match self {
            Self::InstructionError(e) => e.into_py(py),
            Self::DuplicateInstruction(d) => d.into_py(py),
            Self::InsufficientFundsForRent(i) => i.into_py(py),
        }
    }
}

#[derive(FromPyObject, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum TransactionErrorType {
    Fieldless(TransactionErrorFieldless),
    Tagged(TransactionErrorTypeTagged),
}

impl Default for TransactionErrorType {
    fn default() -> Self {
        Self::Fieldless(TransactionErrorFieldless::AccountInUse)
    }
}

impl From<TransactionErrorType> for TransactionErrorOriginal {
    fn from(w: TransactionErrorType) -> Self {
        match w {
            TransactionErrorType::Tagged(t) => match t {
                TransactionErrorTypeTagged::InstructionError(e) => {
                    Self::InstructionError(e.0 .0, e.0 .1.into())
                }
                TransactionErrorTypeTagged::DuplicateInstruction(e) => {
                    Self::DuplicateInstruction(e.0)
                }
                TransactionErrorTypeTagged::InsufficientFundsForRent(e) => {
                    Self::InsufficientFundsForRent {
                        account_index: e.account_index,
                    }
                }
            },
            TransactionErrorType::Fieldless(f) => match f {
                TransactionErrorFieldless::AccountInUse => Self::AccountInUse,
                TransactionErrorFieldless::AccountLoadedTwice => Self::AccountLoadedTwice,
                TransactionErrorFieldless::AccountNotFound => Self::AccountNotFound,
                TransactionErrorFieldless::ProgramAccountNotFound => Self::ProgramAccountNotFound,
                TransactionErrorFieldless::InsufficientFundsForFee => Self::InsufficientFundsForFee,
                TransactionErrorFieldless::InvalidAccountForFee => Self::InvalidAccountForFee,
                TransactionErrorFieldless::AlreadyProcessed => Self::AlreadyProcessed,
                TransactionErrorFieldless::BlockhashNotFound => Self::BlockhashNotFound,
                TransactionErrorFieldless::CallChainTooDeep => Self::CallChainTooDeep,
                TransactionErrorFieldless::MissingSignatureForFee => Self::MissingSignatureForFee,
                TransactionErrorFieldless::InvalidAccountIndex => Self::InvalidAccountIndex,
                TransactionErrorFieldless::SignatureFailure => Self::SignatureFailure,
                TransactionErrorFieldless::InvalidProgramForExecution => {
                    Self::InvalidProgramForExecution
                }
                TransactionErrorFieldless::SanitizeFailure => Self::SanitizeFailure,
                TransactionErrorFieldless::ClusterMaintenance => Self::ClusterMaintenance,
                TransactionErrorFieldless::AccountBorrowOutstanding => {
                    Self::AccountBorrowOutstanding
                }
                TransactionErrorFieldless::WouldExceedMaxBlockCostLimit => {
                    Self::WouldExceedMaxBlockCostLimit
                }
                TransactionErrorFieldless::UnsupportedVersion => Self::UnsupportedVersion,
                TransactionErrorFieldless::InvalidWritableAccount => Self::InvalidWritableAccount,
                TransactionErrorFieldless::WouldExceedMaxAccountCostLimit => {
                    Self::WouldExceedMaxAccountCostLimit
                }
                TransactionErrorFieldless::WouldExceedAccountDataBlockLimit => {
                    Self::WouldExceedAccountDataBlockLimit
                }
                TransactionErrorFieldless::TooManyAccountLocks => Self::TooManyAccountLocks,
                TransactionErrorFieldless::AddressLookupTableNotFound => {
                    Self::AddressLookupTableNotFound
                }
                TransactionErrorFieldless::InvalidAddressLookupTableOwner => {
                    Self::InvalidAddressLookupTableOwner
                }
                TransactionErrorFieldless::InvalidAddressLookupTableData => {
                    Self::InvalidAddressLookupTableData
                }
                TransactionErrorFieldless::InvalidAddressLookupTableIndex => {
                    Self::InvalidAddressLookupTableIndex
                }
                TransactionErrorFieldless::InvalidRentPayingAccount => {
                    Self::InvalidRentPayingAccount
                }
                TransactionErrorFieldless::WouldExceedMaxVoteCostLimit => {
                    Self::WouldExceedMaxVoteCostLimit
                }
                TransactionErrorFieldless::WouldExceedAccountDataTotalLimit => {
                    Self::WouldExceedAccountDataTotalLimit
                }
            },
        }
    }
}

impl From<TransactionErrorOriginal> for TransactionErrorType {
    fn from(w: TransactionErrorOriginal) -> Self {
        match w {
            TransactionErrorOriginal::InstructionError(index, err) => {
                Self::Tagged(TransactionErrorTypeTagged::InstructionError(
                    TransactionErrorInstructionError((index, err.into())),
                ))
            }
            TransactionErrorOriginal::DuplicateInstruction(index) => {
                Self::Tagged(TransactionErrorTypeTagged::DuplicateInstruction(
                    TransactionErrorDuplicateInstruction(index),
                ))
            }
            TransactionErrorOriginal::InsufficientFundsForRent { account_index } => {
                Self::Tagged(TransactionErrorTypeTagged::InsufficientFundsForRent(
                    TransactionErrorInsufficientFundsForRent { account_index },
                ))
            }
            TransactionErrorOriginal::AccountInUse => {
                Self::Fieldless(TransactionErrorFieldless::AccountInUse)
            }
            TransactionErrorOriginal::AccountLoadedTwice => {
                Self::Fieldless(TransactionErrorFieldless::AccountLoadedTwice)
            }
            TransactionErrorOriginal::AccountNotFound => {
                Self::Fieldless(TransactionErrorFieldless::AccountNotFound)
            }
            TransactionErrorOriginal::ProgramAccountNotFound => {
                Self::Fieldless(TransactionErrorFieldless::ProgramAccountNotFound)
            }
            TransactionErrorOriginal::InsufficientFundsForFee => {
                Self::Fieldless(TransactionErrorFieldless::InsufficientFundsForFee)
            }
            TransactionErrorOriginal::InvalidAccountForFee => {
                Self::Fieldless(TransactionErrorFieldless::InvalidAccountForFee)
            }
            TransactionErrorOriginal::AlreadyProcessed => {
                Self::Fieldless(TransactionErrorFieldless::AlreadyProcessed)
            }
            TransactionErrorOriginal::BlockhashNotFound => {
                Self::Fieldless(TransactionErrorFieldless::BlockhashNotFound)
            }
            TransactionErrorOriginal::CallChainTooDeep => {
                Self::Fieldless(TransactionErrorFieldless::CallChainTooDeep)
            }
            TransactionErrorOriginal::MissingSignatureForFee => {
                Self::Fieldless(TransactionErrorFieldless::MissingSignatureForFee)
            }
            TransactionErrorOriginal::InvalidAccountIndex => {
                Self::Fieldless(TransactionErrorFieldless::InvalidAccountIndex)
            }
            TransactionErrorOriginal::SignatureFailure => {
                Self::Fieldless(TransactionErrorFieldless::SignatureFailure)
            }
            TransactionErrorOriginal::InvalidProgramForExecution => {
                Self::Fieldless(TransactionErrorFieldless::InvalidProgramForExecution)
            }
            TransactionErrorOriginal::SanitizeFailure => {
                Self::Fieldless(TransactionErrorFieldless::SanitizeFailure)
            }
            TransactionErrorOriginal::ClusterMaintenance => {
                Self::Fieldless(TransactionErrorFieldless::ClusterMaintenance)
            }
            TransactionErrorOriginal::AccountBorrowOutstanding => {
                Self::Fieldless(TransactionErrorFieldless::AccountBorrowOutstanding)
            }
            TransactionErrorOriginal::WouldExceedMaxBlockCostLimit => {
                Self::Fieldless(TransactionErrorFieldless::WouldExceedMaxBlockCostLimit)
            }
            TransactionErrorOriginal::UnsupportedVersion => {
                Self::Fieldless(TransactionErrorFieldless::UnsupportedVersion)
            }
            TransactionErrorOriginal::InvalidWritableAccount => {
                Self::Fieldless(TransactionErrorFieldless::InvalidWritableAccount)
            }
            TransactionErrorOriginal::WouldExceedMaxAccountCostLimit => {
                Self::Fieldless(TransactionErrorFieldless::WouldExceedMaxAccountCostLimit)
            }
            TransactionErrorOriginal::WouldExceedAccountDataBlockLimit => {
                Self::Fieldless(TransactionErrorFieldless::WouldExceedAccountDataBlockLimit)
            }
            TransactionErrorOriginal::TooManyAccountLocks => {
                Self::Fieldless(TransactionErrorFieldless::TooManyAccountLocks)
            }
            TransactionErrorOriginal::AddressLookupTableNotFound => {
                Self::Fieldless(TransactionErrorFieldless::AddressLookupTableNotFound)
            }
            TransactionErrorOriginal::InvalidAddressLookupTableOwner => {
                Self::Fieldless(TransactionErrorFieldless::InvalidAddressLookupTableOwner)
            }
            TransactionErrorOriginal::InvalidAddressLookupTableData => {
                Self::Fieldless(TransactionErrorFieldless::InvalidAddressLookupTableData)
            }
            TransactionErrorOriginal::InvalidAddressLookupTableIndex => {
                Self::Fieldless(TransactionErrorFieldless::InvalidAddressLookupTableIndex)
            }
            TransactionErrorOriginal::InvalidRentPayingAccount => {
                Self::Fieldless(TransactionErrorFieldless::InvalidRentPayingAccount)
            }
            TransactionErrorOriginal::WouldExceedMaxVoteCostLimit => {
                Self::Fieldless(TransactionErrorFieldless::WouldExceedMaxVoteCostLimit)
            }
            TransactionErrorOriginal::WouldExceedAccountDataTotalLimit => {
                Self::Fieldless(TransactionErrorFieldless::WouldExceedAccountDataTotalLimit)
            }
        }
    }
}

impl IntoPy<PyObject> for TransactionErrorType {
    fn into_py(self, py: Python<'_>) -> PyObject {
        match self {
            Self::Fieldless(f) => f.into_py(py),
            Self::Tagged(t) => t.into_py(py),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct Reward(RewardOriginal);

transaction_status_boilerplate!(Reward);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl Reward {
    #[new]
    pub fn new(
        pubkey: Pubkey,
        lamports: i64,
        post_balance: u64, // Account balance in lamports after `lamports` was applied
        reward_type: Option<RewardType>,
        commission: Option<u8>,
    ) -> Self {
        RewardOriginal {
            pubkey: pubkey.to_string(),
            lamports,
            post_balance,
            reward_type: reward_type.map(|r| r.into()),
            commission,
        }
        .into()
    }

    #[getter]
    pub fn pubkey(&self) -> Pubkey {
        Pubkey::from_str(&self.0.pubkey).unwrap()
    }

    #[getter]
    pub fn lamports(&self) -> i64 {
        self.0.lamports
    }

    #[getter]
    pub fn post_balance(&self) -> u64 {
        self.0.post_balance
    }

    #[getter]
    pub fn reward_type(&self) -> Option<RewardType> {
        self.0.reward_type.map(|r| r.into())
    }

    #[getter]
    pub fn commission(&self) -> Option<u8> {
        self.0.commission
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[enum_original_mapping(TransactionConfirmationStatusOriginal)]
#[pyclass(module = "solders.transaction_status")]
pub enum TransactionConfirmationStatus {
    Processed,
    Confirmed,
    Finalized,
}

pub type Rewards = Vec<Reward>;

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct TransactionStatus(TransactionStatusOriginal);

transaction_status_boilerplate!(TransactionStatus);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl TransactionStatus {
    #[new]
    pub fn new(
        slot: Slot,
        confirmations: Option<usize>,
        status: Option<TransactionErrorType>,
        err: Option<TransactionErrorType>,
        confirmation_status: Option<TransactionConfirmationStatus>,
    ) -> Self {
        TransactionStatusOriginal {
            slot,
            confirmations,
            status: status.map_or(Ok(()), |e| Err(e.into())),
            err: err.map(Into::into),
            confirmation_status: confirmation_status.map(Into::into),
        }
        .into()
    }

    #[getter]
    pub fn slot(&self) -> Slot {
        self.0.slot
    }
    #[getter]
    pub fn confirmations(&self) -> Option<usize> {
        self.0.confirmations
    }
    #[getter]
    pub fn status(&self) -> Option<TransactionErrorType> {
        self.0
            .status
            .clone()
            .map_or_else(|e| Some(e.into()), |_s| None)
    }
    #[getter]
    pub fn err(&self) -> Option<TransactionErrorType> {
        self.0.err.clone().map(Into::into)
    }
    #[getter]
    pub fn confirmation_status(&self) -> Option<TransactionConfirmationStatus> {
        self.0.confirmation_status.clone().map(Into::into)
    }

    pub fn satisfies_commitment(&self, commitment_config: CommitmentConfig) -> bool {
        self.0.satisfies_commitment(commitment_config.into())
    }

    pub fn find_confirmation_status(&self) -> TransactionConfirmationStatus {
        self.0.confirmation_status().into()
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct EncodedConfirmedTransactionWithStatusMeta(
    EncodedConfirmedTransactionWithStatusMetaOriginal,
);

transaction_status_boilerplate!(EncodedConfirmedTransactionWithStatusMeta);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl EncodedConfirmedTransactionWithStatusMeta {
    #[new]
    pub fn new(
        slot: Slot,
        transaction: EncodedTransactionWithStatusMeta,
        block_time: Option<UnixTimestamp>,
    ) -> Self {
        EncodedConfirmedTransactionWithStatusMetaOriginal {
            slot,
            transaction: transaction.into(),
            block_time,
        }
        .into()
    }

    #[getter]
    pub fn slot(&self) -> Slot {
        self.0.slot
    }

    #[getter]
    pub fn transaction(&self) -> EncodedTransactionWithStatusMeta {
        self.0.transaction.clone().into()
    }

    #[getter]
    pub fn block_time(&self) -> Option<UnixTimestamp> {
        self.0.block_time
    }
}

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, From, Into)]
#[pyclass(module = "solders.transaction_status", subclass)]
pub struct UiConfirmedBlock(UiConfirmedBlockOriginal);

transaction_status_boilerplate!(UiConfirmedBlock);

#[richcmp_eq_only]
#[common_methods]
#[pymethods]
impl UiConfirmedBlock {
    #[new]
    pub fn new(
        previous_blockhash: SolderHash,
        blockhash: SolderHash,
        parent_slot: Slot,
        transactions: Option<Vec<EncodedTransactionWithStatusMeta>>,
        signatures: Option<Vec<Signature>>,
        rewards: Option<Rewards>,
        block_time: Option<UnixTimestamp>,
        block_height: Option<u64>,
    ) -> Self {
        UiConfirmedBlockOriginal {
            previous_blockhash: previous_blockhash.to_string(),
            blockhash: blockhash.to_string(),
            parent_slot,
            transactions: transactions.map(|txs| txs.into_iter().map(|tx| tx.into()).collect()),
            signatures: signatures.map(|sigs| sigs.iter().map(|sig| sig.to_string()).collect()),
            rewards: rewards.map(|v| v.into_iter().map(|r| r.into()).collect()),
            block_time,
            block_height,
        }
        .into()
    }

    #[getter]
    pub fn previous_blockhash(&self) -> SolderHash {
        self.0.previous_blockhash.parse().unwrap()
    }

    #[getter]
    pub fn blockhash(&self) -> SolderHash {
        self.0.blockhash.parse().unwrap()
    }

    #[getter]
    pub fn parent_slot(&self) -> Slot {
        self.0.parent_slot
    }

    #[getter]
    pub fn transactions(&self) -> Option<Vec<EncodedTransactionWithStatusMeta>> {
        self.0
            .transactions
            .clone()
            .map(|txs| txs.into_iter().map(|tx| tx.into()).collect())
    }
    #[getter]
    pub fn signatures(&self) -> Option<Vec<Signature>> {
        self.0
            .signatures
            .clone()
            .map(|sigs| sigs.iter().map(|sig| sig.parse().unwrap()).collect())
    }
    #[getter]
    pub fn rewards(&self) -> Option<Rewards> {
        self.0
            .rewards
            .clone()
            .map(|v| v.into_iter().map(|r| r.into()).collect())
    }
    #[getter]
    pub fn block_time(&self) -> Option<UnixTimestamp> {
        self.0.block_time
    }
    #[getter]
    pub fn block_height(&self) -> Option<u64> {
        self.0.block_height
    }
}

pub fn create_transaction_status_mod(py: Python<'_>) -> PyResult<&PyModule> {
    let m = PyModule::new(py, "transaction_status")?;
    m.add_class::<TransactionDetails>()?;
    m.add_class::<UiTransactionEncoding>()?;
    m.add_class::<TransactionBinaryEncoding>()?;
    m.add_class::<UiCompiledInstruction>()?;
    m.add_class::<UiAddressTableLookup>()?;
    m.add_class::<UiRawMessage>()?;
    m.add_class::<ParsedAccount>()?;
    m.add_class::<ParsedInstruction>()?;
    m.add_class::<UiPartiallyDecodedInstruction>()?;
    m.add_class::<UiParsedMessage>()?;
    m.add_class::<UiTransaction>()?;
    m.add_class::<UiInnerInstructions>()?;
    m.add_class::<UiLoadedAddresses>()?;
    m.add_class::<UiTransactionTokenBalance>()?;
    m.add_class::<RewardType>()?;
    m.add_class::<TransactionReturnData>()?;
    m.add_class::<UiTransactionStatusMeta>()?;
    m.add_class::<EncodedTransactionWithStatusMeta>()?;
    m.add_class::<InstructionErrorCustom>()?;
    m.add_class::<InstructionErrorBorshIO>()?;
    m.add_class::<InstructionErrorFieldless>()?;
    m.add_class::<TransactionErrorInstructionError>()?;
    m.add_class::<TransactionErrorDuplicateInstruction>()?;
    m.add_class::<TransactionErrorInsufficientFundsForRent>()?;
    m.add_class::<TransactionErrorFieldless>()?;
    m.add_class::<Reward>()?;
    m.add_class::<TransactionConfirmationStatus>()?;
    m.add_class::<TransactionStatus>()?;
    m.add_class::<EncodedConfirmedTransactionWithStatusMeta>()?;
    m.add_class::<UiConfirmedBlock>()?;
    let typing = py.import("typing")?;
    let union = typing.getattr("Union")?;
    let ui_parsed_instruction_members = vec![
        ParsedInstruction::type_object(py),
        UiPartiallyDecodedInstruction::type_object(py),
    ];
    m.add(
        "UiParsedInstruction",
        union.get_item(PyTuple::new(py, ui_parsed_instruction_members.clone()))?,
    )?;
    let mut ui_instruction_members = vec![UiCompiledInstruction::type_object(py)];
    ui_instruction_members.extend(ui_parsed_instruction_members);
    m.add(
        "UiInstruction",
        union.get_item(PyTuple::new(py, ui_instruction_members))?,
    )?;
    m.add(
        "UiMessage",
        union.get_item(PyTuple::new(
            py,
            vec![
                UiParsedMessage::type_object(py),
                UiRawMessage::type_object(py),
            ],
        ))?,
    )?;
    m.add(
        "EncodedVersionedTransaction",
        union.get_item(PyTuple::new(
            py,
            vec![
                VersionedTransaction::type_object(py),
                UiTransaction::type_object(py),
            ],
        ))?,
    )?;
    m.add(
        "InstructionErrorType",
        union.get_item(PyTuple::new(
            py,
            vec![
                InstructionErrorFieldless::type_object(py),
                InstructionErrorCustom::type_object(py),
                InstructionErrorBorshIO::type_object(py),
            ],
        ))?,
    )?;
    m.add(
        "TransactionErrorType",
        union.get_item(PyTuple::new(
            py,
            vec![
                TransactionErrorFieldless::type_object(py),
                TransactionErrorInstructionError::type_object(py),
                TransactionErrorDuplicateInstruction::type_object(py),
                TransactionErrorInsufficientFundsForRent::type_object(py),
            ],
        ))?,
    )?;
    Ok(m)
}
