use std::str::FromStr;

use super::{helpers::fetch_table_accounts, instructions::get_liquidate_ix};
use anchor_client::{
    solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcSimulateTransactionConfig},
    solana_sdk::{
        commitment_config::CommitmentConfig,
        compute_budget::ComputeBudgetInstruction,
        message::{v0::Message, VersionedMessage},
        signer::Signer,
        transaction::VersionedTransaction,
    },
    Cluster,
};
use anchor_lang::prelude::Pubkey;
use regex::Regex;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct VaultLiquidation {
    pub amt_out: u64,
    pub amt_in: u64,
    pub top_tick: i32,
}

pub async fn get_vault_liquidations(
    vault_id: u16,
    col_per_unit_debt: u128,
    absorb: bool,
    signer: Option<Pubkey>,
    cluster: Cluster,
) -> anyhow::Result<Vec<VaultLiquidation>> {
    let signer = signer.unwrap_or(Pubkey::from_str(
        "HEyJLdMfZhhQ7FHCtjD5DWDFNFQhaeAVAsHeWqoY6dSD",
    )?);

    let liquidate = get_liquidate_ix(
        vault_id,
        (2_u128.pow(64) - 1) as u64,
        col_per_unit_debt,
        absorb,
        Pubkey::default(),
        signer,
        cluster.clone(),
    )
    .await?;

    let rpc =
        RpcClient::new_with_commitment(cluster.url().to_string(), CommitmentConfig::confirmed());

    let (recent_blockhash, address_lookup_table_accounts) = tokio::join!(
        rpc.get_latest_blockhash(),
        fetch_table_accounts(&rpc, liquidate.address_lookup_table_addresses)
    );
    let recent_blockhash = recent_blockhash?;

    let compute_ix = ComputeBudgetInstruction::set_compute_unit_limit(1_000_000u32);
    let mut ixs = vec![compute_ix];

    ixs.extend(liquidate.ixs);

    let message = Message::try_compile(
        &signer,
        &ixs,
        &address_lookup_table_accounts,
        recent_blockhash,
    )?;

    let transaction =
        VersionedTransaction::try_new(VersionedMessage::V0(message), &[&Keypair::new(signer)])?;

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

    Ok(parse_vault_liquidations(
        simulation.value.logs.unwrap_or_default(),
    ))
}

#[derive(Debug, Clone)]
struct Keypair {
    pubkey: Pubkey,
}

impl Keypair {
    fn new(pubkey: Pubkey) -> Self {
        Keypair { pubkey }
    }
}

impl Signer for Keypair {
    fn pubkey(&self) -> Pubkey {
        self.pubkey
    }

    fn try_pubkey(&self) -> Result<Pubkey, anchor_client::solana_sdk::signer::SignerError> {
        Ok(self.pubkey())
    }

    fn try_sign_message(
        &self,
        _message: &[u8],
    ) -> Result<
        anchor_client::solana_sdk::signature::Signature,
        anchor_client::solana_sdk::signer::SignerError,
    > {
        Ok(anchor_client::solana_sdk::signature::Signature::default())
    }

    fn is_interactive(&self) -> bool {
        true
    }
}

fn parse_vault_liquidations(logs: Vec<String>) -> Vec<VaultLiquidation> {
    let mut results = Vec::new();
    let re = Regex::new(r"VaultLiquidationResult: \[([^\]]+)\]").unwrap();

    for log in logs {
        if let Some(caps) = re.captures(&log) {
            if let Some(m) = caps.get(1) {
                let values: Vec<&str> = m.as_str().split(", ").map(|v| v.trim()).collect();
                if values.len() == 3 {
                    let amt_out = values[0].parse::<u64>().unwrap_or(0);
                    let amt_in = values[1].parse::<u64>().unwrap_or(0);
                    let top_tick = values[2].parse::<i32>().unwrap_or(0);

                    if amt_in != 0 && amt_out != 0 {
                        results.push(VaultLiquidation {
                            amt_out,
                            amt_in,
                            top_tick,
                        });
                    }
                }
            }
        }
    }

    results
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_vault_liquidations() {
        let logs = vec![
            "Program ComputeBudget111111111111111111111111111111 invoke [1]".to_string(),
            "Program ComputeBudget111111111111111111111111111111 success".to_string(),
            "Program jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi invoke [1]".to_string(),
            "Program log: Instruction: Liquidate".to_string(),
            "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [2]".to_string(),
            "Program log: Create".to_string(),
            "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]".to_string(),
            "Program log: Instruction: GetAccountDataSize".to_string(),
            "VaultLiquidationResult: [100, 200, 300]".to_string(),
            "VaultLiquidationResult: [300, 400, 500]".to_string(),
        ];

        let expected = vec![
            VaultLiquidation {
                amt_out: 100,
                amt_in: 200,
                top_tick: 300,
            },
            VaultLiquidation {
                amt_out: 300,
                amt_in: 400,
                top_tick: 500,
            },
        ];

        assert_eq!(parse_vault_liquidations(logs), expected);
    }
}
