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

use crate::{
    constants::{INSTRUCTIONS_SYSVAR_ID, SYSTEM_PROGRAM_ID},
    pyra_program::client::accounts::StartSwapKamino,
    shared_accounts::SwapAccounts,
};

pub struct StartSwapKaminoParams {
    pub payer: Pubkey,
    pub liquidity_provider: Pubkey,
    pub owner: Pubkey,
    pub mint_from: Pubkey,
    pub mint_to: Pubkey,
    pub token_program_from: Pubkey,
    pub token_program_to: Pubkey,
}

pub fn start_swap_kamino(params: &StartSwapKaminoParams) -> Instruction {
    let vault = pyra_accounts::get_vault(&params.owner);
    let vault_ata_from = pyra_accounts::get_associated_token_address(
        &vault,
        &params.mint_from,
        &params.token_program_from,
    );
    let vault_ata_to = pyra_accounts::get_associated_token_address(
        &vault,
        &params.mint_to,
        &params.token_program_to,
    );
    let liquidity_provider_ata_from = pyra_accounts::get_associated_token_address(
        &params.liquidity_provider,
        &params.mint_from,
        &params.token_program_from,
    );
    let liquidity_provider_ata_to = pyra_accounts::get_associated_token_address(
        &params.liquidity_provider,
        &params.mint_to,
        &params.token_program_to,
    );
    let ledger = pyra_accounts::get_swap_ledger(&params.mint_from);

    let accounts = StartSwapKamino {
        start_swap_accounts: SwapAccounts {
            payer: params.payer,
            liquidity_provider: params.liquidity_provider,
            liquidity_provider_ata_from,
            liquidity_provider_ata_to,
            owner: params.owner,
            vault,
            vault_ata_from,
            vault_ata_to,
            mint_from: params.mint_from,
            mint_to: params.mint_to,
            token_program_from: params.token_program_from,
            token_program_to: params.token_program_to,
            associated_token_program: pyra_accounts::ASSOCIATED_TOKEN_PROGRAM_ID,
            system_program: SYSTEM_PROGRAM_ID,
            instructions_sysvar_account: INSTRUCTIONS_SYSVAR_ID,
        },
        ledger,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .to_account_metas(None);

    let data = crate::pyra_program::client::args::StartSwapKamino {}.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() -> StartSwapKaminoParams {
        let token_program = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
        StartSwapKaminoParams {
            payer: Pubkey::new_unique(),
            liquidity_provider: Pubkey::new_unique(),
            owner: Pubkey::new_unique(),
            mint_from: Pubkey::new_unique(),
            mint_to: Pubkey::new_unique(),
            token_program_from: token_program,
            token_program_to: token_program,
        }
    }

    #[test]
    fn test_start_swap_kamino_instruction() {
        let params = base_params();
        let ix = start_swap_kamino(&params);

        assert_eq!(ix.program_id, pyra_accounts::PYRA_PROGRAM_ID);
        // 17 accounts: 15 (SwapAccounts) + ledger + system_program
        assert_eq!(ix.accounts.len(), 17);

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

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

        // ledger derived from mint_from
        let ledger = pyra_accounts::get_swap_ledger(&params.mint_from);
        let ledger_meta = ix.accounts.iter().find(|m| m.pubkey == ledger).unwrap();
        assert!(ledger_meta.is_writable);

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

    /// Pin discriminator + arg layout. `start_swap_kamino` uses a four-byte
    /// sub-discriminator ([0, 0, 0, 1]) of the swap-family dispatcher and no args.
    #[test]
    fn start_swap_kamino_data_layout_pinned() {
        let ix = start_swap_kamino(&base_params());
        assert_eq!(ix.data, vec![0, 0, 0, 1]);
    }
}
