#![cfg(feature = "test-sbf")]

mod program_test;
use {
    program_test::{TestContext, TokenContext},
    solana_program_test::tokio,
    solana_sdk::{
        instruction::InstructionError, program_option::COption, pubkey::Pubkey, signature::Signer,
        signer::keypair::Keypair, transaction::TransactionError, transport::TransportError,
    },
    spl_token_2022::{
        error::TokenError,
        extension::{default_account_state::DefaultAccountState, BaseStateWithExtensions},
        instruction::AuthorityType,
        state::AccountState,
    },
    spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
    std::convert::TryFrom,
};

#[tokio::test]
async fn success_init_default_acct_state_frozen() {
    let default_account_state = AccountState::Frozen;
    let mut context = TestContext::new().await;
    context
        .init_token_with_freezing_mint(vec![ExtensionInitializationParams::DefaultAccountState {
            state: default_account_state,
        }])
        .await
        .unwrap();
    let TokenContext {
        decimals,
        mint_authority,
        freeze_authority,
        token,
        ..
    } = context.token_context.unwrap();

    let state = token.get_mint_info().await.unwrap();
    assert_eq!(state.base.decimals, decimals);
    assert_eq!(
        state.base.mint_authority,
        COption::Some(mint_authority.pubkey())
    );
    assert_eq!(state.base.supply, 0);
    assert!(state.base.is_initialized);
    assert_eq!(
        state.base.freeze_authority,
        COption::Some(freeze_authority.unwrap().pubkey())
    );
    let extension = state.get_extension::<DefaultAccountState>().unwrap();
    assert_eq!(
        AccountState::try_from(extension.state).unwrap(),
        default_account_state,
    );
}

#[tokio::test]
async fn fail_init_no_authority_default_acct_state_frozen() {
    let default_account_state = AccountState::Frozen;
    let mut context = TestContext::new().await;
    let err = context
        .init_token_with_mint(vec![ExtensionInitializationParams::DefaultAccountState {
            state: default_account_state,
        }])
        .await
        .unwrap_err();

    assert_eq!(
        err,
        TokenClientError::Client(Box::new(TransportError::TransactionError(
            TransactionError::InstructionError(
                2,
                InstructionError::Custom(TokenError::MintCannotFreeze as u32)
            )
        )))
    );
}

#[tokio::test]
async fn success_init_default_acct_state_initialized() {
    let default_account_state = AccountState::Initialized;
    let mut context = TestContext::new().await;
    context
        .init_token_with_mint(vec![ExtensionInitializationParams::DefaultAccountState {
            state: default_account_state,
        }])
        .await
        .unwrap();
    let TokenContext {
        decimals,
        mint_authority,
        token,
        ..
    } = context.token_context.unwrap();

    let state = token.get_mint_info().await.unwrap();
    assert_eq!(state.base.decimals, decimals);
    assert_eq!(
        state.base.mint_authority,
        COption::Some(mint_authority.pubkey())
    );
    assert_eq!(state.base.supply, 0);
    assert!(state.base.is_initialized);
    assert_eq!(state.base.freeze_authority, COption::None);
    let extension = state.get_extension::<DefaultAccountState>().unwrap();
    assert_eq!(
        AccountState::try_from(extension.state).unwrap(),
        default_account_state,
    );
}

#[tokio::test]
async fn success_no_authority_init_default_acct_state_initialized() {
    let default_account_state = AccountState::Initialized;
    let mut context = TestContext::new().await;
    context
        .init_token_with_freezing_mint(vec![ExtensionInitializationParams::DefaultAccountState {
            state: default_account_state,
        }])
        .await
        .unwrap();
    let TokenContext {
        decimals,
        mint_authority,
        freeze_authority,
        token,
        ..
    } = context.token_context.unwrap();

    let state = token.get_mint_info().await.unwrap();
    assert_eq!(state.base.decimals, decimals);
    assert_eq!(
        state.base.mint_authority,
        COption::Some(mint_authority.pubkey())
    );
    assert_eq!(state.base.supply, 0);
    assert!(state.base.is_initialized);
    assert_eq!(
        state.base.freeze_authority,
        COption::Some(freeze_authority.unwrap().pubkey())
    );
    let extension = state.get_extension::<DefaultAccountState>().unwrap();
    assert_eq!(
        AccountState::try_from(extension.state).unwrap(),
        default_account_state,
    );
}

#[tokio::test]
async fn fail_invalid_default_acct_state() {
    let default_account_state = AccountState::Uninitialized;
    let mut context = TestContext::new().await;
    let err = context
        .init_token_with_freezing_mint(vec![ExtensionInitializationParams::DefaultAccountState {
            state: default_account_state,
        }])
        .await
        .unwrap_err();
    assert_eq!(
        err,
        TokenClientError::Client(Box::new(TransportError::TransactionError(
            TransactionError::InstructionError(
                1,
                InstructionError::Custom(TokenError::InvalidState as u32)
            )
        )))
    );
}

#[tokio::test]
async fn end_to_end_default_account_state() {
    let default_account_state = AccountState::Frozen;
    let mut context = TestContext::new().await;
    context
        .init_token_with_freezing_mint(vec![ExtensionInitializationParams::DefaultAccountState {
            state: default_account_state,
        }])
        .await
        .unwrap();
    let TokenContext {
        mint_authority,
        freeze_authority,
        token,
        ..
    } = context.token_context.unwrap();

    let freeze_authority = freeze_authority.unwrap();

    let owner = Pubkey::new_unique();
    let account = Keypair::new();
    token
        .create_auxiliary_token_account(&account, &owner)
        .await
        .unwrap();
    let account = account.pubkey();
    let account_state = token.get_account_info(&account).await.unwrap();
    assert_eq!(account_state.base.state, default_account_state);

    // Invalid default state
    let err = token
        .set_default_account_state(
            &mint_authority.pubkey(),
            &AccountState::Uninitialized,
            &[&mint_authority],
        )
        .await
        .unwrap_err();
    assert_eq!(
        err,
        TokenClientError::Client(Box::new(TransportError::TransactionError(
            TransactionError::InstructionError(
                0,
                InstructionError::Custom(TokenError::InvalidState as u32)
            )
        )))
    );

    token
        .set_default_account_state(
            &freeze_authority.pubkey(),
            &AccountState::Initialized,
            &[&freeze_authority],
        )
        .await
        .unwrap();
    let state = token.get_mint_info().await.unwrap();
    let extension = state.get_extension::<DefaultAccountState>().unwrap();
    assert_eq!(
        AccountState::try_from(extension.state).unwrap(),
        AccountState::Initialized,
    );

    let owner = Pubkey::new_unique();
    let account = Keypair::new();
    token
        .create_auxiliary_token_account(&account, &owner)
        .await
        .unwrap();
    let account = account.pubkey();
    let account_state = token.get_account_info(&account).await.unwrap();
    assert_eq!(account_state.base.state, AccountState::Initialized);

    // adjusting freeze authority adjusts default state authority
    let new_authority = Keypair::new();
    token
        .set_authority(
            token.get_address(),
            &freeze_authority.pubkey(),
            Some(&new_authority.pubkey()),
            AuthorityType::FreezeAccount,
            &[&freeze_authority],
        )
        .await
        .unwrap();

    let err = token
        .set_default_account_state(
            &mint_authority.pubkey(),
            &AccountState::Frozen,
            &[&mint_authority],
        )
        .await
        .unwrap_err();
    assert_eq!(
        err,
        TokenClientError::Client(Box::new(TransportError::TransactionError(
            TransactionError::InstructionError(
                0,
                InstructionError::Custom(TokenError::OwnerMismatch as u32)
            )
        )))
    );

    token
        .set_default_account_state(
            &new_authority.pubkey(),
            &AccountState::Frozen,
            &[&new_authority],
        )
        .await
        .unwrap();
    let state = token.get_mint_info().await.unwrap();
    let extension = state.get_extension::<DefaultAccountState>().unwrap();
    assert_eq!(
        AccountState::try_from(extension.state).unwrap(),
        AccountState::Frozen,
    );

    token
        .set_authority(
            token.get_address(),
            &new_authority.pubkey(),
            None,
            AuthorityType::FreezeAccount,
            &[&new_authority],
        )
        .await
        .unwrap();

    let err = token
        .set_default_account_state(
            &new_authority.pubkey(),
            &AccountState::Initialized,
            &[&new_authority],
        )
        .await
        .unwrap_err();
    assert_eq!(
        err,
        TokenClientError::Client(Box::new(TransportError::TransactionError(
            TransactionError::InstructionError(
                0,
                InstructionError::Custom(TokenError::NoAuthorityExists as u32)
            )
        )))
    );
    let state = token.get_mint_info().await.unwrap();
    let extension = state.get_extension::<DefaultAccountState>().unwrap();
    assert_eq!(
        AccountState::try_from(extension.state).unwrap(),
        AccountState::Frozen,
    );
}
