use anchor_lang::{InstructionData, ToAccountMetas};
use solana_program::{instruction::Instruction, pubkey::Pubkey};

use crate::constants::{
    INSTRUCTIONS_SYSVAR_ID, KAMINO_FARMS_PROGRAM_ID, KAMINO_LENDING_PROGRAM_ID, SYSTEM_PROGRAM_ID,
};

pub struct FulfillWithdrawKaminoParams {
    pub withdraw_order: Pubkey,
    pub owner: Pubkey,
    pub order_payer: Pubkey,
    pub admin: Pubkey,
    pub payer: Pubkey,
    pub destination: Pubkey,
    pub liquidity_mint: Pubkey,
    pub liquidity_token_program: Pubkey,
    pub kamino_market: Pubkey,
    pub kamino_reserve: Pubkey,
    pub amount_base_units: u64,
    /// Optional: the destination SPL token account. Pass `None` if
    /// withdrawing SOL (no ATA for raw lamports). For all other tokens,
    /// this is mandatory.
    pub destination_spl: Option<Pubkey>,
}

pub fn fulfill_withdraw_kamino(params: &FulfillWithdrawKaminoParams) -> Instruction {
    let vault = pyra_accounts::get_vault(&params.owner);
    let vault_liquidity_ata = pyra_accounts::get_associated_token_address(
        &vault,
        &params.liquidity_mint,
        &params.liquidity_token_program,
    );
    let kamino_obligation =
        pyra_accounts::get_kamino_obligation(&params.owner, &params.kamino_market);
    let kamino_market_authority =
        pyra_accounts::get_kamino_lending_market_authority(&params.kamino_market);
    let kamino_reserve_liquidity_supply =
        pyra_accounts::get_kamino_reserve_liquidity_supply(&params.kamino_reserve);
    let kamino_reserve_collateral_mint =
        pyra_accounts::get_kamino_reserve_collateral_mint(&params.kamino_reserve);
    let kamino_reserve_collateral_supply =
        pyra_accounts::get_kamino_reserve_collateral_supply(&params.kamino_reserve);

    let accounts = crate::pyra_program::client::accounts::FulfillWithdrawKamino {
        withdraw_order: params.withdraw_order,
        vault,
        order_payer: params.order_payer,
        admin: params.admin,
        payer: params.payer,
        destination: params.destination,
        destination_spl: params.destination_spl,
        vault_liquidity_ata,
        liquidity_mint: params.liquidity_mint,
        liquidity_token_program: params.liquidity_token_program,
        associated_token_program: pyra_accounts::ASSOCIATED_TOKEN_PROGRAM_ID,
        kamino_program: KAMINO_LENDING_PROGRAM_ID,
        kamino_obligation,
        kamino_market: params.kamino_market,
        kamino_market_authority,
        kamino_reserve: params.kamino_reserve,
        kamino_reserve_liquidity_supply,
        kamino_reserve_collateral_mint,
        kamino_reserve_collateral_supply,
        // All Kamino collateral tokens use spl-token (not Token-2022)
        collateral_token_program: pyra_tokens::TOKEN_PROGRAM_ID,
        instructions_sysvar_account: INSTRUCTIONS_SYSVAR_ID,
        kamino_farms_program: KAMINO_FARMS_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .to_account_metas(None);

    let data = crate::pyra_program::client::args::FulfillWithdrawKamino {
        amount_base_units: params.amount_base_units,
    }
    .data();

    Instruction {
        program_id: pyra_accounts::PYRA_PROGRAM_ID,
        accounts,
        data,
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use solana_pubkey::pubkey;

    #[test]
    fn test_fulfill_withdraw_kamino_instruction() {
        let owner = pubkey!("d4A2prbA2whesmvHaL88BH6Ewn5N4bTSjm4GiKy2eSi");
        let payer = pubkey!("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF");
        let admin = pubkey!("Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD");
        let market = pubkey!("AdtRGGhmqvom3Fk5H2LTnGMfcXmUQdhSz8aPJGfhFqHj");
        let reserve = pubkey!("DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5");
        let mint = pubkey!("3NJYftD5sjVfxFkKFgU7bbtBBhBsBYAg1CEhJkJ4iY3H");
        let token_program = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
        let withdraw_order = pubkey!("BAtFj4kQttZRVep3UZS2aZRDixkGYgWsbqTBVDbnSsPF");
        let destination = pubkey!("En8hkHLkRe9d9DraYmBTrus518BvmVH448YcvmrFM6Ce");

        let ix = fulfill_withdraw_kamino(&FulfillWithdrawKaminoParams {
            withdraw_order,
            owner,
            order_payer: payer,
            admin,
            payer,
            destination,
            liquidity_mint: mint,
            liquidity_token_program: token_program,
            kamino_market: market,
            kamino_reserve: reserve,
            amount_base_units: 1_000_000,
            destination_spl: None,
        });

        assert_eq!(ix.program_id, pyra_accounts::PYRA_PROGRAM_ID);
        // 23 accounts (destination_spl optional adds 1 even when None)
        assert_eq!(ix.accounts.len(), 23);
        // withdraw_order is first and writable
        assert_eq!(ix.accounts[0].pubkey, withdraw_order);
        assert!(ix.accounts[0].is_writable);
        // data should not be empty
        assert!(!ix.data.is_empty());
    }
}
