use near_api::{
    types::{nft::TokenMetadata, AccountId, NearToken},
    Contract, NetworkConfig, Signer, Tokens,
};
use near_sandbox::{
    config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY},
    GenesisAccount, SandboxConfig,
};
use serde_json::json;

#[tokio::main]
async fn main() -> testresult::TestResult {
    let nft = GenesisAccount::generate_with_name("nft".parse()?);
    let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into();
    let account2 = GenesisAccount::generate_with_name("account2".parse()?);

    let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig {
        additional_accounts: vec![nft.clone(), account2.clone()],
        ..Default::default()
    })
    .await?;
    let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);

    let nft_signer = Signer::from_secret_key(nft.private_key.clone().parse()?)?;
    let account_signer = Signer::from_secret_key(DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse()?)?;

    // Deploying token contract
    Contract::deploy(nft.account_id.clone())
        .use_code(include_bytes!("../resources/nft.wasm").to_vec())
        .with_init_call(
            "new_default_meta",
            json!({
                "owner_id": nft.account_id.to_string(),
            }),
        )?
        .with_signer(nft_signer.clone())
        .send_to(&network)
        .await?
        .assert_success();

    let contract = Contract(nft.account_id.clone());

    // Mint NFT via contract call
    contract
        .call_function(
            "nft_mint",
            json!({
                "token_id": "1",
                "receiver_id": account.to_string(),
                "token_metadata": TokenMetadata {
                    title: Some("My NFT".to_string()),
                    description: Some("My first NFT".to_string()),
                    ..Default::default()
                }
            }),
        )
        .transaction()
        .deposit(NearToken::from_millinear(100))
        .with_signer(nft.account_id.clone(), nft_signer.clone())
        .send_to(&network)
        .await?
        .assert_success();

    // Verifying that account has our nft token
    let tokens = Tokens::account(account.clone())
        .nft_assets(nft.account_id.clone())
        .fetch_from(&network)
        .await?;

    assert_eq!(tokens.data.len(), 1);
    println!(
        "Account has {}",
        tokens.data.first().ok_or("No token found")?.token_id
    );

    Tokens::account(account.clone())
        .send_to(account2.account_id.clone())
        .nft(nft.account_id.clone(), "1".to_string())
        .with_signer(account_signer.clone())
        .send_to(&network)
        .await?
        .assert_success();

    // Verifying that account doesn't have nft anymore
    let tokens = Tokens::account(account.clone())
        .nft_assets(nft.account_id.clone())
        .fetch_from(&network)
        .await?;

    assert!(tokens.data.is_empty());

    let tokens = Tokens::account(account2.account_id.clone())
        .nft_assets(nft.account_id.clone())
        .fetch_from(&network)
        .await?;

    assert_eq!(tokens.data.len(), 1);
    println!(
        "account 2 has {}",
        tokens.data.first().ok_or("No token found")?.token_id
    );

    Ok(())
}
