#![cfg(feature = "serde")]

use {
    base64::{engine::general_purpose::STANDARD, Engine},
    solana_address::Address,
    solana_nullable::MaybeNull,
    solana_program_option::COption,
    solana_zk_sdk_pod::encryption::elgamal::PodElGamalPubkey,
    spl_token_2022_interface::{extension::confidential_transfer, instruction},
    std::str::FromStr,
};

#[test]
fn serde_instruction_coption_pubkey() {
    let inst = instruction::TokenInstruction::InitializeMint2 {
        decimals: 0,
        mint_authority: Address::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap(),
        freeze_authority: COption::Some(
            Address::from_str("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh").unwrap(),
        ),
    };

    let serialized = serde_json::to_string(&inst).unwrap();
    assert_eq!(&serialized, "{\"initializeMint2\":{\"decimals\":0,\"mintAuthority\":\"4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM\",\"freezeAuthority\":\"8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh\"}}");

    serde_json::from_str::<instruction::TokenInstruction>(&serialized).unwrap();
}

#[test]
fn serde_instruction_coption_pubkey_with_none() {
    let inst = instruction::TokenInstruction::InitializeMintCloseAuthority {
        close_authority: COption::None,
    };

    let serialized = serde_json::to_string(&inst).unwrap();
    assert_eq!(
        &serialized,
        "{\"initializeMintCloseAuthority\":{\"closeAuthority\":null}}"
    );

    serde_json::from_str::<instruction::TokenInstruction>(&serialized).unwrap();
}

#[test]
fn serde_instruction_coption_u64() {
    let inst = instruction::TokenInstruction::UnwrapLamports {
        amount: COption::Some(1),
    };

    let serialized = serde_json::to_string(&inst).unwrap();
    assert_eq!(&serialized, "{\"unwrapLamports\":{\"amount\":1}}");

    serde_json::from_str::<instruction::TokenInstruction>(&serialized).unwrap();
}

#[test]
fn serde_instruction_coption_u64_with_none() {
    let inst = instruction::TokenInstruction::UnwrapLamports {
        amount: COption::None,
    };

    let serialized = serde_json::to_string(&inst).unwrap();
    assert_eq!(&serialized, "{\"unwrapLamports\":{\"amount\":null}}");

    serde_json::from_str::<instruction::TokenInstruction>(&serialized).unwrap();
}

#[test]
fn serde_instruction_batch() {
    let create_account_instr_data = instruction::TokenInstruction::InitializeAccount {}.pack();
    let mint_to_instr_data = instruction::TokenInstruction::MintTo { amount: 100 }.pack();
    let transfer_instr_data = instruction::TokenInstruction::TransferChecked {
        amount: 500,
        decimals: 9,
    }
    .pack();

    let mut batch_data = Vec::new();
    batch_data.push(4);
    batch_data.push(create_account_instr_data.len() as u8);
    batch_data.extend_from_slice(&create_account_instr_data);
    batch_data.push(3);
    batch_data.push(mint_to_instr_data.len() as u8);
    batch_data.extend_from_slice(&mint_to_instr_data);
    batch_data.push(4);
    batch_data.push(transfer_instr_data.len() as u8);
    batch_data.extend_from_slice(&transfer_instr_data);

    let batch_instr = instruction::TokenInstruction::Batch { data: batch_data };

    let serialized = serde_json::to_string(&batch_instr).unwrap();
    assert_eq!(
        &serialized,
        "{\"batch\":{\"data\":[{\"accountCount\":4,\"dataLength\":1,\"instruction\":\"initializeAccount\"},{\"accountCount\":3,\"dataLength\":9,\"instruction\":{\"mintTo\":{\"amount\":100}}},{\"accountCount\":4,\"dataLength\":10,\"instruction\":{\"transferChecked\":{\"amount\":500,\"decimals\":9}}}]}}"
    );

    serde_json::from_str::<instruction::TokenInstruction>(&serialized).unwrap();
}

#[test]
fn serde_instruction_optional_nonzero_pubkeys_podbool() {
    // tests serde of ix containing MaybeNull<Address>, Bool and
    // MaybeNull<PodElGamalPubkey>
    let authority_option: Option<Address> =
        Some(Address::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap());
    let authority: MaybeNull<Address> = authority_option.try_into().unwrap();

    let pubkey_string = STANDARD.encode([
        162, 23, 108, 36, 130, 143, 18, 219, 196, 134, 242, 145, 179, 49, 229, 193, 74, 64, 3, 158,
        68, 235, 124, 88, 247, 144, 164, 254, 228, 12, 173, 85,
    ]);
    let elgamal_pubkey_pod_option = Some(PodElGamalPubkey::from_str(&pubkey_string).unwrap());

    let auditor_elgamal_pubkey: MaybeNull<PodElGamalPubkey> =
        elgamal_pubkey_pod_option.try_into().unwrap();

    let inst = confidential_transfer::instruction::InitializeMintData {
        authority,
        auto_approve_new_accounts: false.into(),
        auditor_elgamal_pubkey,
    };

    let serialized = serde_json::to_string(&inst).unwrap();
    let serialized_expected = &"{\"authority\":\"4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM\",\"autoApproveNewAccounts\":false,\"auditorElgamalPubkey\":\"ohdsJIKPEtvEhvKRszHlwUpAA55E63xY95Ck/uQMrVU=\"}";
    assert_eq!(&serialized, serialized_expected);

    let deserialized =
        serde_json::from_str::<confidential_transfer::instruction::InitializeMintData>(&serialized)
            .unwrap();
    assert_eq!(inst, deserialized);
}

#[test]
fn serde_instruction_optional_nonzero_pubkeys_podbool_with_none() {
    // tests serde of ix containing MaybeNull<Address>, Bool and
    // MaybeNull<PodElGamalPubkey> with null values
    let authority: MaybeNull<Address> = None.try_into().unwrap();

    let auditor_elgamal_pubkey: MaybeNull<PodElGamalPubkey> =
        MaybeNull::<PodElGamalPubkey>::default();

    let inst = confidential_transfer::instruction::InitializeMintData {
        authority,
        auto_approve_new_accounts: false.into(),
        auditor_elgamal_pubkey,
    };

    let serialized = serde_json::to_string(&inst).unwrap();
    let serialized_expected =
        &"{\"authority\":null,\"autoApproveNewAccounts\":false,\"auditorElgamalPubkey\":null}";
    assert_eq!(&serialized, serialized_expected);

    let deserialized =
        serde_json::from_str::<confidential_transfer::instruction::InitializeMintData>(
            serialized_expected,
        )
        .unwrap();
    assert_eq!(inst, deserialized);
}

#[test]
fn serde_instruction_decryptable_balance_podu64() {
    let ciphertext_string = STANDARD.encode([
        56, 22, 102, 48, 112, 106, 58, 25, 25, 244, 194, 217, 73, 137, 73, 38, 24, 26, 36, 25, 235,
        234, 68, 181, 11, 82, 170, 163, 89, 205, 113, 160, 55, 16, 35, 151,
    ]);
    let decryptable_zero_balance = FromStr::from_str(&ciphertext_string).unwrap();

    let inst = confidential_transfer::instruction::ConfigureAccountInstructionData {
        decryptable_zero_balance,
        maximum_pending_balance_credit_counter: 1099.into(),
        proof_instruction_offset: 100,
    };

    let serialized = serde_json::to_string(&inst).unwrap();
    let serialized_expected = &"{\"decryptableZeroBalance\":\"OBZmMHBqOhkZ9MLZSYlJJhgaJBnr6kS1C1Kqo1nNcaA3ECOX\",\"maximumPendingBalanceCreditCounter\":1099,\"proofInstructionOffset\":100}";
    assert_eq!(&serialized, serialized_expected);

    let deserialized = serde_json::from_str::<
        confidential_transfer::instruction::ConfigureAccountInstructionData,
    >(serialized_expected)
    .unwrap();
    assert_eq!(inst, deserialized);
}

#[test]
fn serde_instruction_elgamal_pubkey() {
    use spl_token_2022_interface::extension::confidential_transfer_fee::instruction::InitializeConfidentialTransferFeeConfigData;

    let pubkey_string = STANDARD.encode([
        162, 23, 108, 36, 130, 143, 18, 219, 196, 134, 242, 145, 179, 49, 229, 193, 74, 64, 3, 158,
        68, 235, 124, 88, 247, 144, 164, 254, 228, 12, 173, 85,
    ]);
    let withdraw_withheld_authority_elgamal_pubkey = FromStr::from_str(&pubkey_string).unwrap();

    let inst = InitializeConfidentialTransferFeeConfigData {
        authority: MaybeNull::<Address>::default(),
        withdraw_withheld_authority_elgamal_pubkey,
    };

    let serialized = serde_json::to_string(&inst).unwrap();
    let serialized_expected = "{\"authority\":null,\"withdrawWithheldAuthorityElgamalPubkey\":\"ohdsJIKPEtvEhvKRszHlwUpAA55E63xY95Ck/uQMrVU=\"}";
    assert_eq!(&serialized, serialized_expected);

    let deserialized =
        serde_json::from_str::<InitializeConfidentialTransferFeeConfigData>(serialized_expected)
            .unwrap();
    assert_eq!(inst, deserialized);
}

#[test]
fn serde_instruction_basis_points() {
    use spl_token_2022_interface::extension::interest_bearing_mint::instruction::InitializeInstructionData;

    let inst = InitializeInstructionData {
        rate_authority: MaybeNull::<Address>::default(),
        rate: 127.into(),
    };

    let serialized = serde_json::to_string(&inst).unwrap();
    let serialized_expected = "{\"rateAuthority\":null,\"rate\":127}";
    assert_eq!(&serialized, serialized_expected);

    serde_json::from_str::<InitializeInstructionData>(serialized_expected).unwrap();
}
