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,
    },
    shared_accounts::{BaseAccounts, KaminoAccounts, KaminoCTokenAccounts},
};

pub struct DepositKaminoParams {
    pub owner: Pubkey,
    pub payer: Pubkey,
    pub liquidity_mint: Pubkey,
    pub liquidity_token_program: Pubkey,
    pub kamino_market: Pubkey,
    pub kamino_reserve: Pubkey,
    pub kamino_reserve_liquidity_supply: Pubkey,
    pub kamino_reserve_collateral_mint: Pubkey,
    pub kamino_reserve_collateral_supply: Pubkey,
    pub kamino_reserve_collateral_token_program: Pubkey,
    pub kamino_user_farm_state: Option<Pubkey>,
    pub kamino_reserve_farm_state: Option<Pubkey>,
}

pub fn deposit_kamino(params: &DepositKaminoParams) -> 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 deposit_address = pyra_accounts::get_deposit_address(&params.owner);
    let deposit_address_spl = if params.liquidity_mint == pyra_tokens::WSOL.mint {
        None
    } else {
        Some(pyra_accounts::get_associated_token_address(
            &deposit_address,
            &params.liquidity_mint,
            &params.liquidity_token_program,
        ))
    };

    let accounts = crate::pyra_program::client::accounts::DepositKamino {
        deposit_base_accounts: BaseAccounts {
            payer: params.payer,
            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,
        },
        deposit_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,
        },
        deposit_kamino_c_token: KaminoCTokenAccounts {
            reserve_mint: params.kamino_reserve_collateral_mint,
            reserve_supply: params.kamino_reserve_collateral_supply,
            token_program: params.kamino_reserve_collateral_token_program,
        },
        kamino_obligation,
        deposit_address,
        deposit_address_spl,
    }
    .to_account_metas(None);

    let data = crate::pyra_program::client::args::DepositKamino {}.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_deposit_kamino_instruction() {
        let owner = pubkey!("d4A2prbA2whesmvHaL88BH6Ewn5N4bTSjm4GiKy2eSi");
        let payer = pubkey!("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF");
        let market = pubkey!("Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD");
        let reserve = pubkey!("AdtRGGhmqvom3Fk5H2LTnGMfcXmUQdhSz8aPJGfhFqHj");
        let mint = pubkey!("DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5");
        let token_program = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");

        let coll_mint = pubkey!("BAtFj4kQttZRVep3UZS2aZRDixkGYgWsbqTBVDbnSsPF");
        let liq_supply = pubkey!("En8hkHLkRe9d9DraYmBTrus518BvmVH448YcvmrFM6Ce");
        let coll_supply = pubkey!("9fQJnr5EquzcSMijJkhG9VTzGbYcZrDnBmSW9JrSpYHg");

        let ix = deposit_kamino(&DepositKaminoParams {
            owner,
            payer,
            liquidity_mint: mint,
            liquidity_token_program: token_program,
            kamino_market: market,
            kamino_reserve: reserve,
            kamino_reserve_collateral_mint: coll_mint,
            kamino_reserve_liquidity_supply: liq_supply,
            kamino_reserve_collateral_supply: coll_supply,
            kamino_reserve_collateral_token_program: token_program,
            kamino_user_farm_state: None,
            kamino_reserve_farm_state: None,
        });

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

        let payer_meta = ix.accounts.iter().find(|m| m.pubkey == payer).unwrap();
        assert!(payer_meta.is_signer);
        assert!(payer_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);

        // Non-WSOL mint: deposit_address_spl ATA is included
        let deposit_address = pyra_accounts::get_deposit_address(&owner);
        let deposit_address_spl = pyra_accounts::get_associated_token_address(
            &deposit_address,
            &mint,
            &token_program,
        );
        assert!(ix.accounts.iter().any(|m| m.pubkey == deposit_address_spl));

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

    #[test]
    fn test_deposit_kamino_with_wsol_mint() {
        // For WSOL the builder skips the deposit_address_spl derivation
        // (raw lamports have no ATA) — the optional account meta is still
        // present but holds the program id as a placeholder.
        let owner = pubkey!("d4A2prbA2whesmvHaL88BH6Ewn5N4bTSjm4GiKy2eSi");
        let payer = pubkey!("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF");
        let market = pubkey!("Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD");
        let reserve = pubkey!("AdtRGGhmqvom3Fk5H2LTnGMfcXmUQdhSz8aPJGfhFqHj");
        let token_program = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
        let coll_mint = pubkey!("BAtFj4kQttZRVep3UZS2aZRDixkGYgWsbqTBVDbnSsPF");
        let liq_supply = pubkey!("En8hkHLkRe9d9DraYmBTrus518BvmVH448YcvmrFM6Ce");
        let coll_supply = pubkey!("9fQJnr5EquzcSMijJkhG9VTzGbYcZrDnBmSW9JrSpYHg");

        let ix = deposit_kamino(&DepositKaminoParams {
            owner,
            payer,
            liquidity_mint: pyra_tokens::WSOL.mint,
            liquidity_token_program: token_program,
            kamino_market: market,
            kamino_reserve: reserve,
            kamino_reserve_collateral_mint: coll_mint,
            kamino_reserve_liquidity_supply: liq_supply,
            kamino_reserve_collateral_supply: coll_supply,
            kamino_reserve_collateral_token_program: token_program,
            kamino_user_farm_state: None,
            kamino_reserve_farm_state: None,
        });

        assert_eq!(ix.program_id, pyra_accounts::PYRA_PROGRAM_ID);
        assert_eq!(ix.accounts.len(), 22);

        // The deposit_address-derived ATA must not appear: WSOL deposits skip it.
        let deposit_address = pyra_accounts::get_deposit_address(&owner);
        let deposit_address_spl_wsol = pyra_accounts::get_associated_token_address(
            &deposit_address,
            &pyra_tokens::WSOL.mint,
            &token_program,
        );
        assert!(!ix
            .accounts
            .iter()
            .any(|m| m.pubkey == deposit_address_spl_wsol));

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