use {
    solana_account_info::AccountInfo,
    solana_clock::Slot,
    solana_instruction::{AccountMeta, Instruction},
    solana_msg::msg,
    solana_program_error::ProgramResult,
    solana_program_test::{processor, ProgramTest, ProgramTestContext},
    solana_pubkey::Pubkey,
    solana_signer::Signer,
    solana_sysvar::{
        last_restart_slot, last_restart_slot::LastRestartSlot, Sysvar, SysvarSerialize,
    },
    solana_transaction::Transaction,
};

// program to check both syscall and sysvar
fn sysvar_last_restart_slot_process_instruction(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    input: &[u8],
) -> ProgramResult {
    msg!("sysvar_last_restart_slot");
    assert_eq!(input.len(), 8);
    let expected_last_hardfork_slot = u64::from_le_bytes(input[0..8].try_into().unwrap());

    let last_restart_slot = LastRestartSlot::get();
    msg!("last restart slot: {:?}", last_restart_slot);
    assert_eq!(
        last_restart_slot,
        Ok(LastRestartSlot {
            last_restart_slot: expected_last_hardfork_slot
        })
    );

    let last_restart_slot_account = &accounts[0];
    let slot_via_account = LastRestartSlot::from_account_info(last_restart_slot_account)?;
    msg!("slot via account: {:?}", slot_via_account);

    assert_eq!(
        slot_via_account,
        LastRestartSlot {
            last_restart_slot: expected_last_hardfork_slot
        }
    );

    Ok(())
}

async fn check_with_program(
    context: &mut ProgramTestContext,
    program_id: Pubkey,
    expected_last_restart_slot: u64,
) {
    let instructions = vec![Instruction::new_with_bincode(
        program_id,
        &expected_last_restart_slot.to_le_bytes(),
        vec![AccountMeta::new(last_restart_slot::id(), false)],
    )];

    let transaction = Transaction::new_signed_with_payer(
        &instructions,
        Some(&context.payer.pubkey()),
        &[&context.payer],
        context.last_blockhash,
    );

    context
        .banks_client
        .process_transaction(transaction)
        .await
        .unwrap();
}

#[tokio::test]
async fn get_sysvar_last_restart_slot() {
    let program_id = Pubkey::new_unique();
    let program_test = ProgramTest::new(
        "sysvar_last_restart_slot_process",
        program_id,
        processor!(sysvar_last_restart_slot_process_instruction),
    );

    let mut context = program_test.start_with_context().await;

    check_with_program(&mut context, program_id, 0).await;
    context.warp_to_slot(40).unwrap();
    context.register_hard_fork(41 as Slot);
    check_with_program(&mut context, program_id, 0).await;
    context.warp_to_slot(41).unwrap();
    check_with_program(&mut context, program_id, 41).await;
    // check for value lower than previous hardfork
    context.register_hard_fork(40 as Slot);
    context.warp_to_slot(45).unwrap();
    check_with_program(&mut context, program_id, 41).await;
    context.register_hard_fork(47 as Slot);
    context.register_hard_fork(48 as Slot);
    context.warp_to_slot(46).unwrap();
    check_with_program(&mut context, program_id, 41).await;
    context.register_hard_fork(50 as Slot);
    context.warp_to_slot(48).unwrap();
    check_with_program(&mut context, program_id, 48).await;
    context.warp_to_slot(50).unwrap();
    check_with_program(&mut context, program_id, 50).await;
}
