use super::common::{DiscriminatorKey, PaginatedResponse};
use crate::{accounts::Issuance, find_bond_pda, types::Discriminator, STABLEBOND_ID};
use anyhow::{anyhow, Result};
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
use solana_client::{
    nonblocking::rpc_client::RpcClient,
    rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
    rpc_filter::{Memcmp, RpcFilterType},
};
use solana_sdk::{account::ReadableAccount, commitment_config::CommitmentConfig, pubkey::Pubkey};

pub async fn get_issuance_by_address(rpc_client: &RpcClient, address: Pubkey) -> Result<Issuance> {
    let issuance_account = rpc_client.get_account(&address).await?;
    let account = Issuance::from_bytes(issuance_account.data())?;
    Ok(account)
}

pub async fn get_current_issuance(
    rpc_client: &RpcClient,
    bond_address: Pubkey,
) -> Result<Option<(String, Issuance)>> {
    let issuance_index = get_issuance_index(rpc_client, bond_address).await?;
    if let Some((address, _)) = issuance_index.last() {
        let issuance_account = rpc_client.get_account(address).await?;
        let issuance = Issuance::from_bytes(issuance_account.data())?;
        return Ok(Some((address.to_string(), issuance)));
    }
    Ok(None)
}

pub async fn get_issuances_by_mint(
    rpc_client: &RpcClient,
    mint: Pubkey,
    page: usize,
    size: usize,
) -> Result<PaginatedResponse<Issuance>> {
    let (bond_address, _) = find_bond_pda(mint);
    get_issuances_by_bond(rpc_client, bond_address, page, size).await
}

pub async fn get_issuances_by_bond(
    rpc_client: &RpcClient,
    bond_address: Pubkey,
    page: usize,
    size: usize,
) -> Result<PaginatedResponse<Issuance>> {
    if size > 100 {
        return Err(anyhow!("Size must be less than or equal to 100"));
    }

    let issuance_index = get_issuance_index(rpc_client, bond_address).await?;
    let total_items = issuance_index.len();

    let start_index = (page - 1) * size;
    if start_index > total_items {
        return Ok(PaginatedResponse {
            items: vec![],
            page,
            size: 0,
            total_items,
        });
    }

    let mut end_index = page * size;
    if end_index > total_items {
        end_index = total_items;
    }

    let issuance_addresses = issuance_index
        .into_iter()
        .map(|(address, _)| address)
        .collect::<Vec<Pubkey>>();
    let issuance_addresses = &issuance_addresses[start_index..end_index];
    let issuance_accounts = rpc_client
        .get_multiple_accounts(&issuance_addresses)
        .await?;
    let mut issuances = Vec::<(String, Issuance)>::new();
    for (index, issuance_account) in issuance_accounts.into_iter().enumerate() {
        if let Some(account) = issuance_account {
            let issuance = Issuance::from_bytes(account.data())?;
            issuances.push((issuance_addresses[index].to_string(), issuance));
        }
    }
    Ok(PaginatedResponse {
        items: issuances,
        page,
        size,
        total_items,
    })
}

pub async fn get_issuance_index(
    rpc_client: &RpcClient,
    bond_address: Pubkey,
) -> Result<Vec<(Pubkey, u64)>> {
    let discriminator_key: DiscriminatorKey = Discriminator::Issuance.into();
    let filter_vec = vec![
        RpcFilterType::Memcmp(Memcmp::new_base58_encoded(0, &[discriminator_key.into()])), // Issuances
        RpcFilterType::Memcmp(Memcmp::new_base58_encoded(1, &bond_address.to_bytes())), // For this bond
    ];

    let config = RpcProgramAccountsConfig {
        account_config: RpcAccountInfoConfig {
            commitment: Some(CommitmentConfig::finalized()),
            encoding: Some(UiAccountEncoding::Base64),
            data_slice: Some(UiDataSliceConfig {
                // Grab the actual start date from the account
                offset: 48,
                length: 8,
            }),
            min_context_slot: None,
        },
        filters: Some(filter_vec),
        with_context: Some(true),
    };

    let issuance_index = rpc_client
        .get_program_accounts_with_config(&STABLEBOND_ID, config)
        .await?;

    let mut issuances_with_start_times = Vec::<(Pubkey, i64)>::new();
    for i in issuance_index.clone().into_iter() {
        let (address, account) = i;
        if let Ok(actual_start_datetime) = account.data[0..8].try_into() {
            issuances_with_start_times.push((address, i64::from_le_bytes(actual_start_datetime)));
        }
    }

    issuances_with_start_times.sort_by(|a, b| a.1.cmp(&b.1)); // Sort by actual start date

    let mut sorted_issuances = Vec::<(Pubkey, u64)>::new();
    for (index, stuff) in issuances_with_start_times.into_iter().enumerate() {
        // Push to the list of sorted issuances with the issuance number
        sorted_issuances.push((stuff.0, (index + 1) as u64));
    }

    Ok(sorted_issuances)
}
