use serde::{
    ser::{SerializeMap, SerializeStruct},
    Serialize, Serializer,
};
use std::{
    collections::HashMap,
    future::{self, Future},
    hash::Hash,
    pin::Pin,
};
use uuid::Uuid;

use hcl_edit::{expr::Expression, structure::Block, Span};
use indexmap::IndexMap;

use crate::{
    constants::{SIGNED_MESSAGE_BYTES, SIGNED_TRANSACTION_BYTES},
    helpers::hcl::{
        collect_constructs_references_from_expression, visit_optional_untyped_attribute,
    },
};
use crate::{helpers::hcl::get_object_expression_key, types::stores::ValueStore};

use super::{
    diagnostics::Diagnostic,
    frontend::{
        ActionItemRequest, ActionItemRequestType, ActionItemRequestUpdate, ActionItemResponse,
        ActionItemResponseType, ActionItemStatus, Actions, BlockEvent, ProvideInputRequest,
        ProvidedInputResponse, ReviewedInputResponse,
    },
    signers::{
        consolidate_nested_execution_result, consolidate_signer_activate_future_result,
        consolidate_signer_future_result, return_synchronous, PrepareSignedNestedExecutionResult,
        SignerActionsFutureResult, SignerInstance, SignerSignFutureResult, SignersState,
    },
    stores::ValueMap,
    types::{ObjectProperty, RunbookSupervisionContext, Type, Value},
    ConstructDid, Did, EvaluatableInput, PackageId, WithEvaluatableInputs,
};

#[derive(Clone, Debug)]
pub struct CommandExecutionResult {
    pub outputs: HashMap<String, Value>, // todo: change value to be Result<Value, Diagnostic>
}

impl Serialize for CommandExecutionResult {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(self.outputs.len()))?;
        for (k, v) in self.outputs.iter() {
            map.serialize_entry(&k, &v)?;
        }
        map.end()
    }
}
impl CommandExecutionResult {
    pub fn new() -> Self {
        Self { outputs: HashMap::new() }
    }

    pub fn append(&mut self, other: &mut CommandExecutionResult) {
        for (key, value) in other.outputs.drain() {
            self.outputs.insert(key, value);
        }
    }

    pub fn from_value_store(store: &ValueStore) -> Self {
        let mut outputs = HashMap::new();
        for (key, value) in store.iter() {
            outputs.insert(key.clone(), value.clone());
        }
        Self { outputs }
    }

    pub fn insert(&mut self, key: &str, value: Value) {
        self.outputs.insert(key.into(), value);
    }

    /// Applies each of the keys/values of `other` onto `self`
    pub fn apply(&mut self, other: &CommandExecutionResult) {
        for (key, value) in other.outputs.iter() {
            self.outputs.insert(key.clone(), value.clone());
        }
    }
}

#[derive(Clone, Debug)]
pub struct DependencyExecutionResultCache {
    cache: HashMap<ConstructDid, Result<CommandExecutionResult, Diagnostic>>,
}
impl DependencyExecutionResultCache {
    pub fn new() -> Self {
        Self { cache: HashMap::new() }
    }

    pub fn get(
        &self,
        construct_did: &ConstructDid,
    ) -> Option<&Result<CommandExecutionResult, Diagnostic>> {
        self.cache.get(construct_did)
    }

    pub fn insert(
        &mut self,
        construct_did: ConstructDid,
        result: Result<CommandExecutionResult, Diagnostic>,
    ) {
        self.cache.insert(construct_did, result);
    }

    /// If `self` does not contain `construct_did`, insert `construct_did` with `other_result`.
    /// If `self` contains `construct_did`, apply `other_result` onto the existing value by only inserting
    /// each of the keys of `other_result` into `self`'s results at `construct_did`.
    pub fn merge(
        &mut self,
        construct_did: &ConstructDid,
        other_result: &CommandExecutionResult,
    ) -> Result<(), Diagnostic> {
        match self.cache.get_mut(&construct_did) {
            Some(Ok(result)) => {
                result.apply(&other_result);
            }
            Some(Err(e)) => return Err(e.clone()),
            None => {
                self.cache.insert(construct_did.clone(), Ok(other_result.clone()));
            }
        }
        Ok(())
    }
}

#[derive(Clone, Debug)]
pub struct CommandInputsEvaluationResult {
    pub inputs: ValueStore,
    pub unevaluated_inputs: UnevaluatedInputsMap,
}

impl CommandInputsEvaluationResult {
    pub fn get_if_not_unevaluated<'a, ReturnType>(
        &'a self,
        key: &str,
        getter: impl Fn(&'a ValueStore, &str) -> Result<ReturnType, Diagnostic>,
    ) -> Result<ReturnType, Diagnostic> {
        self.unevaluated_inputs.check_for_diagnostic(key)?;
        getter(&self.inputs, key)
    }
}

#[derive(Clone, Debug)]
pub struct UnevaluatedInputsMap {
    pub map: IndexMap<String, Option<Diagnostic>>,
}
impl UnevaluatedInputsMap {
    pub fn new() -> Self {
        Self { map: IndexMap::new() }
    }

    pub fn insert(&mut self, key: String, value: Option<Diagnostic>) {
        self.map.insert(key, value);
    }
    pub fn contains_key(&self, key: &str) -> bool {
        self.map.contains_key(key)
    }

    pub fn check_for_diagnostic(&self, key: &str) -> Result<(), Diagnostic> {
        match self.map.get(key) {
            Some(diag) => match diag {
                Some(diag) => Err(diag.clone()),
                None => {
                    Err(Diagnostic::error_from_string(format!("input '{}' was not evaluated", key)))
                }
            },
            _ => Ok(()),
        }
    }
    pub fn merge(&mut self, other: &UnevaluatedInputsMap) {
        for (key, value) in other.map.iter() {
            self.map.insert(key.clone(), value.clone());
        }
    }
}

impl Serialize for CommandInputsEvaluationResult {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(self.inputs.len()))?;
        for (k, v) in self.inputs.iter() {
            map.serialize_entry(&k, &v)?;
        }
        map.end()
    }
}

impl CommandInputsEvaluationResult {
    pub fn new(name: &str, defaults: &ValueMap) -> Self {
        Self {
            inputs: ValueStore::new(&format!("{name}_inputs"), &Did::zero())
                .with_defaults(defaults),
            unevaluated_inputs: UnevaluatedInputsMap::new(),
        }
    }

    pub fn insert(&mut self, key: &str, value: Value) {
        self.inputs.insert(key, value);
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct CommandInput {
    pub name: String,
    pub documentation: String,
    pub typing: Type,
    pub optional: bool,
    pub tainting: bool,
    pub check_required: bool,
    pub check_performed: bool,
    pub sensitive: bool,
    pub internal: bool,
}
impl EvaluatableInput for CommandInput {
    fn optional(&self) -> bool {
        self.optional
    }
    fn typing(&self) -> &Type {
        &self.typing
    }
    fn name(&self) -> String {
        self.name.clone()
    }
}

impl CommandInput {
    pub fn as_object(&self) -> Option<&Vec<ObjectProperty>> {
        self.typing.as_object()
    }
    pub fn as_array(&self) -> Option<&Box<Type>> {
        self.typing.as_array()
    }
    pub fn as_action(&self) -> Option<&String> {
        self.typing.as_action()
    }
    pub fn as_map(&self) -> Option<&Vec<ObjectProperty>> {
        self.typing.as_map()
    }
    pub fn check_value(&self, value: &Value) -> Result<(), Diagnostic> {
        self.typing.check_value(value).map_err(|e| {
            Diagnostic::error_from_string(format!("error in input '{}': {}", self.name, e.message))
        })
    }
}

impl Serialize for CommandInput {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut ser = serializer.serialize_struct("CommandInput", 4)?;
        ser.serialize_field("name", &self.name)?;
        ser.serialize_field("documentation", &self.documentation)?;
        ser.serialize_field("typing", &self.typing)?;
        ser.serialize_field("optional", &self.optional)?;
        ser.end()
    }
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CommandOutput {
    pub name: String,
    pub documentation: String,
    pub typing: Type,
}

impl Serialize for CommandOutput {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut ser = serializer.serialize_struct("CommandOutput", 4)?;
        ser.serialize_field("name", &self.name)?;
        ser.serialize_field("documentation", &self.documentation)?;
        ser.serialize_field("typing", &self.typing)?;
        ser.end()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum CommandId {
    Action(String),
}

impl CommandId {
    pub fn to_string(&self) -> String {
        match &self {
            &CommandId::Action(id) => format!("action::{id}"),
        }
    }

    pub fn action_name(&self) -> String {
        match &self {
            &CommandId::Action(id) => format!("{id}"),
        }
    }
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum PreCommandSpecification {
    Atomic(CommandSpecification),
    Composite(CompositeCommandSpecification),
}

impl PreCommandSpecification {
    pub fn expect_atomic_specification(&self) -> &CommandSpecification {
        match &self {
            PreCommandSpecification::Atomic(spec) => spec,
            _ => unreachable!(),
        }
    }
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CommandSpecification {
    pub name: String,
    pub matcher: String,
    pub documentation: String,
    pub accepts_arbitrary_inputs: bool,
    pub create_output_for_each_input: bool,
    pub create_critical_output: Option<String>,
    pub update_addon_defaults: bool,
    pub implements_signing_capability: bool,
    pub implements_background_task_capability: bool,
    pub example: String,
    pub default_inputs: Vec<CommandInput>,
    pub inputs: Vec<CommandInput>,
    pub outputs: Vec<CommandOutput>,
    pub inputs_post_processing_closure: InputsPostProcessingClosure,
    pub check_instantiability: InstantiabilityChecker,
    pub check_executability: CommandCheckExecutabilityClosure,
    pub prepare_nested_execution: CommandPrepareNestedExecution,
    pub run_execution: CommandExecutionClosure,
    pub check_signed_executability: CommandCheckSignedExecutabilityClosure,
    pub prepare_signed_nested_execution: CommandSignedPrepareNestedExecution,
    pub run_signed_execution: CommandSignedExecutionClosure,
    pub build_background_task: CommandBackgroundTaskExecutionClosure,
    pub aggregate_nested_execution_results: CommandAggregateNestedExecutionResults,
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CompositeCommandSpecification {
    pub name: String,
    pub matcher: String,
    pub documentation: String,
    pub parts: Vec<PreCommandSpecification>,
    pub default_inputs: Vec<CommandInput>,
    pub router: CommandRouter,
    pub example: String,
}

impl CommandSpecification {
    pub fn default_inputs() -> Vec<CommandInput> {
        vec![
            CommandInput {
                name: "description".into(),
                documentation: "Allows you to describe and comment steps of your runbook".into(),
                typing: Type::string(),
                optional: true,
                tainting: true,
                internal: false,
                check_performed: false,
                check_required: false,
                sensitive: false,
            },
            CommandInput {
                name: "labels".into(),
                documentation: "Allows you to label steps of your runbook".into(),
                typing: Type::array(Type::string()),
                optional: true,
                tainting: true,
                internal: false,
                check_performed: false,
                check_required: false,
                sensitive: false,
            },
            CommandInput {
                name: "environments".into(),
                documentation: "Only enable command for given environments (default: all)".into(),
                typing: Type::array(Type::string()),
                optional: true,
                tainting: true,
                internal: false,
                check_performed: false,
                check_required: false,
                sensitive: false,
            },
            CommandInput {
                name: "sensitive".into(),
                documentation: "Never include value in logs".into(),
                typing: Type::array(Type::string()),
                optional: true,
                tainting: true,
                internal: true,
                check_performed: false,
                check_required: false,
                sensitive: false,
            },
            CommandInput {
                name: "group".into(),
                documentation: "Name used for grouping commands together".into(),
                typing: Type::array(Type::string()),
                optional: true,
                tainting: true,
                internal: true,
                check_performed: false,
                check_required: false,
                sensitive: false,
            },
            CommandInput {
                name: "depends_on".into(),
                documentation: "Name used for grouping commands together".into(),
                typing: Type::array(Type::string()),
                optional: true,
                tainting: false,
                internal: false,
                check_performed: false,
                check_required: false,
                sensitive: false,
            },
        ]
    }
}

impl Serialize for CommandSpecification {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut ser = serializer.serialize_struct("CommandSpecification", 6)?;
        ser.serialize_field("id", &self.matcher)?;
        ser.serialize_field("name", &self.name)?;
        ser.serialize_field("documentation", &self.documentation)?;
        ser.serialize_field("inputs", &self.inputs)?;
        ser.serialize_field("outputs", &self.outputs)?;
        ser.serialize_field("example", &self.example)?;
        ser.end()
    }
}

impl Serialize for CompositeCommandSpecification {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // todo
        let mut ser = serializer.serialize_struct("CompositeCommandSpecification", 2)?;
        ser.serialize_field("name", &self.name)?;
        ser.serialize_field("documentation", &self.documentation)?;
        ser.end()
    }
}

pub type InstantiabilityChecker = fn(&CommandSpecification, Vec<Type>) -> Result<Type, Diagnostic>;

pub type InputsPostProcessingClosure =
    fn(&CommandSpecification, CommandInputsEvaluationResult) -> InputsPostProcessingFutureResult;
pub type InputsPostProcessingFutureResult = Result<InputsPostProcessingFuture, Diagnostic>;
pub type InputsPostProcessingFuture =
    Pin<Box<dyn Future<Output = Result<CommandInputsEvaluationResult, Diagnostic>> + Send>>;

pub type CommandExecutionFutureResult = Result<CommandExecutionFuture, Diagnostic>;
pub type CommandExecutionFuture =
    Pin<Box<dyn Future<Output = Result<CommandExecutionResult, Diagnostic>> + Send>>;

pub type CommandExecutionClosure = Box<
    fn(
        &ConstructDid,
        &CommandSpecification,
        &ValueStore,
        &channel::Sender<BlockEvent>,
    ) -> CommandExecutionFutureResult,
>;

pub type CommandBackgroundTaskExecutionClosure = Box<
    fn(
        &ConstructDid,
        &CommandSpecification,
        &ValueStore,
        &ValueStore,
        &channel::Sender<BlockEvent>,
        &Uuid,
        &RunbookSupervisionContext,
    ) -> CommandExecutionFutureResult,
>;

pub type CommandAggregateNestedExecutionResults = fn(
    &ConstructDid,
    &Vec<(ConstructDid, ValueStore)>,
    &Vec<CommandExecutionResult>,
) -> Result<CommandExecutionResult, Diagnostic>;

pub type CommandSignedExecutionClosure = Box<
    fn(
        &ConstructDid,
        &CommandSpecification,
        &ValueStore,
        &channel::Sender<BlockEvent>,
        &HashMap<ConstructDid, SignerInstance>,
        SignersState,
    ) -> SignerSignFutureResult,
>;

type CommandRouter =
    fn(&String, &String, &Vec<PreCommandSpecification>) -> Result<Vec<String>, Diagnostic>;

pub type CommandCheckExecutabilityClosure = fn(
    &ConstructDid,
    &str,
    &CommandSpecification,
    &ValueStore,
    &RunbookSupervisionContext,
) -> Result<Actions, Diagnostic>;

pub type CommandCheckSignedExecutabilityClosure = fn(
    &ConstructDid,
    &str,
    &CommandSpecification,
    &ValueStore,
    &RunbookSupervisionContext,
    &HashMap<ConstructDid, SignerInstance>,
    SignersState,
) -> SignerActionsFutureResult;

pub type CommandSignedPrepareNestedExecution = fn(
    &ConstructDid,
    &str,
    &ValueStore,
    &HashMap<ConstructDid, SignerInstance>,
    SignersState,
) -> PrepareSignedNestedExecutionResult;

pub type CommandPrepareNestedExecution =
    fn(&ConstructDid, &str, &ValueStore) -> Result<Vec<(ConstructDid, ValueStore)>, Diagnostic>;

pub fn return_synchronous_result(
    res: Result<CommandExecutionResult, Diagnostic>,
) -> CommandExecutionFutureResult {
    Ok(Box::pin(future::ready(res)))
}

pub fn return_synchronous_ok(res: CommandExecutionResult) -> CommandExecutionFutureResult {
    return_synchronous_result(Ok(res))
}

pub fn return_synchronous_err(diag: Diagnostic) -> CommandExecutionFutureResult {
    return_synchronous_result(Err(diag))
}

pub trait CompositeCommandImplementation {
    fn router(
        _first_input_body: &String,
        _command_instance_name: &String,
        _parts: &Vec<PreCommandSpecification>,
    ) -> Result<Vec<String>, Diagnostic>;
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CommandInstanceType {
    Variable,
    Output,
    Action(String),
    Prompt,
    Module,
    Addon,
}

impl CommandInstanceType {
    pub fn to_ident(&self) -> &str {
        match self {
            CommandInstanceType::Variable => "variable",
            CommandInstanceType::Output => "output",
            CommandInstanceType::Action(_) => "action",
            CommandInstanceType::Prompt => "prompt",
            CommandInstanceType::Module => "module",
            CommandInstanceType::Addon => "addon",
        }
    }
}

#[derive(Debug, Clone)]
pub struct CommandInstance {
    pub specification: CommandSpecification,
    pub name: String,
    pub block: Block,
    pub package_id: PackageId,
    pub namespace: String,
    pub typing: CommandInstanceType,
}
pub enum CommandExecutionStatus {
    Complete(Result<CommandExecutionResult, Diagnostic>),
    NeedsAsyncRequest,
}

impl Serialize for CommandInstance {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut ser = serializer.serialize_struct("CommandInstance", 6)?;
        ser.serialize_field("specification", &self.specification)?;
        ser.serialize_field("name", &self.name)?;
        ser.serialize_field("packageUuid", &self.package_id.did())?;
        ser.serialize_field("namespace", &self.namespace)?;
        ser.serialize_field("typing", &self.typing)?;
        ser.end()
    }
}

impl WithEvaluatableInputs for CommandInstance {
    fn name(&self) -> String {
        self.name.clone()
    }
    /// Checks the `CommandInstance` HCL Block for an attribute named `input.name`
    fn get_expression_from_input(&self, input_name: &str) -> Option<Expression> {
        visit_optional_untyped_attribute(&input_name, &self.block)
    }

    fn get_blocks_for_map(
        &self,
        input_name: &str,
        input_typing: &Type,
        input_optional: bool,
    ) -> Result<Option<Vec<Block>>, Vec<Diagnostic>> {
        let mut entries = vec![];

        match &input_typing {
            Type::Map(_) => {
                for block in self.block.body.get_blocks(&input_name) {
                    entries.push(block.clone());
                }
            }
            _ => {
                unreachable!()
            }
        };
        if entries.is_empty() {
            if !input_optional {
                return Err(vec![Diagnostic::error_from_string(format!(
                    "command '{}' (type '{}') is missing value for object '{}'",
                    self.name, self.specification.matcher, input_name
                ))]);
            } else {
                return Ok(None);
            }
        }
        Ok(Some(entries))
    }

    fn get_expression_from_block(
        &self,
        block: &Block,
        prop: &ObjectProperty,
    ) -> Option<Expression> {
        visit_optional_untyped_attribute(&prop.name, &block)
    }

    fn get_expression_from_object(
        &self,
        input_name: &str,
        input_typing: &Type,
    ) -> Result<Option<Expression>, Vec<Diagnostic>> {
        match &input_typing {
            Type::Object(_) => Ok(visit_optional_untyped_attribute(&input_name, &self.block)),
            _ => Err(vec![Diagnostic::error_from_string(format!(
                "command '{}' (type '{}') expected object for input '{}'",
                self.name, self.specification.matcher, input_name
            ))]),
        }
    }

    fn get_expression_from_object_property(
        &self,
        input_name: &str,
        prop: &ObjectProperty,
    ) -> Option<Expression> {
        let expr = visit_optional_untyped_attribute(&input_name, &self.block);
        match expr {
            Some(expr) => {
                let object_expr = expr.as_object().unwrap();
                let expr_res = get_object_expression_key(object_expr, &prop.name);
                match expr_res {
                    Some(expression) => Some(expression.expr().clone()),
                    None => None,
                }
            }
            None => None,
        }
    }
    fn spec_inputs(&self) -> Vec<impl EvaluatableInput> {
        self.specification.inputs.iter().map(|x| x.clone()).collect()
    }
}

impl CommandInstance {
    pub async fn post_process_inputs_evaluations(
        &self,
        inputs_evaluation: CommandInputsEvaluationResult,
    ) -> Result<CommandInputsEvaluationResult, Diagnostic> {
        let spec = &self.specification;
        let future = (self.specification.inputs_post_processing_closure)(spec, inputs_evaluation)?;
        let res = future.await?;
        Ok(res)
    }

    pub fn get_expressions_referencing_commands_from_inputs(
        &self,
    ) -> Result<Vec<(Option<&CommandInput>, Expression)>, String> {
        let mut expressions = vec![];
        for input in self.specification.inputs.iter() {
            match input.typing {
                Type::Map(ref props) => {
                    for block in self.block.body.get_blocks(&input.name) {
                        for prop in props.iter() {
                            let res = visit_optional_untyped_attribute(&prop.name, &block);
                            if let Some(expr) = res {
                                let mut references = vec![];
                                collect_constructs_references_from_expression(
                                    &expr,
                                    Some(input),
                                    &mut references,
                                );
                                expressions.append(&mut references);
                            }
                        }
                    }
                }
                Type::Object(ref props) => {
                    let res = visit_optional_untyped_attribute(&input.name, &self.block);
                    if let Some(expr) = res {
                        let mut references = vec![];
                        collect_constructs_references_from_expression(
                            &expr,
                            Some(input),
                            &mut references,
                        );
                        expressions.append(&mut references);
                    }
                    for prop in props.iter() {
                        let mut blocks_iter = self.block.body.get_blocks(&input.name);
                        while let Some(block) = blocks_iter.next() {
                            let res = visit_optional_untyped_attribute(&prop.name, &block);
                            if let Some(expr) = res {
                                let mut references = vec![];
                                collect_constructs_references_from_expression(
                                    &expr,
                                    Some(input),
                                    &mut references,
                                );
                                expressions.append(&mut references);
                            }
                        }
                    }
                }
                _ => {
                    let res = visit_optional_untyped_attribute(&input.name, &self.block);
                    if let Some(expr) = res {
                        let mut references = vec![];
                        collect_constructs_references_from_expression(
                            &expr,
                            Some(input),
                            &mut references,
                        );
                        expressions.append(&mut references);
                    }
                }
            }
        }
        if self.specification.accepts_arbitrary_inputs {
            for attribute in self.block.body.attributes() {
                let mut references = vec![];
                collect_constructs_references_from_expression(
                    &attribute.value,
                    None,
                    &mut references,
                );
                expressions.append(&mut references);
            }
        }
        Ok(expressions)
    }

    pub fn get_group(&self) -> String {
        let Some(group) = self.block.body.get_attribute("group") else {
            return format!("{} Review", self.specification.name.to_string());
        };
        group.value.to_string()
    }

    pub fn check_executability(
        &mut self,
        construct_did: &ConstructDid,
        nested_evaluation_values: &ValueStore,
        evaluated_inputs: &mut CommandInputsEvaluationResult,
        _signer_instances: &mut HashMap<ConstructDid, SignerInstance>,
        action_item_response: &Option<&Vec<ActionItemResponse>>,
        supervision_context: &RunbookSupervisionContext,
    ) -> Result<Actions, Diagnostic> {
        let mut values = ValueStore::new(
            &format!("{}_inputs", self.specification.matcher),
            &construct_did.value(),
        )
        .with_defaults(&evaluated_inputs.inputs.defaults)
        .append_inputs(&nested_evaluation_values.inputs);

        let mut consolidated_actions = Actions::none();
        match action_item_response {
            Some(responses) => {
                responses.into_iter().for_each(|ActionItemResponse { action_item_id, payload }| {
                    match payload {
                        ActionItemResponseType::ReviewInput(ReviewedInputResponse {
                            input_name,
                            value_checked,
                            ..
                        }) => {
                            for input in self.specification.inputs.iter_mut() {
                                if &input.name == input_name {
                                    input.check_performed = value_checked.clone();
                                    break;
                                }
                            }
                        }
                        ActionItemResponseType::ProvideInput(ProvidedInputResponse {
                            input_name,
                            updated_value,
                        }) => {
                            evaluated_inputs.inputs.insert(&input_name, updated_value.clone());

                            let action_item_update =
                                ActionItemRequestUpdate::from_id(&action_item_id)
                                    .set_type(ActionItemRequestType::ProvideInput(
                                        ProvideInputRequest {
                                            default_value: Some(updated_value.clone()),
                                            input_name: input_name.clone(),
                                            typing: updated_value.get_type(),
                                        },
                                    ))
                                    .set_status(ActionItemStatus::Success(None));
                            consolidated_actions.push_action_item_update(action_item_update);

                            for input in self.specification.inputs.iter_mut() {
                                if &input.name == input_name {
                                    input.check_performed = true;
                                    break;
                                }
                            }
                        }
                        ActionItemResponseType::ProvideSignedTransaction(response) => {
                            match &response.signed_transaction_bytes {
                                Some(bytes) => values
                                    .insert(SIGNED_TRANSACTION_BYTES, Value::string(bytes.clone())),
                                None => values.insert(SIGNED_TRANSACTION_BYTES, Value::null()),
                            }
                        }
                        ActionItemResponseType::ProvideSignedMessage(response) => {
                            values.insert(
                                SIGNED_MESSAGE_BYTES,
                                Value::string(response.signed_message_bytes.clone()),
                            );
                        }
                        _ => {}
                    }
                })
            }
            None => {}
        }
        let values = values
            .with_inputs(&evaluated_inputs.inputs.inputs)
            .check(&self.name, &self.specification.inputs)?;

        let spec = &self.specification;
        if spec.matcher != "output" {
            let mut actions = (spec.check_executability)(
                &construct_did,
                &self.name,
                &spec,
                &values,
                &supervision_context,
            )?;
            consolidated_actions.append(&mut actions);
        }
        Ok(consolidated_actions)
    }

    pub async fn perform_execution(
        &self,
        construct_did: &ConstructDid,
        nested_evaluation_values: &ValueStore,
        evaluated_inputs: &CommandInputsEvaluationResult,
        action_item_requests: &mut Vec<&mut ActionItemRequest>,
        _action_item_responses: &Option<&Vec<ActionItemResponse>>,
        progress_tx: &channel::Sender<BlockEvent>,
    ) -> Result<CommandExecutionResult, Diagnostic> {
        let values = ValueStore::new(&self.name, &construct_did.value())
            .with_defaults(&evaluated_inputs.inputs.defaults)
            .with_inputs(&evaluated_inputs.inputs.inputs)
            .append_inputs(&nested_evaluation_values.inputs);

        let spec = &self.specification;
        let res = (spec.run_execution)(&construct_did, &self.specification, &values, progress_tx)?
            .await
            .map_err(|e| e.set_span_range(self.block.span()));

        for request in action_item_requests.iter_mut() {
            let (status, success) = match &res {
                Ok(_) => (ActionItemStatus::Success(None), true),
                Err(diag) => (ActionItemStatus::Error(diag.clone()), false),
            };
            match request.action_type {
                ActionItemRequestType::ReviewInput(_) => {
                    request.action_status = status.clone();
                }
                ActionItemRequestType::ProvideInput(_) => {
                    request.action_status = status.clone();
                }
                ActionItemRequestType::ProvidePublicKey(_) => {
                    if success {
                        request.action_status = status.clone();
                    }
                }
                ActionItemRequestType::ProvideSignedTransaction(_) => {
                    if success {
                        request.action_status = status.clone();
                    }
                }
                ActionItemRequestType::SendTransaction(_) => {
                    if success {
                        request.action_status = status.clone();
                    }
                }
                ActionItemRequestType::ProvideSignedMessage(_) => {
                    if success {
                        request.action_status = status.clone();
                    }
                }
                _ => unreachable!(),
            }
        }
        res
    }

    pub async fn prepare_signed_nested_execution(
        &self,
        construct_did: &ConstructDid,
        evaluated_inputs: &CommandInputsEvaluationResult,
        signers: SignersState,
        signer_instances: &HashMap<ConstructDid, SignerInstance>,
    ) -> Result<(SignersState, Vec<(ConstructDid, ValueStore)>), (SignersState, Diagnostic)> {
        let values = ValueStore::new(&self.name, &construct_did.value())
            .with_defaults(&evaluated_inputs.inputs.defaults)
            .with_inputs(&evaluated_inputs.inputs.inputs);

        let spec = &self.specification;
        let future = (spec.prepare_signed_nested_execution)(
            &construct_did,
            &self.name,
            &values,
            signer_instances,
            signers,
        );
        return consolidate_nested_execution_result(future, self.block.span()).await;
    }

    pub fn prepare_nested_execution(
        &self,
        construct_did: &ConstructDid,
        evaluated_inputs: &CommandInputsEvaluationResult,
    ) -> Result<Vec<(ConstructDid, ValueStore)>, Diagnostic> {
        let values = ValueStore::new(&self.name, &construct_did.value())
            .with_defaults(&evaluated_inputs.inputs.defaults)
            .with_inputs(&evaluated_inputs.inputs.inputs);

        let spec = &self.specification;

        (spec.prepare_nested_execution)(&construct_did, &self.name, &values)
    }

    pub async fn check_signed_executability(
        &mut self,
        construct_did: &ConstructDid,
        nested_evaluation_values: &ValueStore,
        evaluated_inputs: &CommandInputsEvaluationResult,
        signers: SignersState,
        signer_instances: &mut HashMap<ConstructDid, SignerInstance>,
        action_item_response: &Option<&Vec<ActionItemResponse>>,
        action_item_requests: &Option<&Vec<&mut ActionItemRequest>>,
        supervision_context: &RunbookSupervisionContext,
    ) -> Result<(SignersState, Actions), (SignersState, Diagnostic)> {
        let values = ValueStore::new(&self.name, &construct_did.value())
            .with_defaults(&evaluated_inputs.inputs.defaults)
            .with_inputs(&evaluated_inputs.inputs.inputs)
            .append_inputs(&nested_evaluation_values.inputs);

        // TODO
        let mut consolidated_actions = Actions::none();
        match action_item_response {
            Some(responses) => {
                responses.into_iter().for_each(|ActionItemResponse { action_item_id, payload }| {
                    match payload {
                        ActionItemResponseType::ReviewInput(update) => {
                            // This is a shortcut and should be mutated somewhere else
                            for input in self.specification.inputs.iter_mut() {
                                if input.name == update.input_name {
                                    input.check_performed = true;
                                    break;
                                }
                            }
                        }
                        ActionItemResponseType::ProvideInput(update) => {
                            let action_item_update =
                                ActionItemRequestUpdate::from_id(&action_item_id)
                                    .set_type(ActionItemRequestType::ProvideInput(
                                        ProvideInputRequest {
                                            default_value: Some(update.updated_value.clone()),
                                            input_name: update.input_name.clone(),
                                            typing: update.updated_value.get_type(),
                                        },
                                    ))
                                    .set_status(ActionItemStatus::Success(None));
                            consolidated_actions.push_action_item_update(action_item_update);
                        }
                        ActionItemResponseType::ProvideSignedTransaction(_) => {
                            let action_item_update =
                                ActionItemRequestUpdate::from_id(&action_item_id)
                                    .set_status(ActionItemStatus::Success(None));
                            consolidated_actions.push_action_item_update(action_item_update);
                        }
                        ActionItemResponseType::SendTransaction(_) => {
                            let action_item_update =
                                ActionItemRequestUpdate::from_id(&action_item_id)
                                    .set_status(ActionItemStatus::Success(None));
                            consolidated_actions.push_action_item_update(action_item_update);
                        }
                        ActionItemResponseType::ProvideSignedMessage(_response) => {
                            let action_item_update =
                                ActionItemRequestUpdate::from_id(&action_item_id)
                                    .set_status(ActionItemStatus::Success(None));
                            consolidated_actions.push_action_item_update(action_item_update);
                        }
                        _ => {}
                    }
                })
            }
            None => {}
        }

        let spec = &self.specification;
        let future = (spec.check_signed_executability)(
            &construct_did,
            &self.name,
            &self.specification,
            &values,
            &supervision_context,
            signer_instances,
            signers,
        );
        let (signer_state, mut actions) =
            consolidate_signer_future_result(future, self.block.span()).await?;
        consolidated_actions.append(&mut actions);
        consolidated_actions.filter_existing_action_items(action_item_requests);
        Ok((signer_state, consolidated_actions))
    }

    pub async fn perform_signed_execution(
        &self,
        construct_did: &ConstructDid,
        nested_evaluation_values: &ValueStore,
        evaluated_inputs: &CommandInputsEvaluationResult,
        signers: SignersState,
        signer_instances: &HashMap<ConstructDid, SignerInstance>,
        action_item_requests: &mut Vec<&mut ActionItemRequest>,
        _action_item_responses: &Option<&Vec<ActionItemResponse>>,
        progress_tx: &channel::Sender<BlockEvent>,
    ) -> Result<(SignersState, CommandExecutionResult), (SignersState, Diagnostic)> {
        let values = ValueStore::new(&self.name, &construct_did.value())
            .with_defaults(&evaluated_inputs.inputs.defaults)
            .with_inputs(&evaluated_inputs.inputs.inputs)
            .append_inputs(&nested_evaluation_values.inputs);

        let spec = &self.specification;
        let future = (spec.run_signed_execution)(
            &construct_did,
            &self.specification,
            &values,
            progress_tx,
            signer_instances,
            signers,
        );
        let res = consolidate_signer_activate_future_result(future, self.block.span()).await?;

        for request in action_item_requests.iter_mut() {
            let (status, success) = match &res {
                Ok(_) => (ActionItemStatus::Success(None), true),
                Err((_, diag)) => (ActionItemStatus::Error(diag.clone()), false),
            };
            match request.action_type {
                ActionItemRequestType::ReviewInput(_) => {
                    request.action_status = status.clone();
                }
                ActionItemRequestType::ProvidePublicKey(_) => {
                    if success {
                        request.action_status = status.clone();
                    }
                }
                ActionItemRequestType::ProvideSignedTransaction(_) => {
                    if success {
                        request.action_status = status.clone();
                    }
                }
                ActionItemRequestType::SendTransaction(_) => {
                    if success {
                        request.action_status = status.clone();
                    }
                }
                ActionItemRequestType::ProvideSignedMessage(_) => {
                    if success {
                        request.action_status = status.clone();
                    }
                }
                _ => {}
            }
        }
        res
    }

    pub fn build_background_task(
        &self,
        construct_did: &ConstructDid,
        nested_evaluation_values: &ValueStore,
        evaluated_inputs: &CommandInputsEvaluationResult,
        execution_result: &CommandExecutionResult,
        progress_tx: &channel::Sender<BlockEvent>,
        background_tasks_uuid: &Uuid,
        supervision_context: &RunbookSupervisionContext,
    ) -> CommandExecutionFutureResult {
        let values = ValueStore::new(&self.name, &construct_did.value())
            .with_defaults(&evaluated_inputs.inputs.defaults)
            .with_inputs(&evaluated_inputs.inputs.inputs)
            .with_inputs_from_map(&execution_result.outputs)
            .append_inputs(&nested_evaluation_values.inputs);
        let outputs = ValueStore::new(&self.name, &construct_did.value())
            .with_inputs_from_map(&execution_result.outputs);

        let spec = &self.specification;
        let res = (spec.build_background_task)(
            &construct_did,
            &self.specification,
            &values,
            &outputs,
            progress_tx,
            background_tasks_uuid,
            supervision_context,
        );
        res
    }

    pub fn aggregate_nested_execution_results(
        &self,
        construct_did: &ConstructDid,
        nested_values: &Vec<(ConstructDid, ValueStore)>,
        commands_execution_results: &HashMap<ConstructDid, CommandExecutionResult>,
    ) -> Result<CommandExecutionResult, Diagnostic> {
        let mut nested_results = vec![];
        for (nested_construct_did, _) in nested_values {
            let nested_result = commands_execution_results
                .get(nested_construct_did)
                .cloned()
                .unwrap_or_else(|| {
                    return CommandExecutionResult::new();
                });
            nested_results.push(nested_result);
        }

        (self.specification.aggregate_nested_execution_results)(
            &construct_did,
            &nested_values,
            &nested_results,
        )
    }

    pub fn collect_dependencies(&self) -> Vec<(Option<&CommandInput>, Expression)> {
        let mut dependencies = vec![];

        for input in self.specification.inputs.iter().chain(&self.specification.default_inputs) {
            match input.typing {
                Type::Object(ref props) => {
                    if let Some(attr) = self.block.body.get_attribute(&input.name) {
                        collect_constructs_references_from_expression(
                            &attr.value,
                            Some(input),
                            &mut dependencies,
                        );
                    } else {
                        for prop in props.iter() {
                            let mut blocks_iter = self.block.body.get_blocks(&input.name);
                            while let Some(block) = blocks_iter.next() {
                                let Some(attr) = block.body.get_attribute(&prop.name) else {
                                    continue;
                                };
                                collect_constructs_references_from_expression(
                                    &attr.value,
                                    Some(input),
                                    &mut dependencies,
                                );
                            }
                        }
                    }
                }
                _ => {
                    let Some(attr) = self.block.body.get_attribute(&input.name) else {
                        continue;
                    };
                    collect_constructs_references_from_expression(
                        &attr.value,
                        Some(input),
                        &mut dependencies,
                    );
                }
            }
        }
        dependencies
    }
}

pub trait CommandImplementation {
    fn post_process_evaluated_inputs(
        _ctx: &CommandSpecification,
        inputs: CommandInputsEvaluationResult,
    ) -> InputsPostProcessingFutureResult {
        let future = async move { Ok(inputs) };
        Ok(Box::pin(future))
    }

    fn check_instantiability(
        _ctx: &CommandSpecification,
        _args: Vec<Type>,
    ) -> Result<Type, Diagnostic>;

    fn check_executability(
        _construct_id: &ConstructDid,
        _instance_name: &str,
        _spec: &CommandSpecification,
        _values: &ValueStore,
        _supervision_context: &RunbookSupervisionContext,
    ) -> Result<Actions, Diagnostic> {
        unimplemented!()
    }

    fn run_execution(
        _construct_id: &ConstructDid,
        _spec: &CommandSpecification,
        _values: &ValueStore,
        _progress_tx: &channel::Sender<BlockEvent>,
    ) -> CommandExecutionFutureResult {
        unimplemented!()
    }

    fn check_signed_executability(
        _construct_id: &ConstructDid,
        _instance_name: &str,
        _spec: &CommandSpecification,
        _values: &ValueStore,
        _supervision_context: &RunbookSupervisionContext,
        _signers_instances: &HashMap<ConstructDid, SignerInstance>,
        _signers_state: SignersState,
    ) -> SignerActionsFutureResult {
        unimplemented!()
    }

    fn prepare_signed_nested_execution(
        construct_did: &ConstructDid,
        instance_name: &str,
        _values: &ValueStore,
        _signers_instances: &HashMap<ConstructDid, SignerInstance>,
        signers_state: SignersState,
    ) -> PrepareSignedNestedExecutionResult {
        let signer_state = signers_state
            .get_first_signer()
            .expect(&format!("no signers provided for action '{}'", instance_name));
        return_synchronous((
            signers_state,
            signer_state,
            vec![(
                construct_did.clone(),
                ValueStore::new(&construct_did.to_string(), &construct_did.0),
            )],
        ))
    }

    fn prepare_nested_execution(
        construct_did: &ConstructDid,
        _instance_name: &str,
        _values: &ValueStore,
    ) -> Result<Vec<(ConstructDid, ValueStore)>, Diagnostic> {
        Ok(vec![(
            construct_did.clone(),
            ValueStore::new(&construct_did.to_string(), &construct_did.0),
        )])
    }

    fn aggregate_nested_execution_results(
        _construct_did: &ConstructDid,
        _values: &Vec<(ConstructDid, ValueStore)>,
        nested_results: &Vec<CommandExecutionResult>,
    ) -> Result<CommandExecutionResult, Diagnostic> {
        let mut result = CommandExecutionResult::new();
        for nested_result in nested_results {
            result.outputs.extend(nested_result.outputs.clone());
        }
        Ok(result)
    }

    fn run_signed_execution(
        _construct_id: &ConstructDid,
        _spec: &CommandSpecification,
        _values: &ValueStore,
        _progress_tx: &channel::Sender<BlockEvent>,
        _signers_instances: &HashMap<ConstructDid, SignerInstance>,
        _signers_state: SignersState,
    ) -> SignerSignFutureResult {
        unimplemented!()
    }

    fn build_background_task(
        _construct_did: &ConstructDid,
        _spec: &CommandSpecification,
        _values: &ValueStore,
        _outputs: &ValueStore,
        _progress_tx: &channel::Sender<BlockEvent>,
        _background_tasks_uuid: &Uuid,
        _supervision_context: &RunbookSupervisionContext,
    ) -> CommandExecutionFutureResult {
        unimplemented!()
    }
}

pub fn add_ctx_to_diag(
    command_type: String,
    matcher: String,
    command_instance_name: String,
    namespace: String,
) -> impl Fn(&Diagnostic) -> Diagnostic {
    let diag_with_command_ctx = move |diag: &Diagnostic| -> Diagnostic {
        let mut diag = diag.clone();
        diag.message = format!(
            "'{}:{}' {} '{}': {}",
            namespace, matcher, command_type, command_instance_name, diag.message
        );
        diag
    };
    return diag_with_command_ctx;
}
pub fn add_ctx_to_embedded_runbook_diag(
    embedded_runbook_instance_name: String,
) -> impl Fn(&Diagnostic) -> Diagnostic {
    let diag_with_command_ctx = move |diag: &Diagnostic| -> Diagnostic {
        let mut diag = diag.clone();
        diag.message =
            format!("embedded runbook '{}': {}", embedded_runbook_instance_name, diag.message);
        diag
    };
    return diag_with_command_ctx;
}
