use std::sync::Arc;

use crate::borrow::errors::ErrorCodes;
use crate::math::casting::Cast;
use crate::oracle::{OraclePriceLiquidate, ORACLE_PROGRAM_ID};
use crate::programs::oracle;
use crate::ReadKeypair;

use anchor_client::solana_sdk::message::Message;
use anchor_client::solana_sdk::transaction::Transaction;
use anchor_client::{
    solana_client::rpc_config::RpcSimulateTransactionConfig,
    solana_sdk::{commitment_config::CommitmentConfig, signer::Signer},
    Client, Cluster,
};
use anchor_lang::prelude::{AccountMeta, Pubkey};

pub async fn get_oracle_price_liquidate(
    oracle: Pubkey,
    cluster: Cluster,
) -> anyhow::Result<OraclePriceLiquidate> {
    let payer = ReadKeypair::new();
    let provider = Client::new_with_options(
        cluster.clone(),
        Arc::new(payer.clone()),
        CommitmentConfig::confirmed(),
    );
    let program = provider.program(ORACLE_PROGRAM_ID)?;
    let oracle_data: oracle::accounts::Oracle = program.account(oracle).await?;

    let mut remaining_accounts = vec![];

    for source in &oracle_data.sources {
        remaining_accounts.push(AccountMeta::new_readonly(source.source, false));
    }

    get_oracle_price_liquidate_from_remaining_accounts(
        oracle,
        &remaining_accounts,
        &vec![remaining_accounts.len() as u8],
        cluster,
    )
    .await
}

pub async fn get_oracle_price_liquidate_from_remaining_accounts(
    oracle: Pubkey,
    remaining_accounts: &Vec<AccountMeta>,
    remaining_accounts_indices: &Vec<u8>,
    cluster: Cluster,
) -> anyhow::Result<OraclePriceLiquidate> {
    let payer = ReadKeypair::new();
    let provider = Client::new_with_options(
        cluster.clone(),
        Arc::new(payer.clone()),
        CommitmentConfig::confirmed(),
    );
    let program = provider.program(ORACLE_PROGRAM_ID)?;
    let oracle_data: oracle::accounts::Oracle = program.account(oracle).await?;

    let mut price = 0;

    let start_index: usize = 0;
    let end_index: usize = start_index + remaining_accounts_indices[0].cast::<usize>()?;

    if remaining_accounts.len() < end_index {
        return Err(ErrorCodes::VaultLiquidateRemainingAccountsTooShort.into());
    }

    let remaining_accounts = remaining_accounts
        .iter()
        .take(end_index)
        .skip(start_index)
        .map(|x| x.clone())
        .collect::<Vec<_>>();

    let instructions = program
        .request()
        .accounts(oracle::client::accounts::GetExchangeRateLiquidate { oracle })
        .accounts(remaining_accounts)
        .args(oracle::client::args::GetExchangeRateLiquidate {
            _nonce: oracle_data.nonce,
        })
        .instructions()?;

    let message = Message::new(&instructions, Some(&payer.pubkey()));
    let transaction = Transaction::new_unsigned(message);

    let simulation = program
        .rpc()
        .simulate_transaction_with_config(
            &transaction,
            RpcSimulateTransactionConfig {
                replace_recent_blockhash: true,
                sig_verify: false,
                ..Default::default()
            },
        )
        .await?;

    if let Some(data) = &simulation.value.return_data {
        #[allow(deprecated)]
        let raw_bytes = base64::decode(&data.data.0)?;
        price = u128::from_le_bytes(raw_bytes.try_into().unwrap());
    }

    Ok(OraclePriceLiquidate {
        price,
        sources: oracle_data.sources,
    })
}
