use {
    super::{SvmContext, AUTHORITY_NAME, VALIDATOR_NAME, WITHDRAWER_NAME},
    solana_account::Account,
    solana_clock::Clock,
    solana_pubkey::Pubkey as SolanaPubkey,
    solana_signer::Signer,
    solana_vote_interface::state::{VoteInit, VoteStateV4, VoteStateVersions},
    spherenet_validator_whitelist_interface::{
        account::ID,
        program::ID as WHITELIST_PROGRAM_ID,
        state::{
            account::ValidatorWhitelistAccount, load_mut,
            whitelist_entry::ValidatorWhitelistEntry, AccountState, SizeOf,
        },
        vote_program::ID as VOTE_PROGRAM_ID,
    },
};

pub const VOTE_ACCOUNT_1_NAME: &str = "vote_account_1";
pub const VOTE_ACCOUNT_2_NAME: &str = "vote_account_2";
pub const VALIDATOR_WHITELIST_ACCOUNT_NAME: &str = "validator_whitelist";
pub const WHITELIST_ENTRY_1_NAME: &str = "whitelist_entry_1";
pub const WHITELIST_ENTRY_2_NAME: &str = "whitelist_entry_2";

pub fn setup_vote_account(context: &mut SvmContext, vote_account_name: &str) {
    let validator_pubkey = context.get_actor(VALIDATOR_NAME).pubkey();
    let withdrawer_pubkey = context.get_actor(WITHDRAWER_NAME).pubkey();

    let vote_account_pubkey = SolanaPubkey::new_unique();

    let data_len = VoteStateV4::size_of();
    let rent = context.svm.minimum_balance_for_rent_exemption(data_len);

    let mut vote_account =
        Account::new(rent, data_len, &SolanaPubkey::from(VOTE_PROGRAM_ID));
    let vote_init = VoteInit {
        node_pubkey: validator_pubkey,
        authorized_voter: validator_pubkey,
        authorized_withdrawer: withdrawer_pubkey,
        ..VoteInit::default()
    };

    let vote_state = VoteStateVersions::new_v4(VoteStateV4::new(
        &validator_pubkey,
        &vote_init,
        &Clock::default(),
    ));

    let mut data = vec![0; data_len];
    VoteStateV4::serialize(&vote_state, &mut data).unwrap();
    vote_account.data = data;

    context
        .svm
        .set_account(vote_account_pubkey, vote_account)
        .unwrap();
    context.add_account(vote_account_name, vote_account_pubkey);
}

pub fn setup_validator_whitelist_account(context: &mut SvmContext) {
    let whitelist_account_data = ValidatorWhitelistAccount {
        authority: context.get_actor(AUTHORITY_NAME).pubkey().to_bytes(),
        pending_authority: SolanaPubkey::default().to_bytes(),
        validator_amount: 0u32.to_le_bytes(),
        state: AccountState::Initialized as u8,
    };

    let data_len = ValidatorWhitelistAccount::SIZE_OF;
    let mut account = Account::new(
        context.svm.minimum_balance_for_rent_exemption(data_len),
        data_len,
        &SolanaPubkey::from(WHITELIST_PROGRAM_ID),
    );
    account.data = whitelist_account_data.pack().to_vec();

    context.svm.set_account(ID.into(), account).unwrap();
    context.add_account(VALIDATOR_WHITELIST_ACCOUNT_NAME, ID.into());
}

pub fn setup_whitelist_entry(
    context: &mut SvmContext,
    pubkey: SolanaPubkey,
    name: &str,
) {
    let whitelist_entry_data = ValidatorWhitelistEntry {
        pubkey: pubkey.to_bytes(),
        start_epoch: 1u64.to_le_bytes(),
        end_epoch: 100u64.to_le_bytes(),
        state: AccountState::Initialized as u8,
    };
    let data_len = ValidatorWhitelistEntry::SIZE_OF;
    let mut account = Account::new(
        context.svm.minimum_balance_for_rent_exemption(data_len),
        data_len,
        &SolanaPubkey::from(WHITELIST_PROGRAM_ID),
    );

    account.data = whitelist_entry_data.pack().to_vec();
    let (whitelist_entry_pubkey, _) = SolanaPubkey::find_program_address(
        &[&pubkey.to_bytes()],
        &SolanaPubkey::from(WHITELIST_PROGRAM_ID),
    );
    context
        .svm
        .set_account(whitelist_entry_pubkey, account)
        .unwrap();
    context.add_account(name, whitelist_entry_pubkey);

    let whitelist_account_pubkey =
        context.get_account_pubkey(VALIDATOR_WHITELIST_ACCOUNT_NAME);
    let mut whitelist_account =
        context.svm.get_account(&whitelist_account_pubkey).unwrap();

    let whitelist_account_data =
        load_mut::<ValidatorWhitelistAccount>(&mut whitelist_account.data)
            .unwrap();

    whitelist_account_data.validator_amount =
        (u32::from_le_bytes(whitelist_account_data.validator_amount) + 1)
            .to_le_bytes();

    whitelist_account.data = whitelist_account_data.pack().to_vec();

    context
        .svm
        .set_account(*whitelist_account_pubkey, whitelist_account)
        .unwrap();
}
