#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use core::fmt;

#[cfg(feature = "frozen-abi")]
use rialo_frozen_abi_macro::{AbiEnumVisitor, AbiExample};
use rialo_s_instruction::error::InstructionError;
use rialo_sanitize::SanitizeError;
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};

pub type TransactionResult<T> = Result<T, TransactionError>;

/// Reasons a transaction might be rejected.
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum TransactionError {
    /// An account is already being processed in another transaction in a way
    /// that does not support parallelism
    AccountInUse,

    /// A `Pubkey` appears twice in the transaction's `account_keys`.  Instructions can reference
    /// `Pubkey`s more than once but the message must contain a list with no duplicate keys
    AccountLoadedTwice,

    /// Attempt to debit an account but found no record of a prior credit.
    AccountNotFound,

    /// Attempt to load a program that does not exist
    ProgramAccountNotFound,

    /// The from `Pubkey` does not have sufficient balance to pay the fee to schedule the transaction
    InsufficientFundsForFee,

    /// This account may not be used to pay transaction fees
    InvalidAccountForFee,

    /// The bank has seen this transaction before. This can occur under normal operation
    /// when a UDP packet is duplicated, as a user error from a client not updating
    /// its `recent_blockhash`, or as a double-spend attack.
    AlreadyProcessed,

    /// The transaction timestamp is too old (seconds since `UNIX_EPOCH`).
    TimestampTooStale,

    /// The transaction's timestamp is in the future (seconds since `UNIX_EPOCH`).
    TimestampInFuture,

    /// An error occurred while processing an instruction. The first element of the tuple
    /// indicates the instruction index in which the error occurred.
    InstructionError(u8, InstructionError),

    /// Transaction contains an invalid account reference
    InvalidAccountIndex,

    /// Transaction did not pass signature verification
    SignatureFailure,

    /// This program may not be used for executing instructions
    InvalidProgramForExecution,

    /// Transaction failed to sanitize accounts offsets correctly
    /// implies that account locks are not taken for this TX, and should
    /// not be unlocked.
    SanitizeFailure,

    /// Transaction processing left an account with an outstanding borrowed reference
    AccountBorrowOutstanding,

    /// Transaction would exceed max Block Cost Limit
    WouldExceedMaxBlockCostLimit,

    /// Transaction version is unsupported
    UnsupportedVersion,

    /// Transaction loads a writable account that cannot be written
    InvalidWritableAccount,

    /// Transaction would exceed max account limit within the block
    WouldExceedMaxAccountCostLimit,

    /// Transaction would exceed account data limit within the block
    WouldExceedAccountDataBlockLimit,

    /// Transaction locked too many accounts
    TooManyAccountLocks,

    /// Transaction leaves an account with a lower balance than rent-exempt minimum
    InvalidRentPayingAccount,

    /// Transaction would exceed max Vote Cost Limit
    WouldExceedMaxVoteCostLimit,

    /// Transaction contains a duplicate instruction that is not allowed
    DuplicateInstruction(u8),

    /// Transaction results in an account with insufficient funds for rent
    InsufficientFundsForRent { account_index: u8 },

    /// Transaction exceeded max loaded accounts data size cap
    MaxLoadedAccountsDataSizeExceeded,

    /// LoadedAccountsDataSizeLimit set for transaction must be greater than 0.
    InvalidLoadedAccountsDataSizeLimit,

    /// Sanitized transaction differed before/after feature activiation. Needs to be resanitized.
    ResanitizationNeeded,

    /// The total balance before the transaction does not equal the total balance after the transaction
    UnbalancedTransaction,

    /// Program cache hit max limit.
    ProgramCacheHitMaxLimit,

    /// Failed to serialize transaction instructions.
    SerializationFailure,

    /// Transaction has an invalid config hash prefix.
    InvalidConfigHashPrefix,
}

impl std::error::Error for TransactionError {}

impl fmt::Display for TransactionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::AccountInUse
             => f.write_str("Account in use"),
            Self::AccountLoadedTwice
             => f.write_str("Account loaded twice"),
            Self::AccountNotFound
             => f.write_str("Attempt to debit an account but found no record of a prior credit."),
            Self::ProgramAccountNotFound
             => f.write_str("Attempt to load a program that does not exist"),
            Self::InsufficientFundsForFee
             => f.write_str("Insufficient funds for fee"),
            Self::InvalidAccountForFee
             => f.write_str("This account may not be used to pay transaction fees"),
            Self::AlreadyProcessed
             => f.write_str("This transaction has already been processed"),
            Self::TimestampTooStale
             => f.write_str("Transaction timestamp in seconds is too stale"),
            Self::TimestampInFuture
             => f.write_str("Transaction timestamp in seconds is in the future"),
            Self::InstructionError(idx, err) =>  write!(f, "Error processing Instruction {idx}: {err}"),
            Self::InvalidAccountIndex
             => f.write_str("Transaction contains an invalid account reference"),
            Self::SignatureFailure
             => f.write_str("Transaction did not pass signature verification"),
            Self::InvalidProgramForExecution
             => f.write_str("This program may not be used for executing instructions"),
            Self::SanitizeFailure
             => f.write_str("Transaction failed to sanitize accounts offsets correctly"),
            Self::AccountBorrowOutstanding
             => f.write_str("Transaction processing left an account with an outstanding borrowed reference"),
            Self::WouldExceedMaxBlockCostLimit
             => f.write_str("Transaction would exceed max Block Cost Limit"),
            Self::UnsupportedVersion
             => f.write_str("Transaction version is unsupported"),
            Self::InvalidWritableAccount
             => f.write_str("Transaction loads a writable account that cannot be written"),
            Self::WouldExceedMaxAccountCostLimit
             => f.write_str("Transaction would exceed max account limit within the block"),
            Self::WouldExceedAccountDataBlockLimit
             => f.write_str("Transaction would exceed account data limit within the block"),
            Self::TooManyAccountLocks
             => f.write_str("Transaction locked too many accounts"),
            Self::InvalidRentPayingAccount
             => f.write_str("Transaction leaves an account with a lower balance than rent-exempt minimum"),
            Self::WouldExceedMaxVoteCostLimit
             => f.write_str("Transaction would exceed max Vote Cost Limit"),
            Self::DuplicateInstruction(idx) =>  write!(f, "Transaction contains a duplicate instruction ({idx}) that is not allowed"),
            Self::InsufficientFundsForRent {
                account_index
            } =>  write!(f,"Transaction results in an account ({account_index}) with insufficient funds for rent"),
            Self::MaxLoadedAccountsDataSizeExceeded
             => f.write_str("Transaction exceeded max loaded accounts data size cap"),
            Self::InvalidLoadedAccountsDataSizeLimit
             => f.write_str("LoadedAccountsDataSizeLimit set for transaction must be greater than 0."),
            Self::ResanitizationNeeded
             => f.write_str("ResanitizationNeeded"),
            Self::UnbalancedTransaction
             => f.write_str("Sum of account balances before and after transaction do not match"),
            Self::ProgramCacheHitMaxLimit
             => f.write_str("Program cache hit max limit"),
            Self::SerializationFailure => {
                f.write_str("Serialization failure")
            }
            Self::InvalidConfigHashPrefix => {
                f.write_str(
                    "Transaction has an invalid config hash prefix. \
                     The config hash may have changed or you may be connecting to a different network. \
                     Fetch the current config hash using getRecentValidatorConfigHash and retry."
                )
            }
        }
    }
}

impl From<SanitizeError> for TransactionError {
    fn from(_: SanitizeError) -> Self {
        Self::SanitizeFailure
    }
}

#[cfg(not(target_os = "solana"))]
impl From<SanitizeMessageError> for TransactionError {
    fn from(err: SanitizeMessageError) -> Self {
        #[allow(clippy::match_single_binding)]
        match err {
            _ => Self::SanitizeFailure,
        }
    }
}

#[cfg(not(target_os = "solana"))]
#[derive(PartialEq, Debug, Eq, Clone)]
pub enum SanitizeMessageError {
    IndexOutOfBounds,
    ValueOutOfBounds,
    InvalidValue,
}

#[cfg(not(target_os = "solana"))]
impl std::error::Error for SanitizeMessageError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::IndexOutOfBounds => None,
            Self::ValueOutOfBounds => None,
            Self::InvalidValue => None,
        }
    }
}

#[cfg(not(target_os = "solana"))]
impl fmt::Display for SanitizeMessageError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::IndexOutOfBounds => f.write_str("index out of bounds"),
            Self::ValueOutOfBounds => f.write_str("value out of bounds"),
            Self::InvalidValue => f.write_str("invalid value"),
        }
    }
}

#[cfg(not(target_os = "solana"))]
impl From<SanitizeError> for SanitizeMessageError {
    fn from(err: SanitizeError) -> Self {
        match err {
            SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
            SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
            SanitizeError::InvalidValue => Self::InvalidValue,
        }
    }
}

#[cfg(not(target_os = "solana"))]
#[derive(Debug)]
pub enum TransportError {
    IoError(std::io::Error),
    TransactionError(TransactionError),
    Custom(String),
}

#[cfg(not(target_os = "solana"))]
impl std::error::Error for TransportError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            TransportError::IoError(e) => Some(e),
            TransportError::TransactionError(e) => Some(e),
            TransportError::Custom(_) => None,
        }
    }
}

#[cfg(not(target_os = "solana"))]
impl fmt::Display for TransportError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> ::core::fmt::Result {
        match self {
            Self::IoError(e) => f.write_fmt(format_args!("transport io error: {e}")),
            Self::TransactionError(e) => {
                f.write_fmt(format_args!("transport transaction error: {e}"))
            }
            Self::Custom(s) => f.write_fmt(format_args!("transport custom error: {s}")),
        }
    }
}

#[cfg(not(target_os = "solana"))]
impl From<std::io::Error> for TransportError {
    fn from(e: std::io::Error) -> Self {
        TransportError::IoError(e)
    }
}

#[cfg(not(target_os = "solana"))]
impl From<TransactionError> for TransportError {
    fn from(e: TransactionError) -> Self {
        TransportError::TransactionError(e)
    }
}

#[cfg(not(target_os = "solana"))]
impl TransportError {
    pub fn unwrap(&self) -> TransactionError {
        if let TransportError::TransactionError(err) = self {
            err.clone()
        } else {
            panic!("unexpected transport error")
        }
    }
}

#[cfg(not(target_os = "solana"))]
pub type TransportResult<T> = std::result::Result<T, TransportError>;
