mod setup;

use {
    crate::setup::*,
    solana_pubkey::Pubkey as SolanaPubkey,
    solana_signer::Signer,
    solana_system_interface::program::ID as SYSTEM_PROGRAM_ID,
    solana_transaction::Transaction,
    spherenet_validator_whitelist_interface::{
        program::ID as WHITELIST_PROGRAM_ID,
        state::{
            account::ValidatorWhitelistAccount, load_unchecked,
            whitelist_entry::ValidatorWhitelistEntry, Initializable,
        },
    },
};

#[test]
fn accept_authority_transfer() {
    let mut context = setup_context_with_authority_transfer_initiated();

    let payer = context.get_actor(PAYER_NAME);
    let new_authority = context.get_actor(NEW_AUTHORITY_NAME);
    let new_authority_pubkey = new_authority.pubkey();
    let whitelist_account_pubkey =
        *context.get_account_pubkey(VALIDATOR_WHITELIST_ACCOUNT_NAME);

    let tx = Transaction::new_signed_with_payer(
        &[
            spherenet_validator_whitelist_client::instructions::AcceptAuthorityTransferBuilder::new()
                .payer(payer.pubkey())
                .new_whitelist_authority(new_authority_pubkey)
                .validator_whitelist(whitelist_account_pubkey)
                .instruction(),
        ],
        Some(&payer.pubkey()),
        &[&payer, &new_authority],
        context.svm.latest_blockhash(),
    );

    context.svm.send_transaction(tx).unwrap();

    let raw_account =
        context.svm.get_account(&whitelist_account_pubkey).unwrap();
    let mut raw_data = raw_account.data;

    let whitelist_account_data =
        load_unchecked::<ValidatorWhitelistAccount>(&mut raw_data).unwrap();

    assert_eq!(
        whitelist_account_data.pending_authority,
        SolanaPubkey::default().to_bytes()
    );
    assert_eq!(
        whitelist_account_data.authority,
        new_authority_pubkey.to_bytes()
    );
}

#[test]
fn add_to_whitelist() {
    let mut context = setup_context();

    let payer = context.get_actor(PAYER_NAME);
    let authority = context.get_actor(AUTHORITY_NAME);
    let vote_account_2_pubkey =
        *context.get_account_pubkey(VOTE_ACCOUNT_2_NAME);
    let whitelist_account_pubkey =
        *context.get_account_pubkey(VALIDATOR_WHITELIST_ACCOUNT_NAME);
    let whitelist_entry_pubkey = SolanaPubkey::find_program_address(
        &[&vote_account_2_pubkey.to_bytes()],
        &SolanaPubkey::from(WHITELIST_PROGRAM_ID),
    )
    .0;

    let tx = Transaction::new_signed_with_payer(
        &[
            spherenet_validator_whitelist_client::instructions::AddToWhitelistBuilder::new()
                .payer(payer.pubkey())
                .whitelist_authority(authority.pubkey())
                .validator_whitelist(whitelist_account_pubkey)
                .whitelist_entry(whitelist_entry_pubkey)
                .validator_vote_account(vote_account_2_pubkey)
                .system_program(SYSTEM_PROGRAM_ID)
                .start_epoch(1u64.to_le_bytes())
                .end_epoch(3u64.to_le_bytes())
                .instruction(),
        ],
        Some(&payer.pubkey()),
        &[&payer, &authority],
        context.svm.latest_blockhash(),
    );

    context.svm.send_transaction(tx).unwrap();

    let raw_account = context.svm.get_account(&whitelist_entry_pubkey).unwrap();
    let mut raw_data = raw_account.data;

    let whitelist_entry_data =
        load_unchecked::<ValidatorWhitelistEntry>(&mut raw_data).unwrap();

    assert_eq!(whitelist_entry_data.start_epoch, 1u64.to_le_bytes());
    assert_eq!(whitelist_entry_data.end_epoch, 3u64.to_le_bytes());
    assert_eq!(whitelist_entry_data.is_initialized(), true);
    assert_eq!(raw_account.owner, SolanaPubkey::from(WHITELIST_PROGRAM_ID));

    let mut raw_account_data = context
        .svm
        .get_account(&whitelist_account_pubkey)
        .unwrap()
        .data;
    let whitelist_account_data =
        load_unchecked::<ValidatorWhitelistAccount>(&mut raw_account_data)
            .unwrap();

    assert_eq!(
        u32::from_le_bytes(whitelist_account_data.validator_amount),
        2u32
    );
}

#[test]
fn cancel_authority_transfer() {
    let mut context = setup_context_with_authority_transfer_initiated();

    let payer = context.get_actor(PAYER_NAME);
    let authority = context.get_actor(AUTHORITY_NAME);
    let authority_pubkey = authority.pubkey();
    let whitelist_account_pubkey =
        *context.get_account_pubkey(VALIDATOR_WHITELIST_ACCOUNT_NAME);

    let tx = Transaction::new_signed_with_payer(
        &[
            spherenet_validator_whitelist_client::instructions::CancelAuthorityTransferBuilder::new()
                .payer(payer.pubkey())
                .whitelist_authority(authority_pubkey)
                .validator_whitelist(whitelist_account_pubkey)
                .instruction(),
        ],
        Some(&payer.pubkey()),
        &[&payer, &authority],
        context.svm.latest_blockhash(),
    );

    context.svm.send_transaction(tx).unwrap();
    let raw_account =
        context.svm.get_account(&whitelist_account_pubkey).unwrap();
    let mut raw_data = raw_account.data;

    let whitelist_account_data =
        load_unchecked::<ValidatorWhitelistAccount>(&mut raw_data).unwrap();

    assert_eq!(
        whitelist_account_data.pending_authority,
        SolanaPubkey::default().to_bytes()
    );
    assert_eq!(
        whitelist_account_data.authority,
        authority_pubkey.to_bytes()
    );
}

#[test]
fn initiate_authority_transfer() {
    let mut context = setup_context();

    let payer = context.get_actor(PAYER_NAME);
    let authority = context.get_actor(AUTHORITY_NAME);
    let authority_pubkey = authority.pubkey();
    let whitelist_account_pubkey =
        *context.get_account_pubkey(VALIDATOR_WHITELIST_ACCOUNT_NAME);
    let new_authority = context.get_actor(NEW_AUTHORITY_NAME).pubkey();

    let tx = Transaction::new_signed_with_payer(
        &[
            spherenet_validator_whitelist_client::instructions::InitiateAuthorityTransferBuilder::new()
                .payer(payer.pubkey())
                .whitelist_authority(authority_pubkey)
                .validator_whitelist(whitelist_account_pubkey)
                .new_authority(new_authority)
                .instruction(),
        ],
        Some(&payer.pubkey()),
        &[&payer, &authority],
        context.svm.latest_blockhash(),
    );

    context.svm.send_transaction(tx).unwrap();
    let raw_account =
        context.svm.get_account(&whitelist_account_pubkey).unwrap();
    let mut raw_data = raw_account.data;

    let whitelist_account_data =
        load_unchecked::<ValidatorWhitelistAccount>(&mut raw_data).unwrap();

    assert_eq!(
        whitelist_account_data.authority,
        authority_pubkey.to_bytes()
    );
    assert_eq!(
        whitelist_account_data.pending_authority,
        new_authority.to_bytes()
    );
}

#[test]
fn remove_from_whitelist() {
    let mut context = setup_context_with_2_whitelist_entries();

    let payer = context.get_actor(PAYER_NAME);
    let authority = context.get_actor(AUTHORITY_NAME);
    let vote_account_2_pubkey =
        *context.get_account_pubkey(VOTE_ACCOUNT_2_NAME);
    let whitelist_account_pubkey =
        *context.get_account_pubkey(VALIDATOR_WHITELIST_ACCOUNT_NAME);
    let whitelist_entry_pubkey =
        *context.get_account_pubkey(WHITELIST_ENTRY_2_NAME);

    let tx = Transaction::new_signed_with_payer(
        &[
            spherenet_validator_whitelist_client::instructions::RemoveFromWhitelistBuilder::new()
                .payer(payer.pubkey())
                .whitelist_authority(authority.pubkey())
                .validator_whitelist(whitelist_account_pubkey)
                .whitelist_entry(whitelist_entry_pubkey)
                .vote_account_pubkey(vote_account_2_pubkey)
                .instruction(),
        ],
        Some(&payer.pubkey()),
        &[&payer, &authority],
        context.svm.latest_blockhash(),
    );

    context.svm.send_transaction(tx).unwrap();

    // Account should be closed (no longer exists)
    assert!(context.svm.get_account(&whitelist_entry_pubkey).is_none());

    let mut raw_account_data = context
        .svm
        .get_account(&whitelist_account_pubkey)
        .unwrap()
        .data;
    let whitelist_account_data =
        load_unchecked::<ValidatorWhitelistAccount>(&mut raw_account_data)
            .unwrap();

    assert_eq!(
        u32::from_le_bytes(whitelist_account_data.validator_amount),
        1u32
    );
}

#[test]
fn update_end_epoch() {
    let mut context = setup_context();

    let payer = context.get_actor(PAYER_NAME);
    let authority = context.get_actor(AUTHORITY_NAME);
    let vote_account_2_pubkey =
        *context.get_account_pubkey(VOTE_ACCOUNT_1_NAME);
    let whitelist_account_pubkey =
        *context.get_account_pubkey(VALIDATOR_WHITELIST_ACCOUNT_NAME);
    let whitelist_entry_pubkey =
        *context.get_account_pubkey(WHITELIST_ENTRY_1_NAME);

    let tx = Transaction::new_signed_with_payer(
        &[
            spherenet_validator_whitelist_client::instructions::UpdateEndEpochBuilder::new()
                .payer(payer.pubkey())
                .whitelist_authority(authority.pubkey())
                .validator_whitelist(whitelist_account_pubkey)
                .whitelist_entry(whitelist_entry_pubkey)
                .vote_account_pubkey(vote_account_2_pubkey)
                .new_end_epoch(30u64.to_le_bytes())
                .instruction(),
        ],
        Some(&payer.pubkey()),
        &[&payer, &authority],
        context.svm.latest_blockhash(),
    );

    context.svm.send_transaction(tx).unwrap();

    let raw_account = context.svm.get_account(&whitelist_entry_pubkey).unwrap();
    let mut raw_data = raw_account.data;

    let whitelist_entry_data =
        load_unchecked::<ValidatorWhitelistEntry>(&mut raw_data).unwrap();

    assert_eq!(whitelist_entry_data.start_epoch, 1u64.to_le_bytes());
    assert_eq!(whitelist_entry_data.end_epoch, 30u64.to_le_bytes());
}

#[test]
fn update_start_epoch() {
    let mut context = setup_context();

    let payer = context.get_actor(PAYER_NAME);
    let authority = context.get_actor(AUTHORITY_NAME);
    let vote_account_2_pubkey =
        *context.get_account_pubkey(VOTE_ACCOUNT_1_NAME);
    let whitelist_account_pubkey =
        *context.get_account_pubkey(VALIDATOR_WHITELIST_ACCOUNT_NAME);
    let whitelist_entry_pubkey =
        *context.get_account_pubkey(WHITELIST_ENTRY_1_NAME);

    let tx = Transaction::new_signed_with_payer(
        &[
            spherenet_validator_whitelist_client::instructions::UpdateStartEpochBuilder::new()
                .payer(payer.pubkey())
                .whitelist_authority(authority.pubkey())
                .validator_whitelist(whitelist_account_pubkey)
                .whitelist_entry(whitelist_entry_pubkey)
                .vote_account_pubkey(vote_account_2_pubkey)
                .new_start_epoch(30u64.to_le_bytes())
                .instruction(),
        ],
        Some(&payer.pubkey()),
        &[&payer, &authority],
        context.svm.latest_blockhash(),
    );

    context.svm.send_transaction(tx).unwrap();

    let raw_account = context.svm.get_account(&whitelist_entry_pubkey).unwrap();
    let mut raw_data = raw_account.data;

    let whitelist_entry_data =
        load_unchecked::<ValidatorWhitelistEntry>(&mut raw_data).unwrap();

    assert_eq!(whitelist_entry_data.start_epoch, 30u64.to_le_bytes());
    assert_eq!(whitelist_entry_data.end_epoch, 100u64.to_le_bytes());
}
