Batch

use std::ops::Mul;

use avail_rust::prelude::*;

use avail::{
	runtime_types::{da_runtime::RuntimeCall, pallet_balances::pallet::Call::transfer_keep_alive as TransferKeepAlive},
	utility::events as UtilityEvents,
};

pub async fn run() -> Result<(), ClientError> {
	let sdk = SDK::new(SDK::local_endpoint()).await?;

	let account = account::alice();

	let value_1 = SDK::one_avail();
	let value_2 = SDK::one_avail();
	let dest_bob = account::account_id_from_str("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")?;
	let dest_charlie = account::account_id_from_str("5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y")?;

	let call_1 = TransferKeepAlive {
		dest: dest_bob.clone().into(),
		value: value_1,
	};
	let call_2 = TransferKeepAlive {
		dest: dest_charlie.clone().into(),
		value: value_2,
	};
	let mut calls = Vec::new();
	calls.push(RuntimeCall::Balances(call_1).into());
	calls.push(RuntimeCall::Balances(call_2).into());

	//
	// Happy Path
	//

	// Batch Call
	//
	// This will return `Ok` in all circumstances. To determine the success of the batch, an
	// event is deposited. If a call failed and the batch was interrupted, then the
	// `BatchInterrupted` event is deposited, along with the number of successful calls made
	// and the error of the failed call. If all were successful, then the `BatchCompleted`
	// event is deposited.
	let payload = avail::tx().utility().batch(calls.clone());
	let tx = Transaction::new(sdk.client.clone(), payload);
	let res = tx.execute_and_watch_inclusion(&account, Options::new()).await?;
	assert_eq!(res.is_successful(), Some(true), "Transactions must be successful");

	let events = res.events.unwrap();
	assert_eq!(events.has::<UtilityEvents::BatchCompleted>(), Some(true), "");
	assert_eq!(events.count::<UtilityEvents::ItemCompleted>(), 2, "");

	println!("-- Batch Call Done --");

	// Batch All Call
	//
	// Send a batch of dispatch calls and atomically execute them.
	// The whole transaction will rollback and fail if any of the calls failed.
	let payload = avail::tx().utility().batch_all(calls.clone());
	let tx = Transaction::new(sdk.client.clone(), payload);
	let res = tx.execute_and_watch_inclusion(&account, Options::new()).await?;
	assert_eq!(res.is_successful(), Some(true), "Transactions must be successful");

	let events = res.events.unwrap();
	assert_eq!(events.has::<UtilityEvents::BatchCompleted>(), Some(true), "");
	assert_eq!(events.count::<UtilityEvents::ItemCompleted>(), 2, "");

	println!("-- Batch All Call Done --");

	// Force Batch Call
	//
	// Send a batch of dispatch calls.
	// Unlike `batch`, it allows errors and won't interrupt.

	let payload = avail::tx().utility().force_batch(calls.clone());
	let tx = Transaction::new(sdk.client.clone(), payload);
	let res = tx.execute_and_watch_inclusion(&account, Options::new()).await?;
	assert_eq!(res.is_successful(), Some(true), "Transactions must be successful");

	let events = res.events.unwrap();
	assert_eq!(events.has::<UtilityEvents::BatchCompleted>(), Some(true), "");
	assert_eq!(events.count::<UtilityEvents::ItemCompleted>(), 2, "");

	println!("-- Force Batch Call Done --");

	//
	//	Things differ when we introduce a call that will fail
	//

	let call_3 = TransferKeepAlive {
		dest: dest_charlie.into(),
		value: SDK::one_avail().mul(1_000_000_000u128),
	};

	let call_4 = TransferKeepAlive {
		dest: dest_bob.into(),
		value: SDK::one_avail().mul(1u128),
	};

	// The 3. is poisoned with a too high transfer amount
	calls.push(RuntimeCall::Balances(call_3).into());
	// The 4. call is a normal one
	calls.push(RuntimeCall::Balances(call_4).into());

	// Batch Call
	let payload = avail::tx().utility().batch(calls.clone());
	let tx = Transaction::new(sdk.client.clone(), payload);
	let res = tx.execute_and_watch_inclusion(&account, Options::new()).await?;
	assert_eq!(res.is_successful(), Some(true), "Transactions must be successful");

	let events = res.events.unwrap();
	assert_eq!(events.has::<UtilityEvents::BatchInterrupted>(), Some(true), "");
	assert_eq!(events.has::<UtilityEvents::BatchCompleted>(), Some(false), "");
	assert_eq!(events.count::<UtilityEvents::ItemCompleted>(), 2, "");

	println!("-- Batch Call Done --");

	// Batch All Call
	let payload = avail::tx().utility().batch_all(calls.clone());
	let tx = Transaction::new(sdk.client.clone(), payload);
	let res = tx.execute_and_watch_inclusion(&account, Options::new()).await?;
	assert_eq!(res.is_successful(), Some(false), "Transactions needs fail.");

	println!("-- Batch All Call Done --");

	// Force Batch Call
	let payload = avail::tx().utility().force_batch(calls.clone());
	let tx = Transaction::new(sdk.client.clone(), payload);
	let res = tx.execute_and_watch_inclusion(&account, Options::new()).await?;
	assert_eq!(res.is_successful(), Some(true), "Transactions must be successful");

	let events = res.events.unwrap();
	assert_eq!(events.has::<UtilityEvents::BatchCompletedWithErrors>(), Some(true), "");
	assert_eq!(events.count::<UtilityEvents::ItemFailed>(), 1, "");
	assert_eq!(events.count::<UtilityEvents::ItemCompleted>(), 3, "");

	println!("-- Force Batch Call Done --");

	println!("Batch finished correctly");

	Ok(())
}