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

use crate::{
    constants::{
        INSTRUCTIONS_SYSVAR_ID, KAMINO_FARMS_PROGRAM_ID, KLEND_PROGRAM_ID, SYSTEM_PROGRAM_ID,
    },
    pyra_program::client::accounts::SpendCreditKamino,
    shared_accounts::{KaminoAccounts, SpendAccounts},
};

pub struct SpendCreditKaminoParams {
    pub spend_amount_base_units: u64,
    pub fee_amount_base_units: u64,
    pub owner: Pubkey,
    pub admin: Pubkey,
    pub spend_fee_destination: Pubkey,
    pub liquidity_mint: Pubkey,
    pub liquidity_token_program: Pubkey,
    pub kamino_reserve_liquidity_supply: Pubkey,
    pub kamino_market: Pubkey,
    pub kamino_reserve: Pubkey,
    pub borrow_reserve_liquidity_fee_receiver: Pubkey,
    pub kamino_user_farm_state: Option<Pubkey>,
    pub kamino_reserve_farm_state: Option<Pubkey>,
}

pub fn spend_credit_kamino(params: &SpendCreditKaminoParams) -> 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 spend_hold = pyra_accounts::get_spend_hold();
    let spend_hold_liquidity_ata = pyra_accounts::get_associated_token_address(
        &spend_hold,
        &params.liquidity_mint,
        &params.liquidity_token_program,
    );
    let spend_fee_destination_ata = pyra_accounts::get_associated_token_address(
        &params.spend_fee_destination,
        &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_referrer_token_state =
        pyra_accounts::get_kamino_referrer_token_state(&params.kamino_reserve);

    let accounts = SpendCreditKamino {
        spend_credit: SpendAccounts {
            admin: params.admin,
            vault,
            vault_ata: vault_liquidity_ata,
            mint: params.liquidity_mint,
            token_program: params.liquidity_token_program,
            associated_token_program: pyra_accounts::ASSOCIATED_TOKEN_PROGRAM_ID,
            system_program: SYSTEM_PROGRAM_ID,
            hold: spend_hold,
            hold_ata: spend_hold_liquidity_ata,
            fee_destination: params.spend_fee_destination,
            fee_destination_ata: spend_fee_destination_ata,
        },
        spend_credit_kamino_accounts: KaminoAccounts {
            program: KLEND_PROGRAM_ID,
            market: params.kamino_market,
            market_authority: kamino_market_authority,
            reserve: params.kamino_reserve,
            reserve_liquidity_supply: params.kamino_reserve_liquidity_supply,
            farms_program: KAMINO_FARMS_PROGRAM_ID,
            user_farm_state: params.kamino_user_farm_state,
            reserve_farm_state: params.kamino_reserve_farm_state,
            instructions_sysvar_account: INSTRUCTIONS_SYSVAR_ID,
        },
        kamino_referrer_token_state,
        kamino_reserve_liquidity_fee_receiver: params.borrow_reserve_liquidity_fee_receiver,
        kamino_obligation,
    }
    .to_account_metas(None);

    let data = crate::pyra_program::client::args::SpendCreditKamino {
        spend_amount_base_units: params.spend_amount_base_units,
        fee_amount_base_units: params.fee_amount_base_units,
    }
    .data();

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

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

    #[test]
    fn test_spend_credit_kamino_instruction() {
        let owner = pubkey!("d4A2prbA2whesmvHaL88BH6Ewn5N4bTSjm4GiKy2eSi");
        let admin = pubkey!("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF");
        let market = pubkey!("Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD");
        let reserve = pubkey!("AdtRGGhmqvom3Fk5H2LTnGMfcXmUQdhSz8aPJGfhFqHj");
        let mint = pubkey!("DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5");
        let token_program = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
        let fee_dest = pubkey!("3NJYftD5sjVfxFkKFgU7bbtBBhBsBYAg1CEhJkJ4iY3H");
        let liq_supply = pubkey!("En8hkHLkRe9d9DraYmBTrus518BvmVH448YcvmrFM6Ce");
        let fee_receiver = pubkey!("9fQJnr5EquzcSMijJkhG9VTzGbYcZrDnBmSW9JrSpYHg");

        let ix = spend_credit_kamino(&SpendCreditKaminoParams {
            owner,
            admin,
            spend_fee_destination: fee_dest,
            liquidity_mint: mint,
            liquidity_token_program: token_program,
            kamino_market: market,
            kamino_reserve: reserve,
            kamino_reserve_liquidity_supply: liq_supply,
            borrow_reserve_liquidity_fee_receiver: fee_receiver,
            spend_amount_base_units: 1_000_000,
            fee_amount_base_units: 10_000,
            kamino_user_farm_state: None,
            kamino_reserve_farm_state: None,
        });

        assert_eq!(ix.program_id, pyra_accounts::PYRA_PROGRAM_ID);
        // 23 accounts (farm states are optional, add 1 each regardless)
        assert_eq!(ix.accounts.len(), 23);

        let admin_meta = ix.accounts.iter().find(|m| m.pubkey == admin).unwrap();
        assert!(admin_meta.is_signer);
        assert!(admin_meta.is_writable);

        let vault = pyra_accounts::get_vault(&owner);
        let vault_meta = ix.accounts.iter().find(|m| m.pubkey == vault).unwrap();
        assert!(vault_meta.is_writable);

        let obligation = pyra_accounts::get_kamino_obligation(&owner, &market);
        let obligation_meta = ix.accounts.iter().find(|m| m.pubkey == obligation).unwrap();
        assert!(obligation_meta.is_writable);

        // Referrer token state is derived from the reserve, not caller-supplied.
        let expected_referrer = pyra_accounts::get_kamino_referrer_token_state(&reserve);
        assert!(ix.accounts.iter().any(|m| m.pubkey == expected_referrer));

        assert!(!ix.data.is_empty());
    }
}
