use std::collections::HashMap;

use super::common::PaginatedResponse;
use crate::{
    accounts::PurchaseOrder, sdk::common::DiscriminatorKey, types::Discriminator, STABLEBOND_ID,
};
use anyhow::{anyhow, Result};
use borsh::BorshDeserialize;
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_purchase_order_by_address(
    rpc_client: &RpcClient,
    address: Pubkey,
) -> Result<PurchaseOrder> {
    let purchase_order_account = rpc_client.get_account(&address).await?;
    let account = PurchaseOrder::from_bytes(purchase_order_account.data())?;
    Ok(account)
}

pub async fn get_purchase_order_by_owner(
    rpc_client: &RpcClient,
    owner: Pubkey,
) -> Result<(String, PurchaseOrder)> {
    let purchase_order_index = get_purchase_order_index(rpc_client, None).await?;

    let purchase_order_address = purchase_order_index
        .into_iter()
        .find(|(_, owner_address)| *owner_address == owner)
        .map(|(address, _)| address)
        .ok_or(anyhow!("No purchase order found for owner"))?;

    let purchase_order_account = rpc_client.get_account(&purchase_order_address).await?;
    let account = PurchaseOrder::from_bytes(purchase_order_account.data())?;

    Ok((purchase_order_address.to_string(), account))
}

pub async fn get_purchase_orders_by_mint(
    rpc_client: &RpcClient,
    mint: Pubkey,
    page: usize,
    size: usize,
) -> Result<PaginatedResponse<PurchaseOrder>> {
    get_purchase_orders_with_filter(rpc_client, Some(mint), page, size).await
}

pub async fn get_purchase_orders(
    rpc_client: &RpcClient,
    page: usize,
    size: usize,
) -> Result<PaginatedResponse<PurchaseOrder>> {
    get_purchase_orders_with_filter(rpc_client, None, page, size).await
}

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

    let purchase_order_index = get_purchase_order_index(rpc_client, mint).await?;
    let total_items = purchase_order_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;
    }

    // take a slice of the purchase_order_index
    let purchase_order_addresses = purchase_order_index
        .into_iter()
        .map(|(address, _)| address)
        .collect::<Vec<Pubkey>>();

    let purchase_order_addresses = &purchase_order_addresses[start_index..end_index];
    let response = get_purchase_orders_by_addresses(rpc_client, purchase_order_addresses).await?;

    Ok(PaginatedResponse {
        items: response,
        page,
        size,
        total_items,
    })
}

async fn get_purchase_order_index(
    rpc_client: &RpcClient,
    mint: Option<Pubkey>,
) -> Result<Vec<(Pubkey, Pubkey)>> {
    let purchase_order_discriminator_key: DiscriminatorKey = Discriminator::PurchaseOrder.into(); // Get purchase orders

    let mut filter_vec = vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
        0,
        &[purchase_order_discriminator_key.into()],
    ))];

    if mint.is_some() {
        filter_vec.push(RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
            33,
            &mint.unwrap().to_bytes(),
        )));
    }

    let config = RpcProgramAccountsConfig {
        account_config: RpcAccountInfoConfig {
            commitment: Some(CommitmentConfig::finalized()),
            encoding: Some(UiAccountEncoding::Base64),
            data_slice: Some(UiDataSliceConfig {
                // Grab the owner address from the account
                offset: 1,
                length: 32,
            }),
            min_context_slot: None,
        },
        filters: Some(filter_vec),
        with_context: Some(true),
    };

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

    let mut purchase_orders_with_owner = Vec::<(Pubkey, Pubkey)>::new();
    for stuff in purchase_order_index.into_iter() {
        let (address, account) = stuff;
        if let Ok(owner) = Pubkey::try_from_slice(&account.data()) {
            purchase_orders_with_owner.push((address, owner));
        }
    }
    purchase_orders_with_owner.sort_by(|a, b| a.1.cmp(&b.1));

    Ok(purchase_orders_with_owner)
}

async fn get_purchase_orders_by_addresses(
    rpc_client: &RpcClient,
    purchase_order_addresses: &[Pubkey],
) -> Result<Vec<(String, PurchaseOrder)>> {
    let purchase_orders = rpc_client
        .get_multiple_accounts(&purchase_order_addresses)
        .await?;

    let mut purchase_order_map = HashMap::<Pubkey, PurchaseOrder>::new();
    for (index, option_account) in purchase_orders.into_iter().enumerate() {
        if let Some(purchase_order_account) = option_account {
            let account = PurchaseOrder::from_bytes(purchase_order_account.data())?;
            purchase_order_map.insert(purchase_order_addresses[index], account);
        }
    }

    let mut response = Vec::<(String, PurchaseOrder)>::new();
    for addr in purchase_order_addresses.into_iter() {
        if let Some(val) = purchase_order_map.get(addr) {
            response.push((addr.to_string(), val.clone()));
        }
    }

    Ok(response)
}
