use {
    log::*,
    rand::{thread_rng, Rng},
    rayon::prelude::*,
    solana_runtime::{
        accounts_db::{AccountsDb, LoadHint},
        ancestors::Ancestors,
    },
    solana_sdk::{
        account::{AccountSharedData, ReadableAccount, WritableAccount},
        clock::Slot,
        genesis_config::ClusterType,
        pubkey::Pubkey,
    },
    std::{
        collections::HashSet,
        sync::{
            atomic::{AtomicBool, Ordering},
            Arc,
        },
        time::Instant,
    },
};

#[test]
fn test_shrink_and_clean() {
    solana_logger::setup();

    // repeat the whole test scenario
    for _ in 0..5 {
        let accounts = Arc::new(AccountsDb::new_single_for_tests());
        let accounts_for_shrink = accounts.clone();

        // spawn the slot shrinking background thread
        let exit = Arc::new(AtomicBool::default());
        let exit_for_shrink = exit.clone();
        let shrink_thread = std::thread::spawn(move || loop {
            if exit_for_shrink.load(Ordering::Relaxed) {
                break;
            }
            accounts_for_shrink.process_stale_slot_v1();
        });

        let mut alive_accounts = vec![];
        let owner = Pubkey::default();

        // populate the AccountsDb with plenty of food for slot shrinking
        // also this simulates realistic some heavy spike account updates in the wild
        for current_slot in 0..100 {
            while alive_accounts.len() <= 10 {
                alive_accounts.push((
                    solana_sdk::pubkey::new_rand(),
                    AccountSharedData::new(thread_rng().gen_range(0, 50), 0, &owner),
                ));
            }

            alive_accounts.retain(|(_pubkey, account)| account.lamports() >= 1);

            for (pubkey, account) in alive_accounts.iter_mut() {
                account.checked_sub_lamports(1).unwrap();
                accounts.store_uncached(current_slot, &[(pubkey, account)]);
            }
            accounts.add_root(current_slot);
        }

        // let's dance.
        for _ in 0..10 {
            accounts.clean_accounts(None, false, None);
            std::thread::sleep(std::time::Duration::from_millis(100));
        }

        // cleanup
        exit.store(true, Ordering::Relaxed);
        shrink_thread.join().unwrap();
    }
}

#[test]
fn test_bad_bank_hash() {
    solana_logger::setup();
    use solana_sdk::signature::{Keypair, Signer};
    let db = AccountsDb::new_for_tests(Vec::new(), &ClusterType::Development);

    let some_slot: Slot = 0;
    let ancestors = Ancestors::from(vec![some_slot]);

    let max_accounts = 200;
    let mut accounts_keys: Vec<_> = (0..max_accounts)
        .into_par_iter()
        .map(|_| {
            let key = Keypair::new().pubkey();
            let lamports = thread_rng().gen_range(0, 100);
            let some_data_len = thread_rng().gen_range(0, 1000);
            let account = AccountSharedData::new(lamports, some_data_len, &key);
            (key, account)
        })
        .collect();

    let mut existing = HashSet::new();
    let mut last_print = Instant::now();
    for i in 0..5_000 {
        if last_print.elapsed().as_millis() > 5000 {
            info!("i: {}", i);
            last_print = Instant::now();
        }
        let num_accounts = thread_rng().gen_range(0, 100);
        (0..num_accounts).into_iter().for_each(|_| {
            let mut idx;
            loop {
                idx = thread_rng().gen_range(0, max_accounts);
                if existing.contains(&idx) {
                    continue;
                }
                existing.insert(idx);
                break;
            }
            accounts_keys[idx]
                .1
                .set_lamports(thread_rng().gen_range(0, 1000));
        });

        let account_refs: Vec<_> = existing
            .iter()
            .map(|idx| (&accounts_keys[*idx].0, &accounts_keys[*idx].1))
            .collect();
        db.store_uncached(some_slot, &account_refs);

        for (key, account) in &account_refs {
            assert_eq!(
                db.load_account_hash(&ancestors, key, None, LoadHint::Unspecified)
                    .unwrap(),
                AccountsDb::hash_account(some_slot, *account, key)
            );
        }
        existing.clear();
    }
}
