//! Add liquidity to the Raydium pool instruction

use {
    solana_farm_sdk::{
        instruction::raydium::RaydiumAddLiquidity,
        program::{account, protocol::raydium},
    },
    solana_program::{
        account_info::AccountInfo,
        entrypoint::ProgramResult,
        instruction::{AccountMeta, Instruction},
        msg,
        program::invoke,
        program_error::ProgramError,
    },
};

pub fn add_liquidity(
    accounts: &[AccountInfo],
    max_coin_token_amount: u64,
    max_pc_token_amount: u64,
) -> ProgramResult {
    msg!("Processing AmmInstruction::AddLiquidity");
    msg!("max_coin_token_amount {} ", max_coin_token_amount);
    msg!("max_pc_token_amount {} ", max_pc_token_amount);

    #[allow(clippy::deprecated_cfg_attr)]
    #[cfg_attr(rustfmt, rustfmt_skip)]
    if let [
        user_account,
        user_token_a_account,
        user_token_b_account,
        user_lp_token_account,
        pool_program_id,
        pool_coin_token_account,
        pool_pc_token_account,
        lp_token_mint,
        spl_token_id,
        amm_id,
        amm_authority,
        amm_open_orders,
        amm_target,
        serum_market
        ] = accounts
    {
        if !raydium::check_pool_program_id(pool_program_id.key) {
            return Err(ProgramError::IncorrectProgramId);
        }
        if !account::check_token_account_owner(user_lp_token_account, user_account.key)? {
            return Err(ProgramError::IllegalOwner);
        }

        let (lp_token_amount, coin_token_amount, pc_token_amount) = raydium::get_pool_deposit_amounts(
            pool_coin_token_account,
            pool_pc_token_account,
            lp_token_mint,
            amm_open_orders,
            amm_id,
            max_coin_token_amount,
            max_pc_token_amount,
        )?;

        let initial_token_a_user_balance = account::get_token_balance(user_token_a_account)?;
        let initial_token_b_user_balance = account::get_token_balance(user_token_b_account)?;
        let initial_lp_token_user_balance = account::get_token_balance(user_lp_token_account)?;

        let raydium_accounts = vec![
            AccountMeta::new_readonly(*spl_token_id.key, false),
            AccountMeta::new(*amm_id.key, false),
            AccountMeta::new_readonly(*amm_authority.key, false),
            AccountMeta::new_readonly(*amm_open_orders.key, false),
            AccountMeta::new(*amm_target.key, false),
            AccountMeta::new(*lp_token_mint.key, false),
            AccountMeta::new(*pool_coin_token_account.key, false),
            AccountMeta::new(*pool_pc_token_account.key, false),
            AccountMeta::new_readonly(*serum_market.key, false),
            AccountMeta::new(*user_token_a_account.key, false),
            AccountMeta::new(*user_token_b_account.key, false),
            AccountMeta::new(*user_lp_token_account.key, false),
            AccountMeta::new_readonly(*user_account.key, true)
        ];

        let instruction = Instruction {
            program_id: *pool_program_id.key,
            accounts: raydium_accounts,
            data: RaydiumAddLiquidity {
                instruction: 3,
                max_coin_token_amount: coin_token_amount,
                max_pc_token_amount: pc_token_amount,
                base_side: 0,
            }
            .to_vec()?,
        };
        invoke(&instruction, accounts)?;

        account::check_tokens_spent(
            user_token_a_account,
            initial_token_a_user_balance,
            coin_token_amount,
        )?;
        account::check_tokens_spent(
            user_token_b_account,
            initial_token_b_user_balance,
            pc_token_amount,
        )?;
        account::check_tokens_received(user_lp_token_account, initial_lp_token_user_balance, lp_token_amount)?;
    } else {
        return Err(ProgramError::NotEnoughAccountKeys);
    }

    msg!("AmmInstruction::AddLiquidity complete");
    Ok(())
}
