// Copyright 2024 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use std::iter;

use ootle_byte_type::ToByteType;
use rand::random;
use tari_engine::transaction::TransactionErrorKind;
use tari_engine_types::{
    commit_result::{RejectReason, TransactionResult},
    hashing::hash_template_code,
    limits,
    published_template::{PublishedTemplateAddress, TemplateBlob},
    substate::{SubstateId, SubstateValue},
};
use tari_ootle_transaction::Transaction;
use tari_template_test_tooling::{
    TemplateTest,
    compile::compile_template,
    support::assert_error::assert_reject_reason,
};

const CRATE_PATH: &str = env!("CARGO_MANIFEST_DIR");

#[test]
fn publish_template_success() {
    let mut test = TemplateTest::new(CRATE_PATH, &[] as &[&str]);
    let (account_address, owner_proof, account_key, public_key) = test.create_funded_account_with_keypair();
    let template = compile_template("tests/templates/hello_world", &[]).unwrap();
    let expected_binary_hash = hash_template_code(template.code());
    let expected_template_address =
        PublishedTemplateAddress::from_author_and_binary_hash(&public_key.to_byte_type(), &expected_binary_hash);

    let result = test.execute_expect_success(
        Transaction::builder_localnet()
            .pay_fee_from_component(account_address, 200_000u64)
            .publish_template(template.into_code())
            .build_and_seal(&account_key),
        vec![owner_proof],
    );

    assert!(matches!(result.finalize.result, TransactionResult::Accept(_)));

    let mut template_found = false;
    let diff = result.expect_success();
    diff.up_iter().for_each(|(substate_id, substate)| {
        if let SubstateValue::Template(curr_template) = substate.substate_value() &&
            curr_template.to_binary_hash() == expected_binary_hash
        {
            template_found = true;
            assert!(matches!(substate_id, SubstateId::Template(_)));
            if let SubstateId::Template(curr_template_addr) = substate_id {
                assert_eq!(expected_template_address, *curr_template_addr);
            }
        }
    });

    assert!(template_found);
}

#[test]
fn publish_template_invalid_binary() {
    let mut test = TemplateTest::new(CRATE_PATH, &[] as &[&str]);
    let (account_address, owner_proof, account_key, _) = test.create_funded_account_with_keypair();
    let result = test.execute_expect_failure(
        Transaction::builder_localnet()
            .pay_fee_from_component(account_address, 200_000u64)
            // Main intent instruction #1
            .publish_template(vec![1u8, 2, 3])
            .build_and_seal(&account_key),
        vec![owner_proof],
    );

    assert!(matches!(result, RejectReason::ExecutionFailure(_)));

    if let RejectReason::ExecutionFailure(error) = result {
        assert!(error.starts_with("At instruction #1: Load template error:"));
    }
}

#[test]
fn publish_template_too_big_binary() {
    let mut test = TemplateTest::new(CRATE_PATH, &[] as &[&str]);
    let (account_address, owner_proof, account_key, _) = test.create_funded_account_with_keypair();
    let random_wasm_binary = generate_random_binary(limits::ENGINE_LIMITS.max_template_binary_size_bytes + 1);
    let wasm_binary_size = random_wasm_binary.len();
    let reason = test.execute_expect_failure(
        Transaction::builder_localnet()
            .pay_fee_from_component(account_address, 200_000u64)
            // SAFETY: We are intentionally publishing an oversized binary to test size limits.
            .publish_template(unsafe { TemplateBlob::new_unchecked(random_wasm_binary) })
            .build_and_seal(&account_key),
        vec![owner_proof],
    );

    assert_reject_reason(reason, TransactionErrorKind::WasmBinaryTooBig {
        size: wasm_binary_size,
        max: limits::ENGINE_LIMITS.max_template_binary_size_bytes,
    });
}

fn generate_random_binary(size_in_bytes: usize) -> Vec<u8> {
    iter::repeat_with(random).take(size_in_bytes).collect()
}
