extern crate clap;

use anyhow::Result;
use clap::App;
use hydra::built_info::PKG_VERSION;
use hydra::client::Web3HttpClient;
use hydra::types::Account;
use hydra::{args, console, crypto, format};
use rust_decimal::Decimal;
use secp256k1::SecretKey;
use tokio::time::Instant;
use wagyu_ethereum::{EthereumExtendedPublicKey, Mainnet};
use web3::types::{Address, TransactionParameters, U256};

#[cfg(not(tarpaulin_include))]
#[tokio::main]
async fn main() -> Result<()> {
    let start_time = Instant::now();
    let matches = App::new("🐙 Hydra Distributor")
        .version(PKG_VERSION)
        .about("Distributes funds to multiple derived addresses")
        .arg(args::rpc())
        .arg(args::from())
        .arg(args::until())
        .arg(args::prv())
        .arg(args::xpub())
        .arg(args::amount())
        .get_matches();

    // Parse CLI arguments
    let rpc: &str = console::parse_str(&matches, args::ARG_RPC, args::DEFAULT_RPC);
    let from: i32 = console::parse_int(&matches, args::ARG_FROM, args::DEFAULT_FROM)?;
    let until: i32 = console::parse_int(&matches, args::ARG_UNTIL, args::DEFAULT_UNTIL)?;
    let amount: Decimal = console::parse_decimal(&matches, args::ARG_AMOUNT)?;
    let private_key: SecretKey = console::parse_private_key(&matches, args::ARG_PRV)?;
    let xpub: EthereumExtendedPublicKey<Mainnet> = console::parse_xpub::<Mainnet>(&matches, args::ARG_XPUB)?;

    // Derive sending address
    let from_addr: Address = crypto::private_key_address(&private_key);

    // Derive destination addresses
    let accounts: Vec<Account> = crypto::derive_accounts(xpub, from, until + 1)?;

    // Parse amount
    let tx_value: U256 = format::dec_to_u256(amount)?;

    // Init Web3 HTTP client
    let client = Web3HttpClient::from_cli(rpc)?;

    // Create progress bar
    let size: u64 = accounts.len() as u64;
    let progress_bar = console::progress_bar(size + 1);

    // Fetch nonce
    progress_bar.set_message("Fetching nonce...");
    let mut nonce = client.get_nonce(from_addr).await?;

    // Distribute funds
    for account in accounts {
        progress_bar.set_message(format!("Sending {} ETH to {}", amount, account.address));
        progress_bar.inc(1);

        let raw_tx = TransactionParameters {
            nonce: Some(nonce),
            to: Some(account.address),
            value: tx_value,
            ..Default::default()
        };

        // Sign & send transaction
        let signed = client.sign_tx(raw_tx, private_key).await?;
        client.send_tx(signed).await?;

        // Increase nonce
        nonce = nonce + 1;
    }

    progress_bar.finish_and_clear();
    println!();

    // Return
    println!("Completed in {:?}", Instant::now().duration_since(start_time));
    Ok(())
}
