/* This file is part of DarkFi (https://dark.fi)
 *
 * Copyright (C) 2020-2024 Dyne.org foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

//! Test for PoW reward transaction verification correctness.
//!
//! We first reward Alice some native tokens, and then she send some of them to Bob.
//!
//! With this test, we want to confirm the PoW reward transactions execution works
//! and generated tokens can be processed as usual between multiple parties,
//! with detection of erroneous transactions.

use darkfi::Result;
use darkfi_contract_test_harness::{init_logger, Holder, TestHarness};
use darkfi_sdk::{blockchain::expected_reward, crypto::DARK_TOKEN_ID};
use log::info;

#[test]
fn pow_reward() -> Result<()> {
    smol::block_on(async {
        init_logger();

        // Holders this test will use
        const HOLDERS: [Holder; 3] = [Holder::Faucet, Holder::Alice, Holder::Bob];

        // Block height(slot) to verify against
        let mut current_height = 0;

        // Initialize harness
        let mut th = TestHarness::new(&["money".to_string()], false).await?;

        let mut alice_owncoins = vec![];
        let mut bob_owncoins = vec![];

        // We are going to generate some erroneous transactions to
        // test some malicious cases.
        info!(target: "money", "[Malicious] =======================================");
        info!(target: "money", "[Malicious] Building PoW reward tx for genesis slot");
        info!(target: "money", "[Malicious] =======================================");
        let (pow_reward_tx, _) = th.pow_reward(&Holder::Alice, None, current_height, Some(0))?;

        info!(target: "money", "[Malicious] =======================================");
        info!(target: "money", "[Malicious] Checking PoW reward tx for genesis slot");
        info!(target: "money", "[Malicious] =======================================");
        th.execute_erroneous_pow_reward_tx(&Holder::Alice, &pow_reward_tx.clone(), current_height)
            .await?;

        current_height += 1;
        th.generate_slot(current_height).await?;

        let alice_reward = expected_reward(current_height);
        info!(target: "money", "[Malicious] ================================");
        info!(target: "money", "[Malicious] Building erroneous PoW reward tx");
        info!(target: "money", "[Malicious] ================================");
        let (pow_reward_tx, _) =
            th.pow_reward(&Holder::Alice, None, current_height, Some(alice_reward + 1))?;

        info!(target: "money", "[Malicious] =======================================");
        info!(target: "money", "[Malicious] Checking erroneous amount PoW reward tx");
        info!(target: "money", "[Malicious] =======================================");
        th.execute_erroneous_pow_reward_tx(&Holder::Alice, &pow_reward_tx.clone(), current_height)
            .await?;

        info!(target: "money", "[Alice] ======================");
        info!(target: "money", "[Alice] Building PoW reward tx");
        info!(target: "money", "[Alice] ======================");
        let (pow_reward_tx, pow_reward_params) =
            th.pow_reward(&Holder::Alice, None, current_height, None)?;

        for holder in &HOLDERS {
            info!(target: "money", "[{holder:?}] =============================");
            info!(target: "money", "[{holder:?}] Executing Alice PoW reward tx");
            info!(target: "money", "[{holder:?}] =============================");
            th.execute_pow_reward_tx(holder, &pow_reward_tx, &pow_reward_params, current_height)
                .await?;
        }

        th.assert_trees(&HOLDERS);

        // Alice gathers her new owncoin
        let alice_oc = th.gather_owncoin(&Holder::Alice, &pow_reward_params.output, None)?;
        alice_owncoins.push(alice_oc);

        // Now Alice can send a little bit of funds to Bob
        let alice_send = alice_reward / 2;
        info!(target: "money", "[Alice] ====================================================");
        info!(target: "money", "[Alice] Building Money::Transfer params for a payment to Bob");
        info!(target: "money", "[Alice] ====================================================");
        let (transfer_tx, transfer_params, spent_coins) =
            th.transfer(alice_send, &Holder::Alice, &Holder::Bob, &alice_owncoins, *DARK_TOKEN_ID)?;

        // Validating transfer params
        assert!(transfer_params.inputs.len() == 1);
        assert!(transfer_params.outputs.len() == 2);
        assert!(spent_coins.len() == 1);
        alice_owncoins.retain(|x| x != &spent_coins[0]);
        assert!(alice_owncoins.is_empty());

        for holder in &HOLDERS {
            info!(target: "money", "[{holder:?}] ==============================");
            info!(target: "money", "[{holder:?}] Executing Alice2Bob payment tx");
            info!(target: "money", "[{holder:?}] ==============================");
            th.execute_transfer_tx(holder, &transfer_tx, &transfer_params, current_height, true)
                .await?;
        }

        th.assert_trees(&HOLDERS);

        // Bob should have this new OwnCoin.
        let bob_oc = th.gather_owncoin(&Holder::Bob, &transfer_params.outputs[0], None)?;
        bob_owncoins.push(bob_oc);

        // Alice should now have one OwnCoin with the change from the above transaction.
        let alice_oc = th.gather_owncoin(&Holder::Alice, &transfer_params.outputs[1], None)?;
        alice_owncoins.push(alice_oc);

        // Alice can also send her PoW reward directly to bob
        current_height += 1;
        th.generate_slot(current_height).await?;

        info!(target: "money", "[Alice] ==============================");
        info!(target: "money", "[Alice] Building PoW reward tx for Bob");
        info!(target: "money", "[Alice] ==============================");
        let (pow_reward_tx, pow_reward_params) =
            th.pow_reward(&Holder::Alice, Some(&Holder::Bob), current_height, None)?;

        for holder in &HOLDERS {
            info!(target: "money", "[{holder:?}] =====================================");
            info!(target: "money", "[{holder:?}] Executing Alice PoW reward tx for Bob");
            info!(target: "money", "[{holder:?}] =====================================");
            th.execute_pow_reward_tx(holder, &pow_reward_tx, &pow_reward_params, current_height)
                .await?;
        }

        th.assert_trees(&HOLDERS);

        // Bob gathers his new owncoin
        let bob_oc = th.gather_owncoin(&Holder::Bob, &pow_reward_params.output, None)?;
        bob_owncoins.push(bob_oc);

        // Validating transaction outcomes
        assert!(alice_owncoins.len() == 1);
        assert!(bob_owncoins.len() == 2);
        assert!(alice_owncoins[0].note.value == alice_reward - alice_send);
        assert!(bob_owncoins[0].note.value == alice_send);
        assert!(bob_owncoins[1].note.value == alice_reward);

        // Statistics
        th.statistics();

        // Thanks for reading
        Ok(())
    })
}
