use {
    crate::{
        accounts,
        instruction::{
            InitializeArgs,
            CONFIG_SEED,
        },
        validate::{
            validate_config,
            validate_payer,
            validate_system,
        },
    },
    solana_program::{
        account_info::AccountInfo,
        entrypoint::ProgramResult,
        program::invoke_signed,
        pubkey::Pubkey,
        rent::Rent,
        system_instruction,
        sysvar::Sysvar,
    },
};

/// Creates a config account that stores the authority pubkey.
/// The authority is the account that will be allowed to modify publisher configs.
/// See `Instruction` for the list of required accounts.
/// The config account must be a non-existing PDA account with an expected seed.
pub fn initialize(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    args: &InitializeArgs,
) -> ProgramResult {
    let mut accounts = accounts.iter();
    let payer = validate_payer(accounts.next())?;
    let config = validate_config(accounts.next(), args.config_bump, program_id, true)?;
    let system = validate_system(accounts.next())?;

    let lamports = (Rent::get()?).minimum_balance(accounts::config::SIZE);

    invoke_signed(
        &system_instruction::create_account(
            payer.key,
            config.key,
            lamports,
            accounts::config::SIZE
                .try_into()
                .expect("unexpected overflow"),
            program_id,
        ),
        &[payer.clone(), config.clone(), system.clone()],
        &[&[CONFIG_SEED.as_bytes(), &[args.config_bump]]],
    )?;

    accounts::config::create(*config.data.borrow_mut(), args.authority)?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use {
        crate::{
            accounts,
            instruction::CONFIG_SEED,
        },
        solana_program::{
            instruction::{
                AccountMeta,
                Instruction,
            },
            pubkey::Pubkey,
            system_program,
        },
        solana_program_test::*,
        solana_sdk::{
            signature::Signer,
            transaction::Transaction,
        },
    };

    #[tokio::test]
    async fn test_initialize() {
        // Initialize ProgramTest
        let id = Pubkey::new_unique();
        let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
            "publishers",
            id,
            processor!(crate::processor::process_instruction),
        )
        .start()
        .await;

        // Setup Accounts
        let (config, config_bump) = Pubkey::find_program_address(&[CONFIG_SEED.as_bytes()], &id);
        let mut authority = [0u8; 32];
        authority[..3].copy_from_slice(&[1, 2, 3]);

        let mut data = vec![
            crate::instruction::Instruction::Initialize as u8,
            config_bump,
        ];
        data.extend_from_slice(&authority);

        // Execute Transaction
        let mut transaction = Transaction::new_with_payer(
            &[Instruction {
                program_id: id,
                data,
                accounts: vec![
                    AccountMeta::new_readonly(payer.pubkey(), true),
                    AccountMeta::new(config, false),
                    AccountMeta::new_readonly(system_program::id(), false),
                ],
            }],
            Some(&payer.pubkey()),
        );
        transaction.sign(&[&payer], recent_blockhash);
        banks_client.process_transaction(transaction).await.unwrap();

        // Validate Outcome
        let config = banks_client.get_account(config).await.unwrap().unwrap();
        assert_eq!(config.lamports, 1141440);
        assert_eq!(config.owner, id);
        let config = accounts::config::read(&config.data).unwrap();
        assert_eq!(config.authority, authority);
    }
}
