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

pub struct RepayKaminoParams {
    pub amount_base_units: u64,
    pub payer: Pubkey,
    pub liquidity_provider: Pubkey,
    pub owner: 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 scope_prices: Option<Pubkey>,
}

pub fn repay_kamino(params: &RepayKaminoParams) -> Instruction {
    let vault = pyra_accounts::get_vault(&params.owner);
    let vault_ata = pyra_accounts::get_associated_token_address(
        &vault,
        &params.liquidity_mint,
        &params.liquidity_token_program,
    );
    let liquidity_provider_ata = pyra_accounts::get_associated_token_address(
        &params.liquidity_provider,
        &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 accounts = RepayKamino {
        payer: params.payer,
        liquidity_provider: params.liquidity_provider,
        liquidity_provider_ata,
        owner: params.owner,
        vault,
        vault_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,
        repay_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_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,
        repay_kamino_oracles: KaminoOraclesAccounts {
            scope_prices: params.scope_prices,
        },
    }
    .to_account_metas(None);

    let data = crate::pyra_program::client::args::RepayKamino {
        amount_base_units: params.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;

    fn base_params() -> RepayKaminoParams {
        let token_program = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
        RepayKaminoParams {
            amount_base_units: 1_000_000,
            payer: Pubkey::new_unique(),
            liquidity_provider: Pubkey::new_unique(),
            owner: Pubkey::new_unique(),
            liquidity_mint: Pubkey::new_unique(),
            liquidity_token_program: token_program,
            kamino_market: Pubkey::new_unique(),
            kamino_reserve: Pubkey::new_unique(),
            kamino_reserve_liquidity_supply: Pubkey::new_unique(),
            kamino_reserve_collateral_mint: Pubkey::new_unique(),
            kamino_reserve_collateral_supply: Pubkey::new_unique(),
            kamino_reserve_collateral_token_program: token_program,
            kamino_user_farm_state: None,
            kamino_reserve_farm_state: None,
            scope_prices: None,
        }
    }

    #[test]
    fn test_repay_kamino_instruction() {
        let params = base_params();
        let ix = repay_kamino(&params);

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

        let owner_meta = ix
            .accounts
            .iter()
            .find(|m| m.pubkey == params.owner)
            .unwrap();
        assert!(owner_meta.is_signer);

        let liq_provider_meta = ix
            .accounts
            .iter()
            .find(|m| m.pubkey == params.liquidity_provider)
            .unwrap();
        assert!(liq_provider_meta.is_signer);

        let payer_meta = ix
            .accounts
            .iter()
            .find(|m| m.pubkey == params.payer)
            .unwrap();
        assert!(payer_meta.is_signer);
        assert!(payer_meta.is_writable);

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

    #[test]
    fn test_repay_kamino_with_farm_states_and_scope() {
        let user_farm = Pubkey::new_unique();
        let reserve_farm = Pubkey::new_unique();
        let scope = Pubkey::new_unique();

        let mut params = base_params();
        params.kamino_user_farm_state = Some(user_farm);
        params.kamino_reserve_farm_state = Some(reserve_farm);
        params.scope_prices = Some(scope);

        let ix = repay_kamino(&params);
        assert_eq!(ix.accounts.len(), 24);
        assert!(ix.accounts.iter().any(|m| m.pubkey == user_farm));
        assert!(ix.accounts.iter().any(|m| m.pubkey == reserve_farm));
        assert!(ix.accounts.iter().any(|m| m.pubkey == scope));
    }

    /// Pin discriminator + arg layout. `repay_kamino` uses the default 8-byte
    /// Anchor discriminator followed by one little-endian u64.
    #[test]
    fn repay_kamino_data_layout_pinned() {
        let mut params = base_params();
        params.amount_base_units = 0x0102_0304_0506_0708;
        let ix = repay_kamino(&params);

        let mut expected: Vec<u8> = vec![92, 247, 129, 122, 8, 164, 137, 205];
        expected.extend_from_slice(&params.amount_base_units.to_le_bytes());
        assert_eq!(ix.data, expected);
    }
}
