use std::fs::File;
use std::io::{BufRead, BufReader, Read, Write};
use std::time::Duration;

use cucumber::{Steps, StepsBuilder};
use data_encoding::BASE64;

use algo_rust_sdk::account::Account;
use algo_rust_sdk::algod::models::NodeStatus;
use algo_rust_sdk::auction::{Bid, SignedBid};
use algo_rust_sdk::crypto::{Address, MultisigAddress};
use algo_rust_sdk::transaction::{
    BaseTransaction, KeyRegistration, Payment, SignedTransaction, Transaction, TransactionType,
};
use algo_rust_sdk::AlgodClient;
use algo_rust_sdk::KmdClient;
use algo_rust_sdk::{
    mnemonic, Ed25519PublicKey, HashDigest, MasterDerivationKey, MicroAlgos, Round, VotePK, VRFPK,
};

#[derive(Default)]
pub struct World {
    algod_client: Option<AlgodClient>,
    kmd_client: Option<KmdClient>,
    versions: Vec<String>,
    status: Option<NodeStatus>,
    status_after: Option<NodeStatus>,
    transaction_id: Option<String>,
    last_round: Option<Round>,
    params_fee: Option<MicroAlgos>,
    wallet_name: Option<String>,
    wallet_password: Option<String>,
    wallet_id: Option<String>,
    wallet_handle: Option<String>,
    public_keys: Vec<Ed25519PublicKey>,
    account: Option<Account>,
    accounts: Vec<String>,
    old_address: Option<String>,
    address: Option<String>,
    public_key: Option<Address>,
    mdk: Option<[u8; 32]>,
    new_mnemonic: Option<String>,
    fee: Option<MicroAlgos>,
    first_valid: Option<Round>,
    last_valid: Option<Round>,
    note: Vec<u8>,
    genesis_hash: Option<HashDigest>,
    genesis_id: String,
    vote_pk: Option<VotePK>,
    vrf_pk: Option<VRFPK>,
    vote_first: Option<Round>,
    vote_last: Option<Round>,
    vote_key_dilution: Option<u64>,
    receiver: Option<Address>,
    close: Option<Address>,
    amount: Option<MicroAlgos>,
    multisig: Option<MultisigAddress>,
    transaction: Option<Transaction>,
    signed_transaction: Option<SignedTransaction>,
    signed_transaction_bytes: Vec<u8>,
    signed_transactions: Option<Vec<SignedTransaction>>,
    micro_algos: Option<MicroAlgos>,
    bid: Option<Bid>,
    old_bid: Option<SignedBid>,
    signed_bid: Option<SignedBid>,
    err: bool,
    num: String,
}

impl cucumber::World for World {}

pub fn steps() -> Steps<World> {
    let mut builder: StepsBuilder<World> = Default::default();
    builder
        .given("an algod client", |world: &mut World, _step| {
            let home = dirs::home_dir().expect("Couldn't get home dir");
            let data_dir = home.join("node/network/Node");
            let token = {
                let f = File::open(data_dir.join("algod.token")).expect("Couldn't open algod.token");
                let mut f = BufReader::new(f);
                let mut token = String::new();
                f.read_line(&mut token).unwrap();
                token
            };
            let address = {
                let f = File::open(data_dir.join("algod.net")).expect("Couldn't open algod.net");
                let mut f = BufReader::new(f);
                let mut address = "http://".to_string();
                f.read_line(&mut address).unwrap();
                address
            };
            world.algod_client = Some(AlgodClient::new(&address, &token))
        })
        .given("a kmd client", |world: &mut World, _step| {
            let home = dirs::home_dir().expect("Couldn't get home dir");
            let kmd_dir = std::env::var("KMD_DIR").expect("KMD_DIR is not set");
            let data_dir = home.join("node/network/Node").join(&kmd_dir);
            let token = {
                let f = File::open(data_dir.join("kmd.token")).expect("Couldn't open kmd.token");
                let mut f = BufReader::new(f);
                let mut token = String::new();
                f.read_line(&mut token).unwrap();
                token
            };
            let address = {
                let f = File::open(data_dir.join("kmd.net")).expect("Couldn't open kmd.net");
                let mut f = BufReader::new(f);
                let mut address = "http://".to_string();
                f.read_line(&mut address).unwrap();
                address
            };
            world.kmd_client = Some(KmdClient::new(&address, &token))
        })
        .given("wallet information", |world: &mut World, _step| {
            //FIXME Sometimes this doesn't get created
            let wallet_name = "unencrypted-default-wallet";
            let wallet_password = "";
            let kmd_client = world.kmd_client.as_ref().unwrap();
            for wallet in kmd_client.list_wallets().unwrap().wallets {
                if wallet.name == wallet_name {
                    world.wallet_id = Some(wallet.id);
                }
            }
            let wallet_id = world.wallet_id.as_ref().expect("Could not find wallet unencrypted-default-wallet");
            let handle = kmd_client.init_wallet_handle(wallet_id, wallet_password).unwrap().wallet_handle_token;
            world.accounts = kmd_client.list_keys(&handle).unwrap().addresses;
            world.wallet_handle = Some(handle);
            world.wallet_name = Some(wallet_name.to_string());
            world.wallet_password = Some(wallet_password.to_string())
        })
        .then("the node should be healthy", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            algod_client.health().unwrap();
        })
        .then("I get the ledger supply", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let _ = algod_client.ledger_supply().unwrap();
        })
        .then("I get transactions by address and round", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let _ = algod_client.transactions(&world.accounts[0], Some(Round(1)), Some(algod_client.status().unwrap().last_round), None, None, Some(10)).unwrap();
        })
        .then("I get transactions by address only", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let _ = algod_client.transactions(&world.accounts[0], None, None, None, None, Some(10)).unwrap();
        })
        .then("I get transactions by address and date", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let _ = algod_client.transactions(&world.accounts[0], None, None, Some(chrono::Local::today().naive_local().to_string()), Some(chrono::Local::today().naive_local().to_string()), Some(10)).unwrap();
        })
        .when_regex(r"^I get recent transactions, limited by (\d+) transactions$", |world: &mut World, strings, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let _ = algod_client.transactions(&world.accounts[0], None, None, None, None, Some(strings[1].parse().unwrap())).unwrap();
        })
        .then("I get pending transactions", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let _ = algod_client.pending_transactions(10).unwrap();
        })
        .when("I get the suggested params", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            world.params_fee = Some(algod_client.transaction_params().unwrap().fee)
        })
        .when("I get the suggested fee", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            world.fee = Some(algod_client.suggested_fee().unwrap().fee)
        })
        .then("the fee in the suggested params should equal the suggested fee", |world: &mut World, _step| {
            assert_eq!(world.params_fee.unwrap(), world.fee.unwrap())
        })
        .when("I get the status", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            world.status = Some(algod_client.status().unwrap());
        })
        .when("I get status after this block", |world: &mut World, _step| {
            std::thread::sleep(Duration::from_secs(4));
            let algod_client = world.algod_client.as_ref().unwrap();
            let status = world.status.as_ref().unwrap();
            world.status_after = Some(algod_client.status_after_block(status.last_round).unwrap());
        })
        .then("I can get the block info", |world, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let status = world.status.as_ref().unwrap();
            let _ = algod_client.block(status.last_round + 1).unwrap();
        })
        .given_regex(r#"default transaction with parameters (\d+) "([^"]*)""#, |world: &mut World, strings, _step| {
            let amount = MicroAlgos(strings[1].parse().unwrap());
            world.note = if strings[2] == "none" {
                Vec::new()
            } else {
                BASE64.decode(strings[2].as_bytes()).unwrap()
            };
            let algod_client = world.algod_client.as_ref().unwrap();
            let params = algod_client.transaction_params().unwrap();
            let base = BaseTransaction {
                sender: Address::from_string(&world.accounts[0]).unwrap(),
                first_valid: params.last_round,
                last_valid: params.last_round + 1000,
                note: world.note.clone(),
                genesis_id: params.genesis_id,
                genesis_hash: params.genesis_hash,
            };
            let payment = Payment {
                amount,
                receiver: Address::from_string(&world.accounts[1]).unwrap(),
                close_remainder_to: None,
            };
            world.transaction = Some(Transaction::new(
                base,
                params.fee,
                TransactionType::Payment(payment),
            ).unwrap());
            world.last_round = Some(params.last_round);
            world.public_key = Some(Address::from_string(&world.accounts[0]).unwrap());
        })
        .given_regex(r#"default multisig transaction with parameters (\d+) "([^"]*)""#, |world: &mut World, strings, _step| {
            let amount = MicroAlgos(strings[1].parse().unwrap());
            world.note = if strings[2] == "none" {
                Vec::new()
            } else {
                BASE64.decode(strings[2].as_bytes()).unwrap()
            };
            let algod_client = world.algod_client.as_ref().unwrap();
            let params = algod_client.transaction_params().unwrap();
            let addresses: Vec<Address> = world.accounts.iter().map(|account| Address::from_string(account).unwrap()).collect();
            let multisig = MultisigAddress::new(1, 1, &addresses).unwrap();
            let base = BaseTransaction {
                sender: multisig.address(),
                first_valid: params.last_round,
                last_valid: params.last_round + 1000,
                note: world.note.clone(),
                genesis_id: params.genesis_id,
                genesis_hash: params.genesis_hash,
            };
            let payment = Payment {
                amount,
                receiver: Address::from_string(&world.accounts[1]).unwrap(),
                close_remainder_to: None,
            };
            world.transaction = Some(Transaction::new(
                base,
                params.fee,
                TransactionType::Payment(payment),
            ).unwrap());
            world.last_round = Some(params.last_round);
            world.public_key = Some(Address::from_string(&world.accounts[0]).unwrap());
            world.multisig = Some(multisig);
        })
        .when("I sign the multisig transaction with kmd", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().expect("No wallet handle");
            let wallet_password = world.wallet_password.as_ref().expect("No wallet password");
            let transaction = world.transaction.as_ref().expect("No transaction");
            let multisig = world.multisig.as_ref().expect("No multisig");
            let public_key = world.public_key.expect("No public key");
            let kmd_client = world.kmd_client.as_ref().expect("No kmd client");
            let _ = kmd_client.import_multisig(wallet_handle, multisig.version, multisig.threshold, &multisig.public_keys).unwrap();
            world.signed_transaction_bytes = kmd_client.sign_multisig_transaction(wallet_handle, wallet_password, transaction, Ed25519PublicKey(public_key.0), None).unwrap().multisig;
        })
        .when("I get versions with kmd", |world: &mut World, _step| {
            let kmd_client = world.kmd_client.as_ref().unwrap();
            world.versions = kmd_client.versions().unwrap().versions;
        })
        .when("I get versions with algod", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            world.versions = algod_client.versions().unwrap().versions;
        })
        .then("v1 should be in the versions", |world: &mut World, _step| {
            assert!(world.versions.contains(&"v1".to_string()))
        })
        .when("I create a wallet", |world: &mut World, _step| {
            let wallet_name = "Walletrust";
            let wallet_password = "";
            let response = world.kmd_client.as_ref().unwrap().create_wallet(wallet_name, wallet_password, "sqlite", MasterDerivationKey([0; 32])).unwrap();
            world.wallet_name = Some(wallet_name.to_string());
            world.wallet_password = Some(wallet_password.to_string());
            world.wallet_id = Some(response.wallet.id);
        })
        .then("the wallet should exist", |world: &mut World, _step| {
            let mut exists = false;
            let wallet_name = world.wallet_name.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            for wallet in kmd_client.list_wallets().unwrap().wallets {
                if &wallet.name == wallet_name {
                    exists = true;
                }
            }
            assert!(exists);
        })
        .when("I get the wallet handle", |world: &mut World, _step| {
            let wallet_id = world.wallet_id.as_ref().unwrap();
            let wallet_password = world.wallet_password.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            world.wallet_handle = Some(kmd_client.init_wallet_handle(wallet_id, wallet_password).unwrap().wallet_handle_token);
        })
        .then("I can get the master derivation key", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let wallet_password = world.wallet_password.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let _ = kmd_client.export_master_derivation_key(wallet_handle, wallet_password).unwrap();
        })
        .when("I rename the wallet", |world: &mut World, _step| {
            let wallet_id = world.wallet_id.as_ref().unwrap();
            let wallet_password = world.wallet_password.as_ref().unwrap();
            let new_name = "Walletrust_new";
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let _ = kmd_client.rename_wallet(wallet_id, wallet_password, new_name).unwrap();
            world.wallet_name = Some(new_name.to_string());
        })
        .then("I can still get the wallet information with the same handle", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let name = kmd_client.get_wallet(wallet_handle).unwrap().wallet_handle.wallet.name;
            assert_eq!(Some(name), world.wallet_name);
        })
        .when("I renew the wallet handle", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let _ = kmd_client.renew_wallet_handle(wallet_handle).unwrap();
        })
        .when("I release the wallet handle", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let _ = kmd_client.release_wallet_handle(wallet_handle).unwrap();
        })
        .then("the wallet handle should not work", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            assert!(kmd_client.renew_wallet_handle(wallet_handle).is_err());
        })
        .when("I generate a key using kmd", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let public_key = kmd_client.generate_key(wallet_handle).unwrap().address;
            world.public_key = Some(Address::from_string(&public_key).unwrap())
        })
        .then("the key should be in the wallet", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            assert!(kmd_client.list_keys(wallet_handle).unwrap().addresses.contains(&world.public_key.unwrap().encode_string()));
        })
        .when("I delete the key", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let wallet_password = world.wallet_password.as_ref().unwrap();
            let public_key = world.public_key.unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let _ = kmd_client.delete_key(wallet_handle, wallet_password, &public_key.encode_string()).unwrap();
        })
        .then("the key should not be in the wallet", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            assert!(!kmd_client.list_keys(wallet_handle).unwrap().addresses.contains(&world.public_key.unwrap().encode_string()));
        })
        .then("I get account information", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let _ = algod_client.account_information(&world.accounts[0]).unwrap();
        })
        .then("I can get account information", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let wallet_password = world.wallet_password.as_ref().unwrap();
            let address = world.public_key.unwrap().encode_string();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let algod_client = world.algod_client.as_ref().unwrap();
            let _ = algod_client.account_information(&address).unwrap();
            let _ = kmd_client.delete_key(wallet_handle, wallet_password, &address).unwrap();
        })
        .when("I import the key", |world: &mut World, _step| {
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let private_key = world.account.as_ref().unwrap().seed();
            let _ = kmd_client.import_key(wallet_handle, private_key).unwrap();
        })
        .then("the private key should be equal to the exported private key", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let wallet_password = world.wallet_password.as_ref().unwrap();
            let address = world.public_key.unwrap().encode_string();
            let private_key = world.account.as_ref().unwrap().seed();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let exported = kmd_client.export_key(wallet_handle, wallet_password, &address).unwrap().private_key;
            assert_eq!(&exported[..32], &private_key);
            let _ = kmd_client.delete_key(wallet_handle, wallet_password, &address).unwrap();
        })
        .when("I get the private key", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().expect("No wallet handle");
            let wallet_password = world.wallet_password.as_ref().expect("No wallet password");
            let address = world.public_key.expect("No public key").encode_string();
            let kmd_client = world.kmd_client.as_ref().expect("No kmd client");
            let exported_seed = kmd_client.export_key(wallet_handle, wallet_password, &address).unwrap().private_key;
            let mut seed = [0; 32];
            seed.copy_from_slice(&exported_seed[..32]);
            world.account = Some(Account::from_seed(seed));
        })
        .when("I sign the transaction with kmd", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let wallet_password = world.wallet_password.as_ref().unwrap();
            let transaction = world.transaction.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            world.signed_transaction_bytes = kmd_client.sign_transaction(wallet_handle, wallet_password, transaction).unwrap().signed_transaction;
        })
        .then("the signed transaction should equal the kmd signed transaction", |world: &mut World, _step| {
            let local_bytes = rmp_serde::to_vec_named(world.signed_transaction.as_ref().unwrap()).unwrap();
            assert_eq!(BASE64.encode(&world.signed_transaction_bytes), BASE64.encode(&local_bytes));
        })
        .then("the multisig transaction should equal the kmd signed multisig transaction", |world: &mut World, _step| {
            let local_bytes = rmp_serde::to_vec_named(world.signed_transaction.as_ref().unwrap().multisig.as_ref().unwrap()).unwrap();
            assert_eq!(BASE64.encode(&world.signed_transaction_bytes), BASE64.encode(&local_bytes));

            let multisig = world.multisig.as_ref().unwrap();
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let wallet_password = world.wallet_password.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let _ = kmd_client.delete_multisig(wallet_handle, wallet_password, &multisig.address().encode_string()).unwrap();
        })
        .when("I send the transaction", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let signed_transaction = world.signed_transaction.as_ref().unwrap();
            world.transaction_id = Some(algod_client.send_transaction(signed_transaction).unwrap().tx_id);
        })
        .when("I send the multisig transaction", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let signed_transaction = world.signed_transaction.as_ref().unwrap();
            world.err = algod_client.send_transaction(signed_transaction).is_err();
        })
        .then("the transaction should go through", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let public_key = world.public_key.unwrap().encode_string();
            let transaction_id = world.transaction_id.as_ref().unwrap();
            assert_eq!(algod_client.pending_transaction_information(transaction_id).unwrap().from, public_key);
            let _ = algod_client.status_after_block(world.last_round.unwrap() + 2);
            assert_eq!(algod_client.transaction_information(&public_key, transaction_id).unwrap().from, public_key);
            assert_eq!(algod_client.transaction(transaction_id).unwrap().from, public_key);
        })
        .then("the transaction should not go through", |world: &mut World, _step| {
            assert!(world.err)
        })
        .then("I can get the transaction by ID", |world: &mut World, _step| {
            let algod_client = world.algod_client.as_ref().unwrap();
            let transaction_id = world.transaction_id.as_ref().unwrap();
            let public_key = world.public_key.unwrap().encode_string();
            let _ = algod_client.status_after_block(world.last_round.unwrap() + 2).unwrap();
            assert_eq!(algod_client.transaction(transaction_id).unwrap().from, public_key);
        })
        .when("I import the multisig", |world: &mut World, _step| {
            let multisig = world.multisig.as_ref().unwrap();
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let _ = kmd_client.import_multisig(wallet_handle, multisig.version, multisig.threshold, &multisig.public_keys).unwrap();
        })
        .when("I export the multisig", |world: &mut World, _step| {
            let multisig = world.multisig.as_ref().unwrap();
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            world.public_keys = kmd_client.export_multisig(wallet_handle, &multisig.address().encode_string()).unwrap().pks;
        })
        .when("I delete the multisig", |world: &mut World, _step| {
            let multisig = world.multisig.as_ref().unwrap();
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let wallet_password = world.wallet_password.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let _ = kmd_client.delete_multisig(wallet_handle, wallet_password, &multisig.address().encode_string()).unwrap();
        })
        .then("the multisig should equal the exported multisig", |world: &mut World, _step| {
            let multisig = world.multisig.as_ref().unwrap();
            for (left, right) in multisig.public_keys.iter().zip(&world.public_keys) {
                assert_eq!(left, right);
            }
        })
        .then("the multisig should be in the wallet", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let addresses = kmd_client.list_multisig(wallet_handle).unwrap().addresses;
            assert!(addresses.contains(&world.multisig.as_ref().unwrap().address().encode_string()));
        })
        .then("the multisig should not be in the wallet", |world: &mut World, _step| {
            let wallet_handle = world.wallet_handle.as_ref().unwrap();
            let kmd_client = world.kmd_client.as_ref().unwrap();
            let addresses = kmd_client.list_multisig(wallet_handle).unwrap().addresses;
            assert!(!addresses.contains(&world.multisig.as_ref().unwrap().address().encode_string()));
        })
        .when("I generate a key", |world: &mut World, _step| {
            let account = Account::generate();
            world.public_key = Some(account.address());
            world.address = Some(account.address().encode_string());
            world.account = Some(account);
        })
        .when("I decode the address", |world: &mut World, _step| {
            world.public_key = Some(Address::from_string(&world.address.as_ref().unwrap()).unwrap());
            world.old_address = world.address.clone();
        })
        .when("I encode the address", |world: &mut World, _step| {
            world.address = Some(world.public_key.unwrap().encode_string());
        })
        .then("the address should still be the same", |world: &mut World, _step| {
            assert_eq!(world.old_address, world.address);
        })
        .given_regex(r#"mnemonic for private key "([^"]*)"#, |world: &mut World, strings, _step| {
            let account = Account::from_seed(mnemonic::to_key(&strings[1]).unwrap());
            world.public_key = Some(account.address());
            world.account = Some(account)
        })
        .when("I convert the private key back to a mnemonic", |world: &mut World, _step| {
            world.new_mnemonic = Some(mnemonic::from_key(&world.account.as_ref().unwrap().seed()).unwrap());
        })
        .then_regex(r#"^the mnemonic should still be the same as "([^"]*)""#, |world: &mut World, strings, _step| {
            assert_eq!(world.new_mnemonic.as_ref().unwrap(), &strings[1])
        })
        .given_regex(r#"^mnemonic for master derivation key "([^"]*)""#, |world: &mut World, strings, _step| {
            world.mdk = Some(mnemonic::to_key(&strings[1]).unwrap());
        })
        .when("I convert the master derivation key back to a mnemonic", |world: &mut World, _step| {
            world.new_mnemonic = Some(mnemonic::from_key(&world.mdk.unwrap()).unwrap());
        })
        .given_regex(r#"payment transaction parameters (\d+) (\d+) (\d+) "([^"]*)" "([^"]*)" "([^"]*)" (\d+) "([^"]*)" "([^"]*)""#, |world: &mut World, strings, _step| {
            world.fee = Some(MicroAlgos(strings[1].parse().unwrap()));
            world.first_valid = Some(Round(strings[2].parse().unwrap()));
            world.last_valid = Some(Round(strings[3].parse().unwrap()));
            let mut genesis_hash = [0; 32];
            genesis_hash.copy_from_slice(&BASE64.decode(strings[4].as_bytes()).unwrap());
            world.genesis_hash = Some(HashDigest(genesis_hash));
            world.receiver = Some(Address::from_string(&strings[5]).unwrap());
            if strings[6] != "none" {
                world.close = Some(Address::from_string(&strings[6]).unwrap());
            }
            world.amount = Some(MicroAlgos(strings[7].parse().unwrap()));
            if strings[8] != "none" {
                world.genesis_id = strings[8].clone();
            }
            if strings[9] != "none" {
                world.note = BASE64.decode(strings[9].as_bytes()).unwrap();
            }
        })
        .given_regex(r#"key registration transaction parameters (\d+) (\d+) (\d+) "([^"]*)" "([^"]*)" "([^"]*)" (\d+) (\d+) (\d+) "([^"]*)" "([^"]*)"#, |world: &mut World, strings, _step| {
            world.fee = Some(MicroAlgos(strings[1].parse().unwrap()));
            world.first_valid = Some(Round(strings[2].parse().unwrap()));
            world.last_valid = Some(Round(strings[3].parse().unwrap()));
            let mut genesis_hash = [0; 32];
            genesis_hash.copy_from_slice(&BASE64.decode(strings[4].as_bytes()).unwrap());
            world.genesis_hash = Some(HashDigest(genesis_hash));

            let mut vote_pk = [0; 32];
            vote_pk.copy_from_slice(&BASE64.decode(strings[5].as_bytes()).unwrap());
            world.vote_pk = Some(VotePK(vote_pk));
            let mut vrf_pk = [0; 32];
            vrf_pk.copy_from_slice(&BASE64.decode(strings[6].as_bytes()).unwrap());
            world.vrf_pk = Some(VRFPK(vrf_pk));
            world.vote_first = Some(Round(strings[7].parse().unwrap()));
            world.vote_last = Some(Round(strings[8].parse().unwrap()));
            world.vote_key_dilution = Some(strings[9].parse().unwrap());

            if strings[10] != "none" {
                world.genesis_id = strings[10].clone();
            }
            if strings[11] != "none" {
                world.note = BASE64.decode(strings[11].as_bytes()).unwrap();
            }
        })
        .when("I create the payment transaction", |world: &mut World, _step| {
            let base = BaseTransaction {
                sender: world.public_key.expect("No public key"),
                first_valid: world.first_valid.expect("No first valid"),
                last_valid: world.last_valid.expect("No last valid"),
                note: world.note.clone(),
                genesis_id: world.genesis_id.clone(),
                genesis_hash: world.genesis_hash.expect("No genesis hash"),
            };
            let payment = Payment {
                amount: world.amount.expect("No amount"),
                receiver: world.receiver.expect("No receiver"),
                close_remainder_to: world.close,
            };
            world.transaction = Some(Transaction::new(
                base,
                world.fee.expect("No fee"),
                TransactionType::Payment(payment),
            ).unwrap());
        })
        .when("I create the flat fee payment transaction", |world: &mut World, _step| {
            let base = BaseTransaction {
                sender: world.public_key.expect("No public key"),
                first_valid: world.first_valid.expect("No first valid"),
                last_valid: world.last_valid.expect("No last valid"),
                note: world.note.clone(),
                genesis_id: world.genesis_id.clone(),
                genesis_hash: world.genesis_hash.expect("No genesis hash"),
            };
            let payment = Payment {
                amount: world.amount.expect("No amount"),
                receiver: world.receiver.expect("No receiver"),
                close_remainder_to: world.close,
            };
            world.transaction = Some(Transaction::new_flat_fee(
                base,
                world.fee.expect("No fee"),
                TransactionType::Payment(payment),
            ));
        })
        .when("I create the multisig payment transaction", |world: &mut World, _step| {
            let base = BaseTransaction {
                sender: world.multisig.as_ref().expect("No multisig address").address(),
                first_valid: world.first_valid.expect("No first valid"),
                last_valid: world.last_valid.expect("No last valid"),
                note: world.note.clone(),
                genesis_id: world.genesis_id.clone(),
                genesis_hash: world.genesis_hash.expect("No genesis hash"),
            };
            let payment = Payment {
                amount: world.amount.expect("No amount"),
                receiver: world.receiver.expect("No receiver"),
                close_remainder_to: world.close,
            };
            world.transaction = Some(Transaction::new(
                base,
                world.fee.expect("No fee"),
                TransactionType::Payment(payment),
            ).unwrap());
        })
        .when("I create the key registration transaction", |world: &mut World, _step| {
            let base = BaseTransaction {
                sender: world.public_key.expect("No public key"),
                first_valid: world.first_valid.expect("No first valid"),
                last_valid: world.last_valid.expect("No last valid"),
                note: world.note.clone(),
                genesis_id: world.genesis_id.clone(),
                genesis_hash: world.genesis_hash.expect("No genesis hash"),
            };
            let key_registration = KeyRegistration {
                vote_pk: world.vote_pk.expect("No vote public key"),
                selection_pk: world.vrf_pk.expect("No VRFPK"),
                vote_first: world.vote_first.expect("No vote first"),
                vote_last: world.vote_last.expect("No vote last"),
                vote_key_dilution: world.vote_key_dilution.expect("No vote key dilution"),
            };
            world.transaction = Some(Transaction::new(
                base,
                world.fee.expect("No fee"),
                TransactionType::KeyRegistration(key_registration),
            ).unwrap());
        })
        .when("I sign the transaction with the private key", |world: &mut World, _step| {
            world.signed_transaction = Some(world.account.as_ref().unwrap().sign_transaction(world.transaction.as_ref().unwrap()).expect("Failed to sign transaction"))
        })
        .when("I sign the multisig transaction with the private key", |world: &mut World, _step| {
            world.signed_transaction = Some(world.account.as_ref().unwrap().sign_multisig_transaction(world.multisig.clone().unwrap(), world.transaction.as_ref().unwrap()).expect("Failed to sign transaction"))
        })
        .then_regex(r#"the signed transaction should equal the golden "([^"]*)""#, |world: &mut World, strings, _step| {
            let bytes = rmp_serde::to_vec_named(world.signed_transaction.as_ref().unwrap()).unwrap();
            assert_eq!(BASE64.encode(&bytes), strings[1])
        })
        .then_regex(r#"the multisig transaction should equal the golden "([^"]*)""#, |world: &mut World, strings, _step| {
            let bytes = rmp_serde::to_vec_named(world.signed_transaction.as_ref().unwrap()).unwrap();
            assert_eq!(BASE64.encode(&bytes), strings[1])
        })
        .given_regex(r#"multisig addresses "([^"]*)""#, |world: &mut World, strings, _step| {
            let addresses: Vec<Address> = strings[1].split(' ').map(|s| Address::from_string(s).unwrap()).collect();
            world.multisig = Some(MultisigAddress::new(1, 2, &addresses).unwrap());
        })
        .then_regex(r#"the multisig address should equal the golden "([^"]*)""#, |world: &mut World, strings, _step| {
            let msig_address = world.multisig.as_ref().unwrap().address();
            assert_eq!(msig_address.encode_string(), strings[1])
        })
        .given_regex(r#"encoded multisig transaction "([^"]*)""#, |world: &mut World, strings, _step| {
            let bytes = BASE64.decode(strings[1].as_bytes()).expect("Failed to decode from base64");
            let signed_transaction: SignedTransaction = rmp_serde::from_read_ref(&bytes).expect("Failed to decode");
            let (version, threshold, addresses) = {
                let multisig = signed_transaction.multisig.as_ref().unwrap();
                let addresses: Vec<Address> = multisig.subsigs.iter().map(|subsig| Address::new(subsig.key.0)).collect();
                (multisig.version, multisig.threshold, addresses)
            };
            world.multisig = Some(MultisigAddress::new(version, threshold, &addresses).unwrap());
            world.signed_transaction = Some(signed_transaction);
        })
        .when("I append a signature to the multisig transaction", |world: &mut World, _step| {
            let multisig = world.multisig.clone().unwrap();
            let signed_transaction = world.signed_transaction.as_ref().unwrap();
            world.signed_transaction = Some(world.account.as_ref().unwrap().append_multisig_transaction(multisig, signed_transaction).unwrap())
        })
        .given_regex(r#"encoded multisig transactions "([^"]*)""#, |world: &mut World, strings, _step| {
            let transactions: Vec<SignedTransaction> = strings[1].split(' ').map(|encoded| {
                let bytes = BASE64.decode(encoded.as_bytes()).unwrap();
                rmp_serde::from_read_ref(&bytes).unwrap()
            }).collect();
            world.signed_transactions = Some(transactions);
        })
        .when("I merge the multisig transactions", |world: &mut World, _step| {
            let signed_transaction = Account::merge_multisig_transactions(&world.signed_transactions.as_ref().unwrap()).unwrap();
            world.signed_transaction = Some(signed_transaction);
        })
        .when_regex(r"^I convert (\d+) microalgos to algos and back", |world: &mut World, strings, _step| {
            let micro_algos: MicroAlgos = MicroAlgos(strings[1].parse().unwrap());
            world.micro_algos = Some(MicroAlgos::from_algos(micro_algos.to_algos()));
        })
        .then_regex(r"^it should still be the same amount of microalgos (\d+)", |world: &mut World, strings, _step| {
            let micro_algos: MicroAlgos = MicroAlgos(strings[1].parse().unwrap());
            assert_eq!(world.micro_algos.unwrap(), micro_algos);
        })
        .when("I create a bid", |world: &mut World, _step| {
            let account = Account::generate();
            world.public_key = Some(account.address());
            world.address = Some(account.address().encode_string());
            world.bid = Some(Bid {
                auction_key: account.address(),
                bidder_key: account.address(),
                auction_id: 1,
                bid_currency: 2,
                bid_id: 3,
                max_price: 4,
            });
            world.account = Some(account);
        })
        .when("I sign the bid", |world: &mut World, _step| {
            let account = world.account.as_ref().unwrap();
            world.signed_bid = Some(account.sign_bid(world.bid.unwrap()).unwrap());
            world.old_bid = world.signed_bid;
        })
        .when("I encode and decode the bid", |world: &mut World, _step| {
            let encoded = rmp_serde::to_vec_named(&world.signed_bid.unwrap()).unwrap();
            let signed_bid = rmp_serde::from_read_ref(&encoded).unwrap();
            world.signed_bid = Some(signed_bid);
        })
        .then("the bid should still be the same", |world: &mut World, _step| {
            assert_eq!(world.signed_bid.unwrap(), world.old_bid.unwrap());
        })
        .when_regex(r#"I read a transaction "([^"]*)" from file "([^"]*)""#, |world: &mut World, strings, _step| {
            world.num = strings[2].clone();
            let path = std::env::current_dir().expect("Couldn't get current dir").parent().unwrap().join(format!("temp/raw{}.tx", strings[2]));
            let f = File::open(path).unwrap();
            world.signed_transaction = Some(rmp_serde::from_read(f).unwrap());
        })
        .when("I write the transaction to file", |world: &mut World, _step| {
            let path = std::env::current_dir().expect("Couldn't get current dir").parent().unwrap().join(format!("temp/raw{}.tx", world.num));
            let data = rmp_serde::to_vec_named(world.signed_transaction.as_ref().unwrap()).unwrap();
            let mut f = File::create(path).unwrap();
            f.write_all(&data).unwrap();
        })
        .then("the transaction should still be the same", |world: &mut World, _step| {
            let path = std::env::current_dir().expect("Couldn't get current dir");
            let path = path.parent().unwrap();
            let f = File::open(path.join(format!("temp/raw{}.tx", world.num))).unwrap();
            let new: SignedTransaction = rmp_serde::from_read(f).unwrap();

            let f = File::open(path.join(format!("temp/old{}.tx", world.num))).unwrap();
            let old: SignedTransaction = rmp_serde::from_read(f).unwrap();

            assert_eq!(new, old);
        })
        .then("I do my part", |world: &mut World, _step| {
            let path = std::env::current_dir().expect("Couldn't get current dir").parent().unwrap().join(format!("temp/txn.tx"));
            let mut f = File::open(&path).unwrap();
            let mut bytes = Vec::new();
            let _ = f.read_to_end(&mut bytes).unwrap();
            let signed_transaction: SignedTransaction = rmp_serde::from_read_ref(&bytes).unwrap();
            let transaction = signed_transaction.transaction;

            let wallet_handle = world.wallet_handle.as_ref().expect("No wallet handle");
            let wallet_password = world.wallet_password.as_ref().expect("No wallet password");
            let address = transaction.sender.encode_string();
            let kmd_client = world.kmd_client.as_ref().expect("No kmd client");
            let exported_seed = kmd_client.export_key(wallet_handle, wallet_password, &address).unwrap().private_key;
            let mut seed = [0; 32];
            seed.copy_from_slice(&exported_seed[..32]);
            let account = Account::from_seed(seed);
            let signed_transaction = account.sign_transaction(&transaction).unwrap();

            let data = rmp_serde::to_vec_named(&signed_transaction).unwrap();
            let mut f = File::create(&path).unwrap();
            f.write_all(&data).unwrap();

            world.account = Some(account);
        })
    ;
    builder.build()
}

#[allow(unused_imports)]
fn main() {

}

