use crate::{
    accounts::{
        AccessPass, Bond, BondMultisigMeta, Config, Delegate, Issuance, Kyc, NftIssuanceVault,
        Offramp, Onramp, Payment, PaymentFeed, Payout, PurchaseOrder, SellLiquidity,
    },
    types::Discriminator,
};
use anyhow::Result;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{account::ReadableAccount, pubkey::Pubkey};

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AccountType {
    Bond(Bond),
    Config(Config),
    Delegate(Delegate),
    Issuance(Issuance),
    NftIssuanceVault(NftIssuanceVault),
    Payment(Payment),
    Payout(Payout),
    PaymentFeed(PaymentFeed),
    AccessPass(AccessPass),
    PurchaseOrder(PurchaseOrder),
    Kyc(Kyc),
    SellLiquidity(SellLiquidity),
    Offramp(Offramp),
    Onramp(Onramp),
    BondMultisigMeta(BondMultisigMeta),
}

#[derive(Debug, Clone, Copy)]
pub struct DiscriminatorKey(u8);

impl Into<u8> for DiscriminatorKey {
    fn into(self) -> u8 {
        self.0
    }
}

impl From<u8> for DiscriminatorKey {
    fn from(value: u8) -> Self {
        DiscriminatorKey(value)
    }
}

impl From<Discriminator> for DiscriminatorKey {
    fn from(value: Discriminator) -> Self {
        match value {
            Discriminator::Bond => DiscriminatorKey(0),
            Discriminator::Config => DiscriminatorKey(1),
            Discriminator::Delegate => DiscriminatorKey(2),
            Discriminator::Issuance => DiscriminatorKey(3),
            Discriminator::NftIssuanceVault => DiscriminatorKey(4),
            Discriminator::Payment => DiscriminatorKey(5),
            Discriminator::Payout => DiscriminatorKey(6),
            Discriminator::PaymentFeed => DiscriminatorKey(7),
            Discriminator::AccessPass => DiscriminatorKey(8),
            Discriminator::PurchaseOrder => DiscriminatorKey(9),
            Discriminator::Kyc => DiscriminatorKey(10),
            Discriminator::SellLiquidity => DiscriminatorKey(11),
            Discriminator::Offramp => DiscriminatorKey(12),
            Discriminator::Onramp => DiscriminatorKey(13),
            Discriminator::BondMultisigMeta => DiscriminatorKey(14),
        }
    }
}

impl TryFrom<DiscriminatorKey> for Discriminator {
    type Error = anyhow::Error;

    fn try_from(value: DiscriminatorKey) -> anyhow::Result<Self> {
        match value.0 {
            0 => Ok(Discriminator::Bond),
            1 => Ok(Discriminator::Config),
            2 => Ok(Discriminator::Delegate),
            3 => Ok(Discriminator::Issuance),
            4 => Ok(Discriminator::NftIssuanceVault),
            5 => Ok(Discriminator::Payment),
            6 => Ok(Discriminator::Payout),
            7 => Ok(Discriminator::PaymentFeed),
            8 => Ok(Discriminator::AccessPass),
            9 => Ok(Discriminator::PurchaseOrder),
            10 => Ok(Discriminator::Kyc),
            11 => Ok(Discriminator::SellLiquidity),
            12 => Ok(Discriminator::Offramp),
            13 => Ok(Discriminator::Onramp),
            _ => Err(anyhow::anyhow!("Invalid account type")),
        }
    }
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PaginatedResponse<T> {
    pub items: Vec<(String, T)>,
    pub page: usize,
    pub size: usize,
    pub total_items: usize,
}

pub fn remove_null_bytes(s: String) -> String {
    String::from(s.trim_matches(char::from(0)))
}

pub async fn get_account_by_address(
    rpc_client: &RpcClient,
    address: Pubkey,
) -> Result<AccountType> {
    let account = rpc_client.get_account(&address).await?;
    let data_slice = account.data()[0..1].try_into()?;
    let discriminator_key = DiscriminatorKey(u8::from_le_bytes(data_slice));
    let discriminator: Discriminator = discriminator_key.try_into()?;
    let account_type = match discriminator {
        Discriminator::Bond => {
            let bond = Bond::from_bytes(&account.data())?;
            AccountType::Bond(bond)
        }
        Discriminator::AccessPass => {
            let access_pass = AccessPass::from_bytes(&account.data())?;
            AccountType::AccessPass(access_pass)
        }
        Discriminator::Config => {
            let config = Config::from_bytes(&account.data())?;
            AccountType::Config(config)
        }
        Discriminator::Delegate => {
            let delegate = Delegate::from_bytes(&account.data())?;
            AccountType::Delegate(delegate)
        }
        Discriminator::Issuance => {
            let issuance = Issuance::from_bytes(&account.data())?;
            AccountType::Issuance(issuance)
        }
        Discriminator::NftIssuanceVault => {
            let nft_issuance_vault = NftIssuanceVault::from_bytes(&account.data())?;
            AccountType::NftIssuanceVault(nft_issuance_vault)
        }
        Discriminator::Payment => {
            let payment = Payment::from_bytes(&account.data())?;
            AccountType::Payment(payment)
        }
        Discriminator::PaymentFeed => {
            let payment_feed = PaymentFeed::from_bytes(&account.data())?;
            AccountType::PaymentFeed(payment_feed)
        }
        Discriminator::Payout => {
            let payout = Payout::from_bytes(&account.data())?;
            AccountType::Payout(payout)
        }
        Discriminator::PurchaseOrder => {
            let purchase_order = PurchaseOrder::from_bytes(&account.data())?;
            AccountType::PurchaseOrder(purchase_order)
        }
        Discriminator::Kyc => {
            let kyc = Kyc::from_bytes(&account.data())?;
            AccountType::Kyc(kyc)
        }
        Discriminator::SellLiquidity => {
            let sell_liquidity = SellLiquidity::from_bytes(&account.data())?;
            AccountType::SellLiquidity(sell_liquidity)
        }
        Discriminator::Offramp => {
            let offramp = Offramp::from_bytes(&account.data())?;
            AccountType::Offramp(offramp)
        }
        Discriminator::Onramp => {
            let onramp = Onramp::from_bytes(&account.data())?;
            AccountType::Onramp(onramp)
        }
        Discriminator::BondMultisigMeta => {
            let bond_multisig_meta = BondMultisigMeta::from_bytes(&account.data())?;
            AccountType::BondMultisigMeta(bond_multisig_meta)
        }
    };
    Ok(account_type)
}
