/*
 * AquaVM Workflow Engine
 *
 * Copyright (C) 2024 Fluence DAO
 *
 * 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 version 3 of the
 * License.
 *
 * 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/>.
 */

use air_interpreter_data::ExecutionTrace;
use air_test_utils::prelude::*;

use futures::FutureExt;
use pretty_assertions::assert_eq;

#[tokio::test]
#[ignore]
async fn new_with_global_streams_seq() {
    let set_variable_peer_id = "set_variable_peer_id";
    let local_vm_peer_id_1 = "local_vm_peer_id_1";
    let local_vm_peer_id_2 = "local_vm_peer_id_2";

    let mut local_vm_1 = create_avm(echo_call_service(), local_vm_peer_id_1).await;
    let mut local_vm_2 = create_avm(echo_call_service(), local_vm_peer_id_2).await;

    let variables_mapping = maplit::hashmap! {
        "1".to_string() => json!(1),
        "2".to_string() => json!(2),
    };
    let mut set_variable_vm = create_avm(
        set_variables_call_service(variables_mapping, VariableOptionSource::Argument(0)),
        set_variable_peer_id,
    )
    .await;

    let script = format!(
        r#"
            (seq
                (seq
                    (call "{set_variable_peer_id}" ("" "") ["1"] $stream)
                    (call "{set_variable_peer_id}" ("" "") ["2"] $stream)
                )
                (fold $stream i
                    (seq
                        (new $stream
                            (seq
                                (par
                                    (call "{local_vm_peer_id_1}" ("" "") [i] $stream)
                                    (next i)
                                )
                                (call "{local_vm_peer_id_1}" ("" "") [$stream])
                            )
                        )
                        (call "{local_vm_peer_id_2}" ("" "") [$stream])
                    )
                )
            )"#
    );

    let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", "");
    let vm_1_result = checked_call_vm!(local_vm_1, <_>::default(), &script, "", result.data);
    let vm_2_result = checked_call_vm!(local_vm_2, <_>::default(), &script, "", vm_1_result.data.clone());

    let vm_1_result = checked_call_vm!(
        local_vm_1,
        <_>::default(),
        &script,
        vm_1_result.data,
        vm_2_result.data.clone()
    );
    let vm_2_result = checked_call_vm!(local_vm_2, <_>::default(), script, vm_2_result.data, vm_1_result.data);

    let actual_trace = trace_from_result(&vm_2_result);
    let expected_trace = vec![
        stream!(1, 0, peer = set_variable_peer_id, args = ["1"]),
        stream!(2, 0, peer = set_variable_peer_id, args = ["2"]),
        executed_state::fold(vec![
            executed_state::subtrace_lore(0, SubTraceDesc::new(3.into(), 1), SubTraceDesc::new(7.into(), 2)),
            executed_state::subtrace_lore(1, SubTraceDesc::new(4.into(), 1), SubTraceDesc::new(5.into(), 2)),
        ]),
        stream!(1, 0, peer = local_vm_peer_id_1, args = [1]),
        stream!(2, 0, peer = local_vm_peer_id_1, args = [1]),
        scalar!(json!([2]), peer = local_vm_peer_id_1, args = [json!([2])]),
        scalar!(json!([1, 2]), peer = local_vm_peer_id_1, args = [json!([1, 2])]),
        scalar!(json!([1]), peer = local_vm_peer_id_1, args = [json!([1])]),
        scalar!(json!([1, 2]), peer = local_vm_peer_id_1, args = [json!([1, 2])]),
    ];
    assert_eq!(actual_trace, expected_trace);
}

#[tokio::test]
async fn several_restrictions() {
    let vm_peer_id = "vm_peer_id";
    let mut vm = create_avm(echo_call_service(), vm_peer_id).await;

    let script = format!(
        r#"
            (new $stream
                (seq
                    (new $stream
                        (call "{vm_peer_id}" ("" "") ["test"] $stream)
                    )
                    (seq
                        (canon "{vm_peer_id}" $stream #canon_stream)
                        (call "{vm_peer_id}" ("" "") [#canon_stream])
                    )
                )
            )"#
    );

    let result = checked_call_vm!(vm, <_>::default(), script, "", "");

    let actual_trace = trace_from_result(&result);
    let expected_trace = ExecutionTrace::from(vec![
        stream!("test", 0, peer = vm_peer_id, args = ["test"]),
        executed_state::canon(json!({
            "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
            "values": [

            ]
        })),
        unused!(json!([]), peer = vm_peer_id, args = [json!([])]),
    ]);
    assert_eq!(actual_trace, expected_trace);
}

#[tokio::test]
async fn check_influence_to_not_restricted() {
    let vm_peer_id = "vm_peer_id";
    let mut vm = create_avm(echo_call_service(), vm_peer_id).await;

    let script = format!(
        r#"
    (seq
        (new $a
            (seq
                (seq
                    (seq
                        (ap "push more" $a0)
                        (ap "push more" $a1)
                    )
                    (ap "more" $a)
                )
                (seq
                    (canon "{vm_peer_id}" $a #a)
                    (call "{vm_peer_id}" ("op" "identity") [#a] a-fix)
                )
            )
        )
        (seq
            (seq
                (seq
                    (canon "{vm_peer_id}" $a0 #a0)
                    (call "{vm_peer_id}" ("callbackSrv" "response") [#a0]) ;; should be non-empty
                )
                (seq
                    (canon "{vm_peer_id}" $a1 #a1)
                    (call "{vm_peer_id}" ("callbackSrv" "response") [#a1]) ;; should be non-empty
                )
            )
            (seq
                (seq
                    (canon "{vm_peer_id}" $a #aa)
                    (call "{vm_peer_id}" ("callbackSrv" "response") [#aa])  ;; should be empty
                )
                (call "{vm_peer_id}" ("callbackSrv" "response") [a-fix])  ;; should be empty
            )
        )
    )
    "#
    );

    let result = checked_call_vm!(vm, <_>::default(), script, "", "");

    let actual_trace = trace_from_result(&result);
    let expected_trace = ExecutionTrace::from(vec![
        executed_state::ap(0),
        executed_state::ap(0),
        executed_state::ap(0),
        executed_state::canon(json!(
            {
                "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
                "values": [
                    {
                        "result": "more",
                        "tetraplet": {"function_name": "", "lens": "", "peer_pk": "", "service_id": ""},
                        "trace_pos": 2
                    }
                ]
            }
        )),
        scalar!(
            json!(["more"]),
            peer = vm_peer_id,
            service = "op",
            function = "identity",
            args = [json!(["more"])]
        ),
        executed_state::canon(json!(
            {
                "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
                "values": [
                    {
                        "result": "push more",
                        "tetraplet": {"function_name": "", "lens": "", "peer_pk": "", "service_id": ""},
                        "trace_pos": 0
                    }
                ]
            }
        )),
        unused!(
            json!(["push more"]),
            peer = vm_peer_id,
            service = "callbackSrv",
            function = "response",
            args = [json!(["push more"])]
        ),
        executed_state::canon(json!({
            "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
            "values": [
                {
                    "result": "push more",
                    "tetraplet": {"function_name": "", "lens": "", "peer_pk": "", "service_id": ""},
                    "trace_pos": 1
                }
            ]
        })),
        unused!(
            json!(["push more"]),
            peer = vm_peer_id,
            service = "callbackSrv",
            function = "response",
            args = [json!(["push more"])]
        ),
        executed_state::canon(json!({
            "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
            "values": [
            ]
        })),
        unused!(
            json!([]),
            peer = vm_peer_id,
            service = "callbackSrv",
            function = "response",
            args = [json!([])]
        ),
        unused!(
            json!(["more"]),
            peer = vm_peer_id,
            service = "callbackSrv",
            function = "response",
            args = [json!(["more"])]
        ),
    ]);
    assert_eq!(actual_trace, expected_trace);
}

#[tokio::test]
async fn new_in_fold_with_ap() {
    let set_variable_peer_id = "set_variable_peer_id";
    let vm_peer_id = "vm_peer_id";

    let mut set_variable_vm = create_avm(set_variable_call_service(json!([1, 2, 3, 4, 5])), set_variable_peer_id).await;
    let mut vm = create_avm(echo_call_service(), vm_peer_id).await;

    let script = format!(
        r#"
        (seq
            (call "{set_variable_peer_id}" ("" "") [] iterable)
            (fold iterable x
                (seq
                    (new $s1
                        (seq
                            (ap "none" $s1)
                            (seq
                                (canon "{vm_peer_id}" $s1 #canon_s1)
                                (call "{vm_peer_id}" ("" "") [#canon_s1] s-fix1) ;; should contains only "none" on each iteration
                            )
                        )
                    )
                    (next x)
                )
            )
        )
            "#
    );

    let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", "");
    let result = checked_call_vm!(vm, <_>::default(), script, "", result.data);

    let actual_trace = trace_from_result(&result);
    let expected_trace = vec![
        scalar!(json!([1, 2, 3, 4, 5]), peer = set_variable_peer_id),
        executed_state::ap(0),
        executed_state::canon(json!({
            "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
            "values": [
                {
                    "result": "none",
                    "tetraplet": {"function_name": "", "lens": "", "peer_pk": "", "service_id": ""},
                    "trace_pos": 1
                }
            ]
        })),
        scalar!(json!(["none"]), peer = vm_peer_id, args = [json!(["none"])]),
        executed_state::ap(0),
        executed_state::canon(json!({
            "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
            "values": [
                {
                    "result": "none",
                    "tetraplet": {"function_name": "", "lens": "", "peer_pk": "", "service_id": ""},
                    "trace_pos": 4
                }
            ]
        })),
        scalar!(json!(["none"]), peer = vm_peer_id, args = [json!(["none"])]),
        executed_state::ap(0),
        executed_state::canon(json!({
            "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
            "values": [
                {
                    "result": "none",
                    "tetraplet": {"function_name": "", "lens": "", "peer_pk": "", "service_id": ""},
                    "trace_pos": 7
                }
            ]
        })),
        scalar!(json!(["none"]), peer = vm_peer_id, args = [json!(["none"])]),
        executed_state::ap(0),
        executed_state::canon(json!({
            "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
            "values": [
                {
                    "result": "none",
                    "tetraplet": {"function_name": "", "lens": "", "peer_pk": "", "service_id": ""},
                    "trace_pos": 10
                }
            ]
        })),
        scalar!(json!(["none"]), peer = vm_peer_id, args = [json!(["none"])]),
        executed_state::ap(0),
        executed_state::canon(json!({
            "tetraplet": {"function_name": "", "lens": "", "peer_pk": "vm_peer_id", "service_id": ""},
            "values": [
                {
                    "result": "none",
                    "tetraplet": {"function_name": "", "lens": "", "peer_pk": "", "service_id": ""},
                    "trace_pos": 13
                }
            ]
        })),
        scalar!(json!(["none"]), peer = vm_peer_id, args = [json!(["none"])]),
    ];
    assert_eq!(actual_trace, expected_trace);
}

#[tokio::test]
async fn new_with_streams_with_errors() {
    let fallible_peer_id = "fallible_peer_id";
    let mut fallible_vm = create_avm(fallible_call_service("service_id_1"), fallible_peer_id).await;

    let local_peer_id = "local_peer_id";
    let mut vm = create_avm(echo_call_service(), local_peer_id).await;

    let script = format!(
        r#"
            (seq
                (call "{local_peer_id}" ("" "") [1] $global_stream) ;; this stream should precense in a data
                (new $restricted_stream_1
                    (seq
                        (new $restricted_stream_2
                            (seq
                                (call "{local_peer_id}" ("" "") [2] $restricted_stream_2) ;; should have generation 1 in a data
                                (call "{fallible_peer_id}" ("service_id_1" "local_fn_name") [] result)
                            )
                        )
                        (call "{local_peer_id}" ("" "") [2] restricted_stream_1) ;; should have generation 0 in a data
                    )
                )
            )"#
    );

    let result = checked_call_vm!(vm, <_>::default(), &script, "", "");
    let result = call_vm!(fallible_vm, <_>::default(), script, "", result.data);

    let actual_trace = trace_from_result(&result);
    let expected_trace = vec![
        stream!(1, 0, peer = local_peer_id, args = [1]),
        stream!(2, 0, peer = local_peer_id, args = [2]),
        failed!(
            1,
            "failed result from fallible_call_service",
            peer = fallible_peer_id,
            service = "service_id_1",
            function = "local_fn_name"
        ),
    ];
    assert_eq!(actual_trace, expected_trace);
}

#[tokio::test]
async fn new_with_scalars_with_errors() {
    let set_variable_peer_id = "set_variable_peer_id";
    let variables_mapping = maplit::hashmap! {
        "global".to_string() => json!(1),
        "scoped".to_string() => json!(2),
    };
    let mut set_variable_vm = create_avm(
        set_variables_call_service(variables_mapping, VariableOptionSource::Argument(0)),
        set_variable_peer_id,
    )
    .await;

    let variable_receiver_peer_id = "variable_receiver_peer_id";
    let mut variable_receiver_vm = create_avm(echo_call_service(), variable_receiver_peer_id).await;

    let fallible_peer_id = "fallible_peer_id";
    let fallible_service_id = "fallible_service_id";
    let mut fallible_peer_vm = create_avm(fallible_call_service(fallible_service_id), fallible_peer_id).await;

    let script = format!(
        r#"
            (seq
                (seq
                    (call "{set_variable_peer_id}" ("" "") ["global"] scalar)
                    (xor
                        (new scalar
                            (seq
                                (call "{set_variable_peer_id}" ("" "") ["scoped"] scalar)
                                (seq
                                    (call "{variable_receiver_peer_id}" ("" "") [scalar])
                                    (call "{fallible_peer_id}" ("{fallible_service_id}" "") [])
                                )
                            )
                        )
                        (call "{variable_receiver_peer_id}" ("" "") [scalar])
                    )
                )
                (call "{variable_receiver_peer_id}" ("" "") [scalar])
            )"#
    );

    let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", "");
    let result = checked_call_vm!(variable_receiver_vm, <_>::default(), &script, "", result.data);
    let result = checked_call_vm!(fallible_peer_vm, <_>::default(), &script, "", result.data);
    let result = checked_call_vm!(variable_receiver_vm, <_>::default(), &script, "", result.data);
    let actual_trace = trace_from_result(&result);

    let msg = r#"failed result from fallible_call_service"#;
    let expected_trace = ExecutionTrace::from(vec![
        scalar!(1, peer = set_variable_peer_id, args = ["global"]),
        scalar!(2, peer = set_variable_peer_id, args = ["scoped"]),
        unused!(2, peer = variable_receiver_peer_id, args = [2]),
        failed!(1, msg, peer = fallible_peer_id, service = fallible_service_id),
        unused!(1, peer = variable_receiver_peer_id, args = [1]),
        unused!(1, peer = variable_receiver_peer_id, args = [1]),
    ]);
    assert_eq!(actual_trace, expected_trace);
}

#[tokio::test]
async fn new_with_global_scalars() {
    let set_variable_peer_id = "set_variable_peer_id";
    let variables_mapping = maplit::hashmap! {
        "global".to_string() => json!(1),
        "scoped".to_string() => json!(2),
    };
    let mut set_variable_vm = create_avm(
        set_variables_call_service(variables_mapping, VariableOptionSource::Argument(0)),
        set_variable_peer_id,
    )
    .await;

    let variable_receiver_peer_id = "variable_receiver_peer_id";
    let mut variable_receiver = create_avm(echo_call_service(), variable_receiver_peer_id).await;

    let script = format!(
        r#"
            (seq
                (seq
                    (call "{set_variable_peer_id}" ("" "") ["global"] scalar)
                    (new scalar
                        (seq
                            (call "{set_variable_peer_id}" ("" "") ["scoped"] scalar)
                            (call "{variable_receiver_peer_id}" ("" "") [scalar])
                        )
                    )
                )
                (call "{variable_receiver_peer_id}" ("" "") [scalar])
            )"#
    );

    let result = checked_call_vm!(set_variable_vm, <_>::default(), &script, "", "");
    let result = checked_call_vm!(variable_receiver, <_>::default(), &script, "", result.data);
    let actual_trace = trace_from_result(&result);

    let expected_trace = vec![
        scalar!(1, peer = set_variable_peer_id, args = ["global"]),
        scalar!(2, peer = set_variable_peer_id, args = ["scoped"]),
        unused!(2, peer = variable_receiver_peer_id, args = [2]),
        unused!(1, peer = variable_receiver_peer_id, args = [1]),
    ];
    assert_eq!(actual_trace, expected_trace);
}

const GET_ITERABLE_ACTION_NAME: &str = "get_iterable_action_name";
const OUTSIDE_ACTION_NAME: &str = "outside_new";
const INSIDE_ACTION_NAME: &str = "inside_new";
const OUTPUT_ACTION_NAME: &str = "output";

fn prepare_new_test_call_service() -> CallServiceClosure<'static> {
    let outside_new_id = std::cell::Cell::new(0u32);
    let inside_new_id = std::cell::Cell::new(10u32);

    Box::new(move |mut params| {
        let action = params.arguments.remove(0);
        let action = action.as_str().unwrap();
        let result = match action {
            GET_ITERABLE_ACTION_NAME => CallServiceResult::ok(json!([1, 2, 3])),
            OUTSIDE_ACTION_NAME => {
                let outside_result = outside_new_id.get();
                outside_new_id.set(outside_result + 1);
                CallServiceResult::ok(json!(outside_result))
            }
            INSIDE_ACTION_NAME => {
                let inside_result = inside_new_id.get();
                inside_new_id.set(inside_result + 1);
                CallServiceResult::ok(json!(inside_result))
            }
            OUTPUT_ACTION_NAME => {
                let second_argument = params.arguments.remove(0);
                CallServiceResult::ok(second_argument)
            }
            action_name => {
                println!("unknown action: {action_name:?}");
                CallServiceResult::err(1, json!("no such action"))
            }
        };
        async move { result }.boxed_local()
    })
}

#[tokio::test]
async fn new_with_scalars_in_lfold_with_outside_next() {
    let test_peer_id = "test_peer_id";

    let test_call_service = prepare_new_test_call_service();
    let mut test_vm = create_avm(test_call_service, test_peer_id).await;

    let script = format!(
        r#"
    (seq
        (call "{test_peer_id}" ("" "") ["{GET_ITERABLE_ACTION_NAME}"] iterable)
        (fold iterable iterator
            (seq
                (seq
                    (seq
                        (call "{test_peer_id}" ("" "") ["{OUTSIDE_ACTION_NAME}"] scalar)
                        (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
                    )
                    (new scalar
                        (seq
                            (call "{test_peer_id}" ("" "") ["{INSIDE_ACTION_NAME}"] scalar)
                            (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
                        )
                    )
                )
                (seq
                    (next iterator)
                    (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
                )
            )
        )
    )
    "#
    );

    let result = checked_call_vm!(test_vm, <_>::default(), &script, "", "");
    let actual_trace = trace_from_result(&result);

    let expected_trace = ExecutionTrace::from(vec![
        scalar!(json!([1, 2, 3]), peer = test_peer_id, args = [GET_ITERABLE_ACTION_NAME]),
        scalar!(0, peer = test_peer_id, args = [json!(OUTSIDE_ACTION_NAME)]),
        unused!(0, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(0)]),
        scalar!(10, peer = test_peer_id, args = [INSIDE_ACTION_NAME]),
        unused!(10, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(10)]),
        scalar!(1, peer = test_peer_id, args = [json!(OUTSIDE_ACTION_NAME)]),
        unused!(1, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(1)]),
        scalar!(11, peer = test_peer_id, args = [INSIDE_ACTION_NAME]),
        unused!(11, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(11)]),
        scalar!(2, peer = test_peer_id, args = [json!(OUTSIDE_ACTION_NAME)]),
        unused!(2, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(2)]),
        scalar!(12, peer = test_peer_id, args = [INSIDE_ACTION_NAME]),
        unused!(12, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(12)]),
        unused!(2, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(2)]),
        unused!(1, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(1)]),
        unused!(0, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(0)]),
    ]);
    assert_eq!(actual_trace, expected_trace);
}

#[tokio::test]
async fn new_with_scalars_in_rfold_with_outside_next() {
    let test_peer_id = "test_peer_id";

    let test_call_service = prepare_new_test_call_service();
    let mut test_vm = create_avm(test_call_service, test_peer_id).await;

    let script = format!(
        r#"
    (seq
        (call "{test_peer_id}" ("" "") ["{GET_ITERABLE_ACTION_NAME}"] iterable)
        (fold iterable iterator
            (seq
                (next iterator)
                (seq
                    (seq
                        (call "{test_peer_id}" ("" "") ["{OUTSIDE_ACTION_NAME}"] scalar)
                        (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
                    )
                    (seq
                        (new scalar
                            (seq
                                (call "{test_peer_id}" ("" "") ["{INSIDE_ACTION_NAME}"] scalar)
                                (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
                            )
                        )
                        (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
                    )
                )
            )
        )
    )
    "#
    );

    let result = checked_call_vm!(test_vm, <_>::default(), &script, "", "");
    let actual_trace = trace_from_result(&result);

    let expected_trace = ExecutionTrace::from(vec![
        scalar!(json!([1, 2, 3]), peer = test_peer_id, args = [GET_ITERABLE_ACTION_NAME]),
        scalar!(0, peer = test_peer_id, args = [OUTSIDE_ACTION_NAME]),
        unused!(0, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(0)]),
        scalar!(10, peer = test_peer_id, args = [json!(INSIDE_ACTION_NAME)]),
        unused!(10, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(10)]),
        unused!(0, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(0)]),
        scalar!(1, peer = test_peer_id, args = [OUTSIDE_ACTION_NAME]),
        unused!(1, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(1)]),
        scalar!(11, peer = test_peer_id, args = [json!(INSIDE_ACTION_NAME)]),
        unused!(11, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(11)]),
        unused!(1, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(1)]),
        scalar!(2, peer = test_peer_id, args = [OUTSIDE_ACTION_NAME]),
        unused!(2, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(2)]),
        scalar!(12, peer = test_peer_id, args = [json!(INSIDE_ACTION_NAME)]),
        unused!(12, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(12)]),
        unused!(2, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(2)]),
    ]);
    assert_eq!(actual_trace, expected_trace);
}

#[tokio::test]
async fn new_with_scalars_in_fold_with_inside_next() {
    let test_peer_id = "test_peer_id";

    let test_call_service = prepare_new_test_call_service();
    let mut test_vm = create_avm(test_call_service, test_peer_id).await;

    let script = format!(
        r#"
    (seq
        (call "{test_peer_id}" ("" "") ["{GET_ITERABLE_ACTION_NAME}"] iterable)
        (fold iterable iterator
            (seq
                (seq
                    (seq
                        (call "{test_peer_id}" ("" "") ["{OUTSIDE_ACTION_NAME}"] scalar)
                        (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
                    )
                    (new scalar
                        (seq
                            (call "{test_peer_id}" ("" "") ["{INSIDE_ACTION_NAME}"] scalar)
                            (seq
                                (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
                                (seq
                                    (next iterator)
                                    (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
                                )
                            )
                        )
                    )
                )
                (call "{test_peer_id}" ("" "") ["{OUTPUT_ACTION_NAME}" scalar])
            )
        )
    )
    "#
    );

    let result = checked_call_vm!(test_vm, <_>::default(), &script, "", "");
    let actual_trace = trace_from_result(&result);

    let expected_trace = ExecutionTrace::from(vec![
        scalar!(json!([1, 2, 3]), peer = test_peer_id, args = [GET_ITERABLE_ACTION_NAME]),
        scalar!(0, peer = test_peer_id, args = [OUTSIDE_ACTION_NAME]),
        unused!(0, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(0)]),
        scalar!(10, peer = test_peer_id, args = [json!(INSIDE_ACTION_NAME)]),
        unused!(10, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(10)]),
        scalar!(1, peer = test_peer_id, args = [OUTSIDE_ACTION_NAME]),
        unused!(1, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(1)]),
        scalar!(11, peer = test_peer_id, args = [json!(INSIDE_ACTION_NAME)]),
        unused!(11, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(11)]),
        scalar!(2, peer = test_peer_id, args = [OUTSIDE_ACTION_NAME]),
        unused!(2, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(2)]),
        scalar!(12, peer = test_peer_id, args = [json!(INSIDE_ACTION_NAME)]),
        unused!(12, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(12)]),
        unused!(12, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(12)]),
        unused!(2, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(2)]),
        unused!(11, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(11)]),
        unused!(1, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(1)]),
        unused!(10, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(10)]),
        unused!(0, peer = test_peer_id, args = [json!(OUTPUT_ACTION_NAME), json!(0)]),
    ]);
    assert_eq!(actual_trace, expected_trace);
}
