pub mod harness;

mod tests {
    use crate::{
        harness,
        harness::{
            init_bitcoind, init_test,
            node::{run_alice_until, run_bob_until},
        },
    };
    use futures::future;
    use monero_harness::Monero;
    use rand::rngs::OsRng;
    use std::convert::TryInto;
    use testcontainers::clients::Cli;
    use xmr_btc::{
        alice, bitcoin,
        bitcoin::{Amount, TX_FEE},
        bob,
    };

    #[tokio::test]
    async fn happy_path() {
        let cli = Cli::default();
        let (monero, _container) = Monero::new(&cli, Some("hp".to_string()), vec![
            "alice".to_string(),
            "bob".to_string(),
        ])
        .await
        .unwrap();
        let bitcoind = init_bitcoind(&cli).await;

        let (
            alice_state0,
            bob_state0,
            mut alice_node,
            mut bob_node,
            initial_balances,
            swap_amounts,
        ) = init_test(&monero, &bitcoind, None, None).await;

        let (alice_state, bob_state) = future::try_join(
            run_alice_until(
                &mut alice_node,
                alice_state0.into(),
                harness::alice::is_state6,
                &mut OsRng,
            ),
            run_bob_until(
                &mut bob_node,
                bob_state0.into(),
                harness::bob::is_state5,
                &mut OsRng,
            ),
        )
        .await
        .unwrap();

        let alice_state6: alice::State6 = alice_state.try_into().unwrap();
        let bob_state5: bob::State5 = bob_state.try_into().unwrap();

        let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap();
        let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap();

        let lock_tx_bitcoin_fee = bob_node
            .bitcoin_wallet
            .transaction_fee(bob_state5.tx_lock_id())
            .await
            .unwrap();

        let alice_final_xmr_balance = alice_node.monero_wallet.get_balance().await.unwrap();

        monero.wallet("bob").unwrap().refresh().await.unwrap();

        let bob_final_xmr_balance = bob_node.monero_wallet.get_balance().await.unwrap();

        assert_eq!(
            alice_final_btc_balance,
            initial_balances.alice_btc + swap_amounts.btc
                - bitcoin::Amount::from_sat(bitcoin::TX_FEE)
        );
        assert_eq!(
            bob_final_btc_balance,
            initial_balances.bob_btc - swap_amounts.btc - lock_tx_bitcoin_fee
        );

        assert_eq!(
            alice_final_xmr_balance,
            initial_balances.alice_xmr - swap_amounts.xmr - alice_state6.lock_xmr_fee()
        );
        assert_eq!(
            bob_final_xmr_balance,
            initial_balances.bob_xmr + swap_amounts.xmr
        );
    }

    #[tokio::test]
    async fn both_refund() {
        let cli = Cli::default();
        let (monero, _container) = Monero::new(&cli, Some("br".to_string()), vec![
            "alice".to_string(),
            "bob".to_string(),
        ])
        .await
        .unwrap();
        let bitcoind = init_bitcoind(&cli).await;

        let (
            alice_state0,
            bob_state0,
            mut alice_node,
            mut bob_node,
            initial_balances,
            swap_amounts,
        ) = init_test(&monero, &bitcoind, None, None).await;

        let (alice_state, bob_state) = future::try_join(
            run_alice_until(
                &mut alice_node,
                alice_state0.into(),
                harness::alice::is_state5,
                &mut OsRng,
            ),
            run_bob_until(
                &mut bob_node,
                bob_state0.into(),
                harness::bob::is_state3,
                &mut OsRng,
            ),
        )
        .await
        .unwrap();

        let alice_state5: alice::State5 = alice_state.try_into().unwrap();
        let bob_state3: bob::State3 = bob_state.try_into().unwrap();

        bob_state3
            .refund_btc(&bob_node.bitcoin_wallet)
            .await
            .unwrap();
        alice_state5
            .refund_xmr(&alice_node.bitcoin_wallet, &alice_node.monero_wallet)
            .await
            .unwrap();

        let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap();
        let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap();

        // lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
        // to TX_FEE
        let lock_tx_bitcoin_fee = bob_node
            .bitcoin_wallet
            .transaction_fee(bob_state3.tx_lock_id())
            .await
            .unwrap();

        monero.wallet("alice").unwrap().refresh().await.unwrap();
        let alice_final_xmr_balance = alice_node.monero_wallet.get_balance().await.unwrap();
        let bob_final_xmr_balance = bob_node.monero_wallet.get_balance().await.unwrap();

        assert_eq!(alice_final_btc_balance, initial_balances.alice_btc);
        assert_eq!(
            bob_final_btc_balance,
            // The 2 * TX_FEE corresponds to tx_refund and tx_cancel.
            initial_balances.bob_btc - Amount::from_sat(2 * TX_FEE) - lock_tx_bitcoin_fee
        );

        // Because we create a new wallet when claiming Monero, we can only assert on
        // this new wallet owning all of `xmr_amount` after refund
        assert_eq!(alice_final_xmr_balance, swap_amounts.xmr);
        assert_eq!(bob_final_xmr_balance, initial_balances.bob_xmr);
    }

    #[tokio::test]
    async fn alice_punishes() {
        let cli = Cli::default();
        let (monero, _containers) = Monero::new(&cli, Some("ap".to_string()), vec![
            "alice".to_string(),
            "bob".to_string(),
        ])
        .await
        .unwrap();

        let bitcoind = init_bitcoind(&cli).await;

        let (
            alice_state0,
            bob_state0,
            mut alice_node,
            mut bob_node,
            initial_balances,
            swap_amounts,
        ) = init_test(&monero, &bitcoind, None, None).await;

        let (alice_state, bob_state) = future::try_join(
            run_alice_until(
                &mut alice_node,
                alice_state0.into(),
                harness::alice::is_state4,
                &mut OsRng,
            ),
            run_bob_until(
                &mut bob_node,
                bob_state0.into(),
                harness::bob::is_state3,
                &mut OsRng,
            ),
        )
        .await
        .unwrap();

        let alice_state4: alice::State4 = alice_state.try_into().unwrap();
        let bob_state3: bob::State3 = bob_state.try_into().unwrap();

        alice_state4
            .punish(&alice_node.bitcoin_wallet)
            .await
            .unwrap();

        let alice_final_btc_balance = alice_node.bitcoin_wallet.balance().await.unwrap();
        let bob_final_btc_balance = bob_node.bitcoin_wallet.balance().await.unwrap();

        // lock_tx_bitcoin_fee is determined by the wallet, it is not necessarily equal
        // to TX_FEE
        let lock_tx_bitcoin_fee = bob_node
            .bitcoin_wallet
            .transaction_fee(bob_state3.tx_lock_id())
            .await
            .unwrap();

        assert_eq!(
            alice_final_btc_balance,
            initial_balances.alice_btc + swap_amounts.btc - Amount::from_sat(2 * TX_FEE)
        );
        assert_eq!(
            bob_final_btc_balance,
            initial_balances.bob_btc - swap_amounts.btc - lock_tx_bitcoin_fee
        );
    }
}