use std::collections::{BTreeMap, HashMap};

use crate::{
  bos::{
    self,
    session::http_client::v2::types::{BosSession, Operation},
    template::http_client::v2::types::{BootSet, BosSessionTemplate, Cfs},
  },
  cfs::{
    self,
    configuration::http_client::v2::types::{
      cfs_configuration_request::CfsConfigurationRequest,
      cfs_configuration_response::CfsConfigurationResponse,
    },
    session::http_client::v2::types::CfsSessionPostRequest,
  },
  common,
  error::Error,
  hsm,
  ims::{self, image::http_client::types::Link},
  node::utils::validate_target_hsm_members,
};
use image::Image;
use serde::{Deserialize, Serialize};
use serde_json::Map;
use serde_yaml::Value;
use uuid::Uuid;

use self::sessiontemplate::SessionTemplate;

#[derive(Deserialize, Serialize, Debug)]
pub struct SatFile {
  #[serde(skip_serializing_if = "Option::is_none")]
  pub configurations: Option<Vec<configuration::Configuration>>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub images: Option<Vec<image::Image>>,
  #[serde(skip_serializing_if = "Option::is_none")]
  pub session_templates: Option<Vec<sessiontemplate::SessionTemplate>>,
}

impl SatFile {
  /// Filter either images or session_templates section according to user request
  pub fn filter(
    &mut self,
    image_only: bool,
    session_template_only: bool,
  ) -> Result<(), Error> {
    // Clean SAT template file if user only wan'ts to process the 'images' section. In this case,
    // we will remove 'session_templates' section from SAT fiel and also the entries in
    // 'configurations' section not used
    if image_only {
      let image_vec_opt: Option<&Vec<Image>> = self.images.as_ref();

      let configuration_name_image_vec: Vec<String> = match image_vec_opt {
        Some(image_vec) => image_vec
          .iter()
          .filter_map(|sat_template_image| {
            sat_template_image.configuration.clone()
          })
          .collect(),
        None => {
          return Err(Error::Message(
            "ERROR - 'images' section missing in SAT file".to_string(),
          ));
          /* eprintln!("ERROR - 'images' section missing in SAT file");
          std::process::exit(1); */
        }
      };

      // Remove configurations not used by any image
      self
        .configurations
        .as_mut()
        .unwrap_or(&mut Vec::new())
        .retain(|configuration| {
          configuration_name_image_vec.contains(&configuration.name)
        });

      // Remove section "session_templates"
      self.session_templates = None;
    }

    // Clean SAT template file if user only wan'ts to process the 'session_template' section. In this case,
    // we will remove 'images' section from SAT fiel and also the entries in
    // 'configurations' section not used
    if session_template_only {
      let sessiontemplate_vec_opt: Option<&Vec<SessionTemplate>> =
        self.session_templates.as_ref();

      let configuration_name_sessiontemplate_vec: Vec<String> =
        match sessiontemplate_vec_opt {
          Some(sessiontemplate_vec) => sessiontemplate_vec
            .iter()
            .map(|sat_sessiontemplate| {
              sat_sessiontemplate.configuration.clone()
            })
            .collect(),
          None => {
            return Err(Error::Message(
              "ERROR - 'session_templates' section not defined in SAT file"
                .to_string(),
            ));
            /* eprintln!("ERROR - 'session_templates' section not defined in SAT file");
            std::process::exit(1); */
          }
        };

      // Remove configurations not used by any sessiontemplate
      /* self.configurations
      .as_mut()
      .unwrap_or(&mut Vec::new())
      .retain(|configuration| {
          configuration_name_sessiontemplate_vec.contains(&configuration.name)
      }); */

      if let Some(&[_]) = self.configurations.as_deref() {
        self
          .configurations
          .as_mut()
          .unwrap_or(&mut Vec::new())
          .retain(|configuration| {
            configuration_name_sessiontemplate_vec.contains(&configuration.name)
          })
      } else {
        self.configurations = None;
      }

      let image_name_sessiontemplate_vec: Vec<String> = self
        .session_templates
        .as_ref()
        .unwrap_or(&Vec::new())
        .iter()
        .filter_map(|sessiontemplate| match &sessiontemplate.image {
          sessiontemplate::Image::ImageRef(name) => Some(name),
          sessiontemplate::Image::Ims { ims } => match ims {
            sessiontemplate::ImsDetails::Name { name } => Some(name),
            sessiontemplate::ImsDetails::Id { .. } => None,
          },
        })
        .cloned()
        .collect();

      // Remove images not used by any sessiontemplate
      self
        .images
        .as_mut()
        .unwrap_or(&mut Vec::new())
        .retain(|image| image_name_sessiontemplate_vec.contains(&image.name));

      if self.images.as_ref().is_some_and(|images| images.is_empty()) {
        self.images = None;
      }
    }

    Ok(())
  }
}

/// struct to represent the `session_templates` section in SAT file
pub mod sessiontemplate {
  use std::collections::HashMap;
  use strum_macros::Display;

  use serde::{Deserialize, Serialize};

  #[derive(Deserialize, Serialize, Debug)]
  pub struct SessionTemplate {
    pub name: String,
    pub image: Image,
    pub configuration: String,
    pub bos_parameters: BosParamters,
  }

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum ImsDetails {
    Name { name: String },
    Id { id: String },
  }

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum Image {
    Ims { ims: ImsDetails },
    ImageRef(String),
  }

  #[derive(Deserialize, Serialize, Debug)]
  pub struct BosParamters {
    pub boot_sets: HashMap<String, BootSet>,
  }

  #[derive(Deserialize, Serialize, Debug)]
  pub struct BootSet {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub arch: Option<Arch>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub kernel_parameters: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub network: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub node_list: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub node_roles_group: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub node_groups: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rootfs_provider: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rootfs_provider_passthrough: Option<String>,
  }

  #[derive(Deserialize, Serialize, Debug, Display)]
  pub enum Arch {
    X86,
    ARM,
    Other,
    Unknown,
  }
}

/// Convert from `sessiontemplate` in SAT file to mesa BosSessionTemplate
/// example from https://doc.rust-lang.org/rust-by-example/conversion/try_from_try_into.html
impl TryFrom<SessionTemplate> for BosSessionTemplate {
  type Error = ();

  fn try_from(
    value: SessionTemplate,
  ) -> Result<BosSessionTemplate, Self::Error> {
    let b_st_cfs = Cfs {
      configuration: Some(value.configuration),
    };

    let mut boot_set_map: HashMap<String, BootSet> = HashMap::new();

    for (property, boot_set) in value.bos_parameters.boot_sets {
      let boot_set = BootSet {
        name: Some(format!(
          "Boot set property '{}' created by manta from SAT file",
          property
        )),
        path: None,
        r#type: None,
        etag: None,
        kernel_parameters: None,
        node_list: boot_set.node_list,
        node_roles_groups: boot_set.node_roles_group,
        node_groups: boot_set.node_groups,
        rootfs_provider: boot_set.rootfs_provider,
        rootfs_provider_passthrough: boot_set.rootfs_provider_passthrough,
        cfs: Some(b_st_cfs.clone()),
        arch: boot_set.arch.map(|value| value.to_string()),
      };

      boot_set_map.insert(property, boot_set);
    }

    let b_st = BosSessionTemplate {
      name: Some(value.name),
      description: Some(format!(
        "BOS sessiontemplate created by manta from SAT file"
      )),
      enable_cfs: Some(true),
      cfs: Some(b_st_cfs),
      boot_sets: Some(boot_set_map),
      links: None,
      tenant: None,
    };

    Ok(b_st)
  }
}

/// struct to represent the `images` section in SAT file
pub mod image {
  use serde::{Deserialize, Serialize};

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum Arch {
    #[serde(rename(serialize = "aarch64", deserialize = "aarch64"))]
    Aarch64,
    #[serde(rename(serialize = "x86_64", deserialize = "x86_64"))]
    X86_64,
  }

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum ImageIms {
    NameIsRecipe { name: String, is_recipe: bool },
    IdIsRecipe { id: String, is_recipe: bool },
  }

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum ImageBaseIms {
    NameType { name: String, r#type: String },
    IdType { id: String, r#type: String },
    BackwardCompatible { is_recipe: Option<bool>, id: String },
  }

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum Filter {
    Prefix { prefix: String },
    Wildcard { wildcard: String },
    Arch { arch: Arch },
  }

  #[derive(Deserialize, Serialize, Debug)]
  pub struct Product {
    name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    version: Option<String>,
    r#type: String,
    filter: Filter,
  }

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum Base {
    Ims { ims: ImageBaseIms },
    Product { product: Product },
    ImageRef { image_ref: String },
  }

  // Used for backguard compatibility
  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum BaseOrIms {
    Base { base: Base },
    Ims { ims: ImageIms },
  }

  #[derive(Deserialize, Serialize, Debug)]
  pub struct Image {
    pub name: String,
    #[serde(flatten)]
    pub base_or_ims: BaseOrIms,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub configuration: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub configuration_group_names: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ref_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
  }
}

/// struct to represent the `configurations` section in SAT file
pub mod configuration {
  use serde::{Deserialize, Serialize};

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum Product {
    ProductVersionBranch {
      name: String,
      version: Option<String>,
      branch: String,
    },
    ProductVersionCommit {
      name: String,
      version: Option<String>,
      commit: String,
    },
    ProductVersion {
      name: String,
      version: String,
    },
  }

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum Git {
    GitCommit { url: String, commit: String },
    GitBranch { url: String, branch: String },
    GitTag { url: String, tag: String },
  }

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum LayerType {
    Git { git: Git },
    Product { product: Product },
  }

  #[derive(Deserialize, Serialize, Debug)]
  pub struct Layer {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(default = "default_playbook")]
    pub playbook: String, // This field is optional but with default value. Therefore we won't
    #[serde(flatten)]
    pub layer_type: LayerType,
  }

  fn default_playbook() -> String {
    "site.yml".to_string()
  }

  #[derive(Deserialize, Serialize, Debug)]
  #[serde(untagged)] // <-- this is important. More info https://serde.rs/enum-representations.html#untagged
  pub enum Inventory {
    InventoryCommit {
      #[serde(skip_serializing_if = "Option::is_none")]
      name: Option<String>,
      url: String,
      commit: String,
    },
    InventoryBranch {
      #[serde(skip_serializing_if = "Option::is_none")]
      name: Option<String>,
      url: String,
      branch: String,
    },
  }

  #[derive(Deserialize, Serialize, Debug)]
  pub struct Configuration {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    pub layers: Vec<Layer>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub additional_inventory: Option<Inventory>,
  }
}

pub mod sat_file_image_old {
  use serde::{Deserialize, Serialize};

  #[derive(Deserialize, Serialize, Debug)]
  pub struct Ims {
    is_recipe: bool,
    id: String,
  }

  #[derive(Deserialize, Serialize, Debug)]
  pub struct Product {
    name: String,
    version: String,
    r#type: String,
  }

  /* pub struct SatFileImage {
      pub name: String,
      pub ims: Ims,
      pub configuration: Option<String>,
      pub configuration_group_names: Option<Vec<String>>,
      pub ref_name: Option<String>,
      pub description: Option<String>,
  } */
}

/* /// Merge 2 yamls, 'b' values will overwrite 'a' values
/// eg:
/// having a:
///
/// ```
/// key_1
///   key_1_1: value_1_1
///   key_1_2: value_1_2
/// key_2: value_2
/// key_3: value_3
/// ```
/// and b:
/// ```
/// key_1
///   key_1_1: new_value_1_1
///   key_1_2: value_1_2
///   key_1_3: new_value_1_3
/// key_2: new_value_2
/// key_4: new_value_4
/// ```
/// would convert a into:
/// ```
/// key_1
///   key_1_1: new_value_1_1
///   key_1_3: new_value_1_3
/// key_2: new_value_2
/// key_3: value_3
/// key_4: new_value_4
/// ```
fn merge_yaml(base: Value, merge: Value) -> Option<Value> {
    match (base, merge) {
        (Value::Mapping(mut base_map), Value::Mapping(merge_map)) => {
            for (key, value) in merge_map {
                if let Some(base_value) = base_map.get_mut(&key) {
                    *base_value = merge_yaml(base_value.clone(), value)?;
                } else {
                    base_map.insert(key, value);
                }
            }
            Some(Value::Mapping(base_map))
        }
        (Value::Sequence(mut base_seq), Value::Sequence(merge_seq)) => {
            base_seq.extend(merge_seq);
            Some(Value::Sequence(base_seq))
        }
        (_, merge) => Some(merge),
    }
} */

/* /// Convert a String dot notation expression into a serde_yaml::Value.
/// eg:
/// dot notation input like:
/// ```
/// key_1.key_2.key_3=1
/// ````
/// would result in a serde_yaml::Value equivalent to:
/// ```
/// key_1
///   key_2
///     key_3: 1
/// ```
fn dot_notation_to_yaml(dot_notation: &str) -> Result<serde_yaml::Value, Error> {
    let parts: Vec<&str> = dot_notation.split('=').collect();
    if parts.len() != 2 {
        return Err(Error::Message("Invalid format".to_string()));
    }

    let keys: Vec<&str> = parts[0].trim().split('.').collect();
    let value_str = parts[1].trim().trim_matches('"'); // Remove leading and trailing quotes
    let value: Value = Value::String(value_str.to_string());

    let mut root = Value::Mapping(Mapping::new());
    let mut current_level = &mut root;

    for (i, &key) in keys.iter().enumerate() {
        if i == keys.len() - 1 {
            // Last key, assign the value
            if let Value::Mapping(map) = current_level {
                map.insert(Value::String(key.to_string()), value.clone());
            }
        } else {
            // Not the last key, create or use existing map
            let next_level = if let Value::Mapping(map) = current_level {
                if map.contains_key(&Value::String(key.to_string())) {
                    // Use existing map
                    map.get_mut(&Value::String(key.to_string())).unwrap()
                } else {
                    // Create new map and insert
                    map.insert(
                        Value::String(key.to_string()),
                        Value::Mapping(Mapping::new()),
                    );
                    map.get_mut(&Value::String(key.to_string())).unwrap()
                }
            } else {
                // In case the structure is not as expected; should not happen in this logic
                return Err(Error::Message(
                    "Unexpected structure encountered".to_string(),
                ));
            };
            current_level = next_level;
        }
    }

    Ok(root)
} */

/* pub fn render_jinja2_sat_file_yaml(
    sat_file_content: &String,
    values_file_content_opt: Option<&String>,
    value_cli_vec_opt: Option<Vec<String>>,
) -> Value {
    let mut env = minijinja::Environment::new();
    // Set/enable debug in order to force minijinja to print debug error messages which are more
    // descriptive. Eg https://github.com/mitsuhiko/minijinja/blob/main/examples/error/src/main.rs#L4-L5
    env.set_debug(true);
    // Set lines starting with `#` as comments
    env.set_syntax(
        minijinja::syntax::SyntaxConfig::builder()
            .line_comment_prefix("#")
            .build()
            .unwrap(),
    );
    // Set 'String' as undefined behaviour meaning, missing values won't pass the template
    // rendering
    env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict);

    // Render session values file
    let mut values_file_yaml: Value = if let Some(values_file_content) = values_file_content_opt {
        log::info!("'Session vars' file provided. Going to process SAT file as a template.");
        // Read sesson vars file and parse it to YAML
        let values_file_yaml: Value = serde_yaml::from_str(values_file_content).unwrap();
        // Render session vars file with itself (copying ansible behaviour where the ansible vars
        // file is also a jinja template and combine both vars and values in it)
        let values_file_rendered = env
            .render_str(values_file_content, values_file_yaml)
            .expect("ERROR - Error parsing values file to YAML. Exit");
        serde_yaml::from_str(&values_file_rendered).unwrap()
    } else {
        serde_yaml::from_str(sat_file_content).unwrap()
    };

    // Convert variable values sent by cli argument from dot notation to yaml format
    log::debug!("Convert variable values sent by cli argument from dot notation to yaml format");
    if let Some(value_option_vec) = value_cli_vec_opt {
        for value_option in value_option_vec {
            let cli_var_context_yaml_rslt = dot_notation_to_yaml(&value_option);
            let cli_var_context_yaml = match cli_var_context_yaml_rslt {
                Ok(value) => value,
                Err(e) => {
                    eprintln!("ERROR - {:#?}", e);
                    panic!();
                }
            };
            values_file_yaml = merge_yaml(values_file_yaml.clone(), cli_var_context_yaml).unwrap();
        }
    }

    // render sat template file
    let sat_file_rendered_rslt = env.render_str(sat_file_content, values_file_yaml);

    let sat_file_rendered = match sat_file_rendered_rslt {
        Ok(sat_file_rendered) => sat_file_rendered,
        Err(err) => {
            eprintln!("ERROR - Could not render template: {:#}", err);
            // render causes as well
            let mut err = &err as &dyn std::error::Error;
            while let Some(next_err) = err.source() {
                eprintln!();
                eprintln!("caused by: {:#}", next_err);
                err = next_err;
            }

            std::process::exit(1);
        }
    };

    // Disable debug
    env.set_debug(false);

    let sat_file_yaml: Value = serde_yaml::from_str(&sat_file_rendered).unwrap();

    sat_file_yaml
} */

/* pub fn render_jinja2_sat_file_struct(
    sat_file_content: &String,
    values_file_content_opt: Option<&String>,
    value_cli_vec_opt: Option<Vec<String>>,
) -> SatFile {
    let env = minijinja::Environment::new();

    // Render session values file
    let mut values_file_yaml: Value = if let Some(values_file_content) = values_file_content_opt {
        log::info!("'Session vars' file provided. Going to process SAT file as a template.");
        // Read sesson vars file and parse it to YAML
        let values_file_yaml: Value = serde_yaml::from_str(values_file_content).unwrap();
        // Render session vars file with itself (copying ansible behaviour where the ansible vars
        // file is also a jinja template and combine both vars and values in it)
        let values_file_rendered = env
            .render_str(values_file_content, values_file_yaml)
            .expect("ERROR - Error parsing values file to YAML. Exit");
        serde_yaml::from_str(&values_file_rendered).unwrap()
    } else {
        serde_yaml::from_str(sat_file_content).unwrap()
    };

    if let Some(value_option_vec) = value_cli_vec_opt {
        for value_option in value_option_vec {
            let cli_var_context_yaml = dot_notation_to_yaml(&value_option).unwrap();
            values_file_yaml = merge_yaml(values_file_yaml.clone(), cli_var_context_yaml).unwrap();
        }
    }

    // render sat template file
    let sat_file_rendered = env.render_str(sat_file_content, values_file_yaml).unwrap();

    let sat_file_yaml: SatFile = serde_yaml::from_str(&sat_file_rendered).unwrap();

    sat_file_yaml
} */

pub async fn create_cfs_configuration_from_sat_file(
  shasta_token: &str,
  shasta_base_url: &str,
  shasta_root_cert: &[u8],
  gitea_base_url: &str,
  gitea_token: &str,
  cray_product_catalog: &BTreeMap<String, String>,
  sat_file_configuration_yaml: &serde_yaml::Value,
  // tag: &str,
  dry_run: bool,
  site_name: &str,
  overwrite: bool,
) -> Result<CfsConfigurationResponse, Error> {
  log::debug!(
    "Convert CFS configuration in SAT file (yaml):\n{:#?}",
    sat_file_configuration_yaml
  );

  let (cfs_configuration_name, cfs_configuration) =
    CfsConfigurationRequest::from_sat_file_serde_yaml(
      shasta_root_cert,
      gitea_base_url,
      gitea_token,
      sat_file_configuration_yaml,
      cray_product_catalog,
      site_name,
    )
    .await?;

  if dry_run {
    println!(
      "Dry run mode: Create CFS configuration:\n{}",
      serde_json::to_string_pretty(&cfs_configuration)?
    );

    // Generate mock CFS configuration
    let cfs_configuration = CfsConfigurationResponse {
      name: cfs_configuration_name,
      last_updated: "".to_string(),
      layers: Vec::new(),
      additional_inventory: None,
    };

    // Return mock CFS configuration
    Ok(cfs_configuration)
  } else {
    /* cfs::configuration::http_client::v2::put(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      &mut cfs_configuration,
      &cfs_configuration_name,
    )
    .await */
    cfs::configuration::utils::create_new_configuration(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      &cfs_configuration,
      &cfs_configuration_name,
      overwrite,
    )
    .await
  }
}

/// Analyze a list of images in SAT file and returns the image to process next.
/// Input values:
///  - image_yaml_vec: the list of images in the SAT file, each element is a serde_yaml::Value
///  - ref_name_processed_vec: he list of images (ref_name) already processed
/// Note:
/// image.base.image_ref value in SAT file points to the image it depends on (image.ref_name)
/// NOTE 2: we assume that there may be a mix of images in SAT file with and without "ref_name"
/// value, we will use the function "get_ref_name" which will fall back to "name" field if
/// "ref_name" is missing in the image
/// An image is ready to be processed if:
///  - It does not depends on another image (image.base.image_ref is missing)
///  - The image it depends to is already processed (image.base.image_ref included in
///  ref_name_processed)
///  - It has not been already processed
pub fn get_next_image_in_sat_file_to_process(
  image_yaml_vec: &[serde_yaml::Value],
  ref_name_processed_vec: &[String],
) -> Option<serde_yaml::Value> {
  image_yaml_vec
    .iter()
    .find(|image_yaml| {
      let ref_name: &str = &get_image_name_or_ref_name_to_process(image_yaml); // Again, because we assume images in
                                                                               // SAT file may or may not have ref_name value, we will use "get_ref_name" function to
                                                                               // get the id of the image

      let image_base_image_ref_opt: Option<&str> =
        image_yaml.get("base").and_then(|image_base_yaml| {
          image_base_yaml.get("image_ref").and_then(
            |image_base_image_ref_yaml| image_base_image_ref_yaml.as_str(),
          )
        });

      !ref_name_processed_vec.contains(&ref_name.to_string())
        && (image_base_image_ref_opt.is_none()
          || image_base_image_ref_opt.is_some_and(|image_base_image_ref| {
            ref_name_processed_vec.contains(&image_base_image_ref.to_string())
          }))
    })
    .cloned()
}

/// Get the "ref_name" from an image, because we need to be aware of which images in SAT file have
/// been processed in order to find the next image to process. We assume not all images in the yaml
/// will have an "image_ref" value, therefore we will use "ref_name" or "name" field if the former
/// is missing
pub fn get_image_name_or_ref_name_to_process(
  image_yaml: &serde_yaml::Value,
) -> String {
  if image_yaml.get("ref_name").is_some() {
    image_yaml["ref_name"].as_str().unwrap().to_string()
  } else {
    // If the image processed is missing the field "ref_name", then use the field "name"
    // instead, this is needed to flag this image as processed and filtered when
    // calculating the next image to process (get_next_image_to_process)
    image_yaml["name"].as_str().unwrap().to_string()
  }
}

#[deprecated(
  since = "v0.86.2",
  note = "this function prints cfs session logs to stdout"
)]
pub async fn i_import_images_section_in_sat_file(
  shasta_token: &str,
  shasta_base_url: &str,
  shasta_root_cert: &[u8],
  vault_base_url: &str,
  site_name: &str,
  // vault_secret_path: &str,
  // vault_role_id: &str,
  k8s_api_url: &str,
  ref_name_processed_hashmap: &mut HashMap<String, String>,
  image_yaml_vec: &[serde_yaml::Value],
  cray_product_catalog: &BTreeMap<String, String>,
  ansible_verbosity_opt: Option<u8>,
  ansible_passthrough_opt: Option<&str>,
  debug_on_failure: bool, // tag: &str,
  dry_run: bool,
  watch_logs: bool,
  timestamps: bool,
) -> Result<HashMap<String, serde_yaml::Value>, Error> {
  if image_yaml_vec.is_empty() {
    log::warn!("No images found in SAT file. Nothing to process.");
    return Ok(HashMap::new());
  }

  // Get an image to process (the image either has no dependency or it's image dependency has
  // already ben processed)
  let mut next_image_to_process_opt: Option<serde_yaml::Value> =
    get_next_image_in_sat_file_to_process(
      &image_yaml_vec,
      &ref_name_processed_hashmap
        .keys()
        .cloned()
        .collect::<Vec<String>>(),
    );

  // Process images
  log::info!("Processing image '{:?}'", next_image_to_process_opt);
  let mut image_processed_hashmap: HashMap<String, serde_yaml::Value> =
    HashMap::new();

  while let Some(image_yaml) = &next_image_to_process_opt {
    let image_id = i_create_image_from_sat_file_serde_yaml(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      vault_base_url,
      site_name,
      // vault_secret_path,
      // vault_role_id,
      k8s_api_url,
      image_yaml,
      cray_product_catalog,
      ansible_verbosity_opt,
      ansible_passthrough_opt,
      ref_name_processed_hashmap,
      debug_on_failure,
      dry_run,
      watch_logs,
      timestamps,
    )
    .await?;

    image_processed_hashmap.insert(image_id.clone(), image_yaml.clone());

    ref_name_processed_hashmap.insert(
      get_image_name_or_ref_name_to_process(image_yaml),
      image_id.clone(),
    );

    next_image_to_process_opt = get_next_image_in_sat_file_to_process(
      &image_yaml_vec,
      &ref_name_processed_hashmap
        .keys()
        .cloned()
        .collect::<Vec<String>>(),
    );
  }

  Ok(image_processed_hashmap)
}

#[deprecated(
  since = "v0.86.2",
  note = "this function prints cfs session logs to stdout"
)]
pub async fn i_create_image_from_sat_file_serde_yaml(
  shasta_token: &str,
  shasta_base_url: &str,
  shasta_root_cert: &[u8],
  vault_base_url: &str,
  site_name: &str,
  k8s_api_url: &str,
  image_yaml: &serde_yaml::Value, // NOTE: image may be an IMS job or a CFS session
  cray_product_catalog: &BTreeMap<String, String>,
  ansible_verbosity_opt: Option<u8>,
  ansible_passthrough_opt: Option<&str>,
  ref_name_image_id_hashmap: &HashMap<String, String>,
  _debug_on_failure: bool,
  dry_run: bool,
  watch_logs: bool,
  timestamps: bool,
) -> Result<String, Error> {
  // Collect CFS session details from SAT file
  // Get CFS session name from SAT file
  let image_name = image_yaml["name"].as_str().unwrap().to_string();
  // let image_name = image_yaml["name"]
  //     .as_str()
  //     .unwrap()
  //     .to_string()
  //     .replace("__DATE__", tag);

  log::info!(
    "Creating CFS session related to build image '{}'",
    image_name
  );

  // Get CFS configuration related to CFS session in SAT file
  let configuration_name: String = image_yaml["configuration"]
    .as_str()
    .unwrap_or_default()
    .to_string();

  // Rename session's configuration name
  // configuration = configuration.replace("__DATE__", tag);

  // Get HSM groups related to CFS session in SAT file
  let groups_name: Vec<&str> = image_yaml["configuration_group_names"]
    .as_sequence()
    .and_then(|group_name_vec| {
      group_name_vec
        .iter()
        .map(|group_name| group_name.as_str())
        .collect::<Option<Vec<&str>>>()
    })
    .unwrap_or(vec![]);

  // VALIDATION: make sure grups in SAT.images "CFS session" are valid
  // NOTE: this is temporary until we get rid off "group" names as ansible folder names
  let invalid_groups: Vec<String> =
    hsm::group::hacks::validate_groups_auth_token(&groups_name, shasta_token)?;

  if !invalid_groups.is_empty() {
    log::debug!("CFS session group validation - failed");

    return Err(Error::Message(format!(
      "Please fix 'images' section in SAT file.\nInvalid groups: {:?}",
      invalid_groups
    )));
  } else {
    log::debug!("CFS session group validation - passed");
  }

  let base_image_id: String;

  // Get/process base image
  if let Some(sat_file_image_ims_value_yaml) = image_yaml.get("ims") {
    // ----------- BASE IMAGE - BACKWARD COMPATIBILITY WITH PREVIOUS SAT FILE
    log::info!("SAT file - 'image.ims' job ('images' section in SAT file is outdated - switching to backward compatibility)");

    base_image_id =
      process_sat_file_image_old_version(sat_file_image_ims_value_yaml)
        .unwrap();
  } else if let Some(sat_file_image_base_value_yaml) = image_yaml.get("base") {
    if let Some(sat_file_image_base_image_ref_value_yaml) =
      sat_file_image_base_value_yaml.get("image_ref")
    {
      log::info!("SAT file - 'image.base.image_ref' job");

      base_image_id = process_sat_file_image_ref_name(
        sat_file_image_base_image_ref_value_yaml,
        ref_name_image_id_hashmap,
      )
      .unwrap();
    } else if let Some(sat_file_image_base_ims_value_yaml) =
      sat_file_image_base_value_yaml.get("ims")
    {
      log::info!("SAT file - 'image.base.ims' job");
      let ims_job_type =
        sat_file_image_base_ims_value_yaml["type"].as_str().unwrap();
      if ims_job_type == "recipe" {
        log::info!("SAT file - 'image.base.ims' job of type 'recipe'");

        base_image_id = process_sat_file_image_ims_type_recipe(
          shasta_token,
          shasta_base_url,
          shasta_root_cert,
          sat_file_image_base_ims_value_yaml,
          &image_name,
          dry_run,
        )
        .await
        .unwrap();
      } else if ims_job_type == "image" {
        log::info!("SAT file - 'image.base.ims' job of type 'image'");

        base_image_id = sat_file_image_base_ims_value_yaml["id"]
          .as_str()
          .unwrap()
          .to_string();
      } else {
        return Err(Error::Message(
          "Can't process SAT file 'images.base.ims' is missing. Exit"
            .to_string(),
        ));
      }

    // ----------- BASE IMAGE - CRAY PRODUCT CATALOG
    } else if let Some(sat_file_image_base_product_value_yaml) =
      sat_file_image_base_value_yaml.get("product")
    {
      log::info!("SAT file - 'image.base.product' job");
      // Base image created from a cray product
      let product_name = sat_file_image_base_product_value_yaml["name"]
        .as_str()
        .unwrap();

      let product_version = sat_file_image_base_product_value_yaml["version"]
        .as_str()
        .unwrap();

      let product_type = sat_file_image_base_product_value_yaml["type"]
        .as_str()
        .unwrap()
        .to_string()
        + "s";

      // We assume the SAT file has been alredy validated therefore taking some risks in
      // getting the details from the Cray product catalog
      let product_image_map = &serde_yaml::from_str::<serde_json::Value>(
        &cray_product_catalog[product_name],
      )
      .unwrap()[product_version][product_type.clone()]
      .as_object()
      .unwrap()
      .clone();

      let image_id = if let Some(filter) =
        sat_file_image_base_product_value_yaml.get("filter")
      {
        filter_product_catalog_images(
          filter,
          product_image_map.clone(),
          &image_name,
        )
        .unwrap()
      } else {
        // There is no 'image.product.filter' value defined in SAT file. Check Cray
        // product catalog only has 1 image. Othewise fail
        log::info!("No 'image.product.filter' defined in SAT file. Checking Cray product catalog only/must have 1 image");
        product_image_map
          .values()
          .next()
          .and_then(|value| value.get("id"))
          .unwrap()
          .as_str()
          .unwrap()
          .to_string()
      };

      // ----------- BASE IMAGE - CRAY PRODUCT CATALOG TYPE RECIPE
      base_image_id = if product_type == "recipes" {
        // Create base image from an IMS job (the 'id' field in
        // images[].base.product.id is the id of the IMS recipe used to
        // build the new base image)

        log::info!("SAT file - 'image.base.product' job based on IMS recipes");

        let product_recipe_id = image_id.clone();

        process_sat_file_image_product_type_ims_recipe(
          shasta_token,
          shasta_base_url,
          shasta_root_cert,
          &product_recipe_id,
          &image_name,
          dry_run,
        )
        .await
        .unwrap()

        // ----------- BASE IMAGE - CRAY PRODUCT CATALOG TYPE IMAGE
      } else if product_type == "images" {
        // Base image already created and its id is available in the Cray
        // product catalog

        log::info!("SAT file - 'image.base.product' job based on IMS images");

        log::info!("Getting base image id from Cray product catalog");

        let product_image_id = image_id;

        product_image_id
      } else {
        return Err(Error::Message(
                    "Can't process SAT file, field 'images.base.product.type' must be either 'images' or 'recipes'. Exit".to_string(),
                ));
      }
    } else {
      return Err(Error::Message(
        "Can't process SAT file 'images.base.product' is missing. Exit"
          .to_string(),
      ));
    }
  } else {
    return Err(Error::Message(
      "Can't process SAT file 'images.base' is missing. Exit".to_string(),
    ));
  }

  if configuration_name.is_empty() {
    log::info!("No CFS session needs to be created since there is no CFS configuration assigned to this image");
    println!(
      "Image '{}' imported image_id '{}'",
      image_name, base_image_id
    );

    Ok(base_image_id)
  } else {
    // Create a CFS session
    log::info!("Creating CFS session");

    // Create CFS session
    let session_name = image_name.clone();

    let cfs_session = CfsSessionPostRequest::new(
      session_name,
      &configuration_name,
      None,
      ansible_verbosity_opt,
      ansible_passthrough_opt,
      true,
      Some(&groups_name),
      Some(&base_image_id),
    );

    if !dry_run {
      let cfs_session_rslt = cfs::session::i_post_sync(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        vault_base_url,
        site_name,
        // vault_secret_path,
        // vault_role_id,
        k8s_api_url,
        &cfs_session,
        watch_logs,
        timestamps,
      )
      .await;

      let cfs_session = match cfs_session_rslt {
        Ok(cfs_session) => cfs_session,
        Err(e) => {
          return Err(Error::Message(format!(
            "Could not create Image. Reason:\n{}",
            e
          )));
          /* eprintln!("ERROR - Could not create Image. Reason:\n{}", e);
          std::process::exit(1); */
        }
      };

      if !cfs_session.is_success() {
        return Err(Error::Message(format!(
          "CFS session '{}' failed. Exit",
          cfs_session.name.unwrap()
        )));
      }

      let image_id = cfs_session.first_result_id().unwrap_or_default();
      println!("Image '{}' ({}) created", image_name, image_id);

      Ok(image_id.to_string())
    } else {
      println!(
        "Dry run mode: Create CFS session:\n{}",
        serde_json::to_string_pretty(&cfs_session)?
      );

      let image_id = Uuid::new_v4().to_string();

      println!(
        "Dry run mode: Image '{}' ({}) created",
        image_name, image_id
      );

      Ok(image_id)
    }
  }
}

async fn process_sat_file_image_product_type_ims_recipe(
  shasta_token: &str,
  shasta_base_url: &str,
  shasta_root_cert: &[u8],
  recipe_id: &str,
  image_name: &str,
  dry_run: bool,
) -> Result<String, Error> {
  // Get root public ssh key
  let root_public_ssh_key_value: serde_json::Value =
    ims::public_keys::http_client::v3::get_single(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      "mgmt root key",
    )
    .await
    .unwrap();

  let root_public_ssh_key = root_public_ssh_key_value["id"].as_str().unwrap();

  // let ims_job = ims::job::types::JobPostRequest {
  let ims_job = ims::job::types::Job {
    job_type: "create".to_string(),
    image_root_archive_name: image_name.to_string(),
    kernel_file_name: Some("vmlinuz".to_string()),
    initrd_file_name: Some("initrd".to_string()),
    kernel_parameters_file_name: Some("kernel-parameters".to_string()),
    artifact_id: recipe_id.to_string(),
    public_key_id: root_public_ssh_key.to_string(),
    ssh_containers: None, // Should this be None ???
    enable_debug: Some(false),
    build_env_size: Some(15),
    require_dkms: None, // FIXME: check SAT file and see if this value needs to be set
    id: None,
    created: None,
    status: None,
    kubernetes_job: None,
    kubernetes_service: None,
    kubernetes_configmap: None,
    resultant_image_id: None,
    kubernetes_namespace: None,
    arch: None,
  };

  let ims_job = if dry_run {
    println!(
      "Dry run mode: Create IMS job:\n{}",
      serde_json::to_string_pretty(&ims_job)?
    );
    let mut dry_run_ims_job = ims_job;
    dry_run_ims_job.resultant_image_id = Some(Uuid::new_v4().to_string());
    dry_run_ims_job
  } else {
    ims::job::http_client::post_sync(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      &ims_job,
    )
    .await
    .unwrap()
  };

  Ok(ims_job.resultant_image_id.unwrap())
}

async fn process_sat_file_image_ims_type_recipe(
  shasta_token: &str,
  shasta_base_url: &str,
  shasta_root_cert: &[u8],
  sat_file_image_base_ims_value_yaml: &serde_yaml::Value,
  image_name: &String,
  dry_run: bool,
) -> Result<String, Error> {
  // Base image needs to be created from a IMS job using an IMS recipe
  let recipe_name =
    sat_file_image_base_ims_value_yaml["name"].as_str().unwrap();

  // Get all IMS recipes
  let recipe_detail_vec: Vec<ims::recipe::types::RecipeGetResponse> =
    ims::recipe::http_client::get(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      None,
    )
    .await
    .unwrap();

  // Filter recipes by name
  let recipe_detail_opt = recipe_detail_vec
    .iter()
    .find(|recipe| recipe.name == recipe_name);

  log::info!("IMS recipe details:\n{:#?}", recipe_detail_opt);

  // Check recipe with requested name exists
  let recipe_id = if let Some(recipe_detail) = recipe_detail_opt {
    recipe_detail.id.as_ref().unwrap()
  } else {
    return Err(Error::Message(format!(
      "IMS recipe with name '{}' - not found. Exit",
      recipe_name
    )));
  };

  log::info!("IMS recipe id found '{}'", recipe_id);

  // Get root public ssh key
  let root_public_ssh_key_value: serde_json::Value =
    ims::public_keys::http_client::v3::get_single(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      "mgmt root key",
    )
    .await
    .unwrap();

  let root_public_ssh_key = root_public_ssh_key_value["id"].as_str().unwrap();

  let ims_job = ims::job::types::Job {
    job_type: "create".to_string(),
    image_root_archive_name: image_name.to_string(),
    kernel_file_name: Some("vmlinuz".to_string()),
    initrd_file_name: Some("initrd".to_string()),
    kernel_parameters_file_name: Some("kernel-parameters".to_string()),
    artifact_id: recipe_id.to_string(),
    public_key_id: root_public_ssh_key.to_string(),
    ssh_containers: None, // Should this be None ???
    enable_debug: Some(false),
    build_env_size: Some(15),
    require_dkms: None, // FIXME: check SAT file and see if this value needs to be set
    id: None,
    created: None,
    status: None,
    kubernetes_job: None,
    kubernetes_service: None,
    kubernetes_configmap: None,
    resultant_image_id: None,
    kubernetes_namespace: None,
    arch: None,
  };

  let ims_job = if dry_run {
    println!(
      "Dry run mode: Create IMS job:\n{}",
      serde_json::to_string_pretty(&ims_job)?
    );
    ims_job.into()
  } else {
    ims::job::http_client::post_sync(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      &ims_job,
    )
    .await
    .unwrap()
  };

  log::info!("IMS job response:\n{:#?}", ims_job);

  Ok(ims_job.resultant_image_id.unwrap())
}

fn process_sat_file_image_old_version(
  sat_file_image_ims_value_yaml: &serde_yaml::Value,
) -> Result<String, Error> {
  if sat_file_image_ims_value_yaml
    .get("is_recipe")
    .is_some_and(|is_recipe_value| is_recipe_value.as_bool().unwrap() == false)
    && sat_file_image_ims_value_yaml.get("id").is_some()
  {
    // Create final image from CFS session
    Ok(
      sat_file_image_ims_value_yaml["id"]
        .as_str()
        .unwrap()
        .to_string(),
    )
  } else {
    Err(Error::Message("Functionality not built. Exit".to_string()))
  }
}

fn process_sat_file_image_ref_name(
  sat_file_image_base_image_ref_value_yaml: &serde_yaml::Value,
  ref_name_image_id_hashmap: &HashMap<String, String>,
) -> Result<String, Error> {
  let image_ref: String = sat_file_image_base_image_ref_value_yaml
    .as_str()
    .unwrap()
    .to_string();

  // Process image with 'image_ref' from another image in this same SAT file
  Ok(
    ref_name_image_id_hashmap
      .get(&image_ref)
      .unwrap()
      .to_string(),
  )
}

pub fn filter_product_catalog_images(
  filter: &Value,
  image_map: Map<String, serde_json::Value>,
  image_name: &str,
) -> Result<String, Error> {
  if let Some(arch) = filter.get("arch") {
    // Search image in product catalog and filter by arch
    let image_key_vec = image_map
      .keys()
      .collect::<Vec<_>>()
      .into_iter()
      .filter(|product| {
        product
          .split(".")
          .last()
          .unwrap()
          .eq(arch.as_str().unwrap())
      })
      .collect::<Vec<_>>();

    if image_key_vec.is_empty() {
      Err(Error::Message(format!(
        "Product catalog for image '{}' not found. Exit",
        image_name
      )))
    } else if image_key_vec.len() > 1 {
      Err(Error::Message(format!(
        "Product catalog for image '{}' multiple items found. Exit",
        image_name
      )))
    } else {
      let image_key = image_key_vec.first().cloned().unwrap();
      Ok(
        image_map.get(image_key).unwrap()["id"]
          .as_str()
          .unwrap()
          .to_string(),
      )
    }
  } else if let Some(wildcard) = filter.get("wildcard") {
    // Search image in product catalog and filter by wildcard
    let image_key_vec = image_map
      .keys()
      .filter(|product| product.contains(wildcard.as_str().unwrap()))
      .collect::<Vec<_>>();

    if image_key_vec.is_empty() {
      Err(Error::Message(format!(
        "Product catalog for image '{}' not found. Exit",
        image_name
      )))
    } else if image_key_vec.len() > 1 {
      Err(Error::Message(format!(
        "Product catalog for image '{}' multiple items found. Exit",
        image_name
      )))
    } else {
      let image_key = image_key_vec.first().cloned().unwrap();
      Ok(
        image_map.get(image_key).unwrap()["id"]
          .as_str()
          .unwrap()
          .to_string(),
      )
    }
  } else if let Some(prefix) = filter.get("prefix") {
    // Search image in product catalog and filter by prefix
    let image_key_vec = image_map
      .keys()
      .filter(|product| {
        product
          .strip_prefix(&prefix.as_str().unwrap().to_string())
          .is_some()
      })
      .collect::<Vec<_>>();

    if image_key_vec.is_empty() {
      Err(Error::Message(format!(
        "Product catalog for image '{}' not found. Exit",
        image_name
      )))
    } else if image_key_vec.len() > 1 {
      Err(Error::Message(format!(
        "Product catalog for image '{}' multiple items found. Exit",
        image_name
      )))
    } else {
      let image_key = image_key_vec.first().cloned().unwrap();
      Ok(
        image_map.get(image_key).unwrap()["id"]
          .as_str()
          .unwrap()
          .to_string(),
      )
    }
  } else {
    Err(Error::Message(format!(
      "Product catalog for image '{}' not found. Exit",
      image_name
    )))
  }
}

pub fn validate_sat_file_images_section(
  image_yaml_vec: &Vec<Value>,
  configuration_yaml_vec: &Vec<Value>,
  hsm_group_available_vec: &[&str],
  cray_product_catalog: &BTreeMap<String, String>,
  image_vec: Vec<ims::image::http_client::types::Image>,
  configuration_vec: Vec<CfsConfigurationResponse>,
  ims_recipe_vec: Vec<ims::recipe::types::RecipeGetResponse>,
) -> Result<(), Error> {
  // Validate 'images' section in SAT file

  for image_yaml in image_yaml_vec {
    // Validate image
    let image_name = image_yaml["name"].as_str().unwrap();

    log::info!("Validate 'image' '{}'", image_name);

    if let Some(image_ims_id_to_find) = image_yaml
      .get("ims")
      .and_then(|ims| ims.get("id").and_then(|id| id.as_str()))
    {
      // Validate base image
      log::info!(
        "Validate 'image' '{}' base image '{}'",
        image_name,
        image_ims_id_to_find
      );

      // Old format
      log::info!(
                "Searching image.ims.id (old format - backward compatibility) '{}' in CSM",
                image_ims_id_to_find,
            );

      let is_image_base_id_in_csm = image_vec.iter().any(
        |image: &ims::image::http_client::types::Image| {
          let image_id = image.id.as_ref().unwrap();
          image_id.eq(image_ims_id_to_find)
        },
      );

      if !is_image_base_id_in_csm {
        return Err(Error::Message(format!(
          "Could not find base image id '{}' in image '{}'. Exit",
          image_ims_id_to_find,
          image_yaml["name"].as_str().unwrap()
        )));
      }
    } else if image_yaml.get("base").is_some() {
      // New format
      if let Some(image_ref_to_find) = image_yaml["base"].get("image_ref") {
        // Validate base image
        log::info!(
          "Validate 'image' '{}' base image '{}'",
          image_name,
          image_ref_to_find.clone().as_str().unwrap()
        );

        // Check there is another image with 'ref_name' that matches this 'image_ref'
        let image_found = image_yaml_vec.iter().any(|image_yaml| {
          image_yaml
            .get("ref_name")
            .is_some_and(|ref_name| ref_name.eq(image_ref_to_find))
        });

        if !image_found {
          return Err(Error::Message(format!(
                                "Could not find image with ref name '{}' in SAT file. Cancelling image build proccess. Exit",
                                image_ref_to_find.as_str().unwrap(),
                            )));
        }
      } else if let Some(image_base_product) = image_yaml["base"].get("product")
      {
        // Check if the 'Cray/HPE product' in CSM exists

        log::info!("Image '{}' base.base.product", image_name);
        log::info!("SAT file - 'image.base.product' job");

        // Base image created from a cray product

        let product_name = image_base_product["name"].as_str().unwrap();

        let product_version = image_base_product["version"].as_str().unwrap();

        let product_type =
          image_base_product["type"].as_str().unwrap().to_string() + "s";

        let product_catalog_rslt = &serde_yaml::from_str::<serde_json::Value>(
          &cray_product_catalog
            .get(product_name)
            .unwrap_or(&"".to_string()),
        );

        let product_catalog = if let Ok(product_catalog) = product_catalog_rslt
        {
          product_catalog
        } else {
          return Err(Error::Message(format!(
            "Product catalog for image '{}' not found. Exit",
            image_name
          )));
        };

        let product_type_opt = product_catalog
          .get(product_version)
          .and_then(|product_version| product_version.get(product_type.clone()))
          .cloned();

        let product_type_opt = if let Some(product_type) = product_type_opt {
          product_type.as_object().cloned()
        } else {
          return Err(Error::Message(format!(
            "Product catalog for image '{}' not found. Exit",
            image_name
          )));
        };

        let image_map: Map<String, serde_json::Value> =
          if let Some(product_type) = &product_type_opt {
            product_type.clone()
          } else {
            return Err(Error::Message(format!(
              "Product catalog for image '{}' not found. Exit",
              image_name
            )));
          };

        log::debug!("CRAY product catalog items related to product name '{}', product version '{}' and product type '{}':\n{:#?}", product_name, product_version, product_type, product_type_opt);

        if let Some(filter) = image_base_product.get("filter") {
          let image_recipe_id =
            filter_product_catalog_images(filter, image_map, image_name);
          image_recipe_id.is_ok()
        } else {
          // There is no 'image.product.filter' value defined in SAT file. Check Cray
          // product catalog only has 1 image. Othewise fail
          log::info!("No 'image.product.filter' defined in SAT file. Checking Cray product catalog only/must have 1 image");
          image_map
            .values()
            .next()
            .is_some_and(|value| value.get("id").is_some())
        };
      } else if let Some(image_base_ims_yaml) = image_yaml["base"].get("ims") {
        // Check if the image exists

        log::info!("Image '{}' base.base.ims", image_name);
        if let Some(image_base_ims_name_yaml) = image_base_ims_yaml.get("name")
        {
          let image_base_ims_name_to_find =
            image_base_ims_name_yaml.as_str().unwrap();

          // Search image in SAT file

          log::info!(
            "Searching base image '{}' related to image '{}' in SAT file",
            image_base_ims_name_to_find,
            image_name
          );

          let mut image_found = image_yaml_vec
            .iter()
            .any(|image_yaml| image_yaml["name"].eq(image_base_ims_name_yaml));

          if !image_found {
            log::warn!(
              "Base image '{}' not found in SAT file, looking in CSM",
              image_base_ims_name_to_find
            );

            if let Some(image_base_ims_type_yaml) =
              image_base_ims_yaml.get("type")
            {
              let image_base_ims_type =
                image_base_ims_type_yaml.as_str().unwrap();
              if image_base_ims_type.eq("recipe") {
                // Base IMS type is a recipe
                // Search in CSM (IMS Recipe)

                log::info!(
                                    "Searching base image recipe '{}' related to image '{}' in CSM",
                                    image_base_ims_name_to_find,
                                    image_name
                                );

                image_found = ims_recipe_vec
                  .iter()
                  .any(|recipe| recipe.name.eq(image_base_ims_name_to_find));

                if !image_found {
                  return Err(Error::Message(format!(
                                        "Could not find IMS recipe '{}' in CSM. Cancelling image build proccess. Exit",
                                        image_base_ims_name_to_find,
                                    )));
                }
              } else {
                // Base IMS type is an image
                // Search in CSM (IMS Image)

                log::info!(
                  "Searching base image '{}' related to image '{}' in CSM",
                  image_base_ims_name_to_find,
                  image_name
                );

                // CFS session sets a custom image name, therefore we can't seach
                // for exact image name but search by substring
                image_found = image_vec.iter().any(|image| {
                  image.name.contains(image_base_ims_name_to_find)
                });

                if !image_found {
                  return Err(Error::Message(format!(
                                        "Could not find image base '{}' in image '{}'. Cancelling image build proccess. Exit",
                                        image_base_ims_name_to_find,
                                        image_name
                                    )));
                }
              }
            } else {
              return Err(Error::Message(format!(
                                "Image '{}' is missing the field base.ims.type. Cancelling image build proccess. Exit",
                                image_base_ims_name_to_find,
                            )));
            }
          }
        } else {
          eprintln!(
            "Image '{}' is missing the field 'base.ims.name'. Exit",
            image_name
          );
        };
      } else {
        return Err(Error::Message(format!(
          "Image '{}' yaml not recognised. Exit",
          image_name
        )));
      }
    } else {
      return Err(Error::Message(format!(
        "Image '{}' neither have 'ims' nor 'base' value. Exit",
        image_name
      )));
    }

    // Validate CFS configuration exists (image.configuration)
    log::info!("Validate 'image' '{}' configuration", image_name);

    if let Some(configuration_yaml) = image_yaml.get("configuration") {
      let configuration_name_to_find = configuration_yaml.as_str().unwrap();

      log::info!(
        "Searching configuration name '{}' related to image '{}' in SAT file",
        configuration_name_to_find,
        image_name
      );

      let mut configuration_found =
        configuration_yaml_vec.iter().any(|configuration_yaml| {
          configuration_yaml["name"]
            .as_str()
            .unwrap()
            .eq(configuration_name_to_find)
        });

      if !configuration_found {
        // CFS configuration in image not found in SAT file, searching in CSM
        log::warn!(
          "Configuration '{}' not found in SAT file, looking in CSM",
          configuration_name_to_find
        );

        log::info!(
          "Searching configuration name '{}' related to image '{}' in CSM",
          configuration_name_to_find,
          image_yaml["name"].as_str().unwrap()
        );

        configuration_found = configuration_vec.iter().any(|configuration| {
          configuration.name.eq(configuration_name_to_find)
        });

        if !configuration_found {
          return Err(Error::Message(format!(
                        "Could not find configuration '{}' in image '{}'. Cancelling image build proccess. Exit",
                        configuration_name_to_find,
                        image_name
                    )));
        }
      }

      // Validate user has access to HSM groups in 'image' section
      log::info!("Validate 'image' '{}' HSM groups", image_name);

      let configuration_group_names_vec: Vec<String> =
        serde_yaml::from_value(image_yaml["configuration_group_names"].clone())
          .unwrap_or(Vec::new());

      //TODO: Get rid of this by making sure CSM admins don't create HSM groups for system
      //wide operations instead of using roles
      let configuration_group_names_vec =
        hsm::group::hacks::filter_system_hsm_group_names(
          configuration_group_names_vec,
        );

      if configuration_group_names_vec.is_empty() {
        return Err(Error::Message(format!("Image '{}' must have group name values assigned to it. Canceling image build process. Exit", image_name)));
      } else {
        for hsm_group in
          configuration_group_names_vec.iter().filter(|&hsm_group| {
            !hsm_group.eq_ignore_ascii_case("Compute")
              && !hsm_group.eq_ignore_ascii_case("Application")
              && !hsm_group.eq_ignore_ascii_case("Application_UAN")
          })
        {
          if !hsm_group_available_vec.contains(&hsm_group.as_str()) {
            return Err(Error::Message(format!
                        (
                        "HSM group '{}' in image '{}' not allowed, List of HSM groups available:\n{:?}. Exit",
                        hsm_group,
                        image_yaml["name"].as_str().unwrap(),
                        hsm_group_available_vec
                    )));
          }
        }
      };
    }
  }

  Ok(())
}

pub fn validate_sat_file_configurations_section(
  configuration_yaml_vec_opt: Option<&Vec<Value>>,
  image_yaml_vec_opt: Option<&Vec<Value>>,
  sessiontemplate_yaml_vec_opt: Option<&Vec<Value>>,
) -> Result<(), Error> {
  // Validate 'configurations' sections
  if configuration_yaml_vec_opt.is_some()
    && !configuration_yaml_vec_opt.unwrap().is_empty()
  {
    if !(image_yaml_vec_opt.is_some()
      && !image_yaml_vec_opt.unwrap().is_empty())
      && !(sessiontemplate_yaml_vec_opt.is_some()
        && !sessiontemplate_yaml_vec_opt.unwrap().is_empty())
    {
      return Err(Error::Message(
                "Incorrect SAT file. Please define either an 'image' or a 'session template'. Exit"
                    .to_string(),
            ));
      /* eprint!(
          "Incorrect SAT file. Please define either an 'image' or a 'session template'. Exit"
      );
      std::process::exit(1); */
    }
  }

  Ok(())
}

pub async fn validate_sat_file_session_template_section(
  shasta_token: &str,
  shasta_base_url: &str,
  shasta_root_cert: &[u8],
  image_yaml_vec_opt: Option<&Vec<Value>>,
  configuration_yaml_vec_opt: Option<&Vec<Value>>,
  session_template_yaml_vec_opt: Option<&Vec<Value>>,
  hsm_group_available_vec: &[&str],
) -> Result<(), Error> {
  // Validate 'session_template' section in SAT file
  log::info!("Validate 'session_template' section in SAT file");
  for session_template_yaml in session_template_yaml_vec_opt.unwrap_or(&vec![])
  {
    // Validate session_template
    let session_template_name = session_template_yaml["name"].as_str().unwrap();

    log::info!("Validate 'session_template' '{}'", session_template_name);

    // Validate user has access to HSM groups in 'session_template' section
    log::info!(
      "Validate 'session_template' '{}' HSM groups",
      session_template_name
    );

    let bos_session_template_hsm_groups: Vec<String> =
      if let Some(boot_sets_compute) =
        session_template_yaml["bos_parameters"]["boot_sets"].get("compute")
      {
        boot_sets_compute["node_groups"]
          .as_sequence()
          .unwrap_or(&vec![])
          .iter()
          .map(|node| node.as_str().unwrap().to_string())
          .collect()
      } else if let Some(boot_sets_compute) =
        session_template_yaml["bos_parameters"]["boot_sets"].get("uan")
      {
        boot_sets_compute["node_groups"]
          .as_sequence()
          .unwrap_or(&vec![])
          .iter()
          .map(|node| node.as_str().unwrap().to_string())
          .collect()
      } else {
        return Err(Error::Message(format!(
          "No HSM group found in session_templates section in SAT file"
        )));
        /* println!("No HSM group found in session_templates section in SAT file");
        std::process::exit(1); */
      };

    for hsm_group in bos_session_template_hsm_groups {
      if !hsm_group_available_vec.contains(&hsm_group.as_str()) {
        return Err(Error::Message(format!(
                    "HSM group '{}' in session_templates {} not allowed, List of HSM groups available {:?}. Exit",
                    hsm_group,
                    session_template_yaml["name"].as_str().unwrap(),
                    hsm_group_available_vec
                )));
      }
    }

    // Validate boot image (session_template.image)
    log::info!(
      "Validate 'session_template' '{}' boot image",
      session_template_name
    );

    if let Some(ref_name_to_find) = session_template_yaml
      .get("image")
      .and_then(|image| image.get("image_ref"))
    {
      // Validate image_ref (session_template.image.image_ref). Search in SAT file for any
      // image with images[].ref_name
      log::info!(
        "Searching ref_name '{}' in SAT file",
        ref_name_to_find.as_str().unwrap(),
      );

      let image_ref_name_found = image_yaml_vec_opt.is_some_and(|image_vec| {
        image_vec.iter().any(|image| {
          image
            .get("ref_name")
            .is_some_and(|ref_name| ref_name.eq(ref_name_to_find))
        })
      });

      if !image_ref_name_found {
        return Err(Error::Message(format!(
          "Could not find image ref '{}' in SAT file. Exit",
          ref_name_to_find.as_str().unwrap()
        )));
      }
    } else if let Some(image_name_substr_to_find) = session_template_yaml
      .get("image")
      .and_then(|image| image.get("ims").and_then(|ims| ims.get("name")))
    {
      // Validate image name (session_template.image.ims.name). Search in SAT file and CSM
      log::info!(
                "Searching image name '{}' related to session template '{}' in SAT file",
                image_name_substr_to_find.as_str().unwrap(),
                session_template_yaml["name"].as_str().unwrap()
            );

      let mut image_found = image_yaml_vec_opt.is_some_and(|image_vec| {
        image_vec.iter().any(|image| {
          image
            .get("name")
            .is_some_and(|name| name.eq(image_name_substr_to_find))
        })
      });

      if !image_found {
        // image not found in SAT file, looking in CSM
        log::warn!(
          "Image name '{}' not found in SAT file, looking in CSM",
          image_name_substr_to_find.as_str().unwrap()
        );
        log::info!(
          "Searching image name '{}' related to session template '{}' in CSM",
          image_name_substr_to_find.as_str().unwrap(),
          session_template_yaml["name"].as_str().unwrap()
        );

        image_found = ims::image::utils::get_fuzzy(
          shasta_token,
          shasta_base_url,
          shasta_root_cert,
          hsm_group_available_vec,
          image_name_substr_to_find.as_str(),
          Some(&1),
        )
        .await
        .is_ok();
      }

      if !image_found {
        return Err(Error::Message(format!(
          "Could not find image name '{}' in session_template '{}'. Exit",
          image_name_substr_to_find.as_str().unwrap(),
          session_template_yaml["name"].as_str().unwrap()
        )));
      }
    } else if let Some(image_id) = session_template_yaml
      .get("image")
      .and_then(|image| image.get("ims").and_then(|ims| ims.get("id")))
    {
      // Validate image id (session_template.image.ims.id). Search in SAT file and CSM
      log::info!(
        "Searching image id '{}' related to session template '{}' in CSM",
        image_id.as_str().unwrap(),
        session_template_yaml["name"].as_str().unwrap()
      );

      let image_found = ims::image::http_client::get(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        image_id.as_str(),
      )
      .await
      .is_ok();

      if !image_found {
        return Err(Error::Message(format!(
          "Could not find image id '{}' in session_template '{}'. Exit",
          image_id.as_str().unwrap(),
          session_template_yaml["name"].as_str().unwrap()
        )));
      }
    } else if let Some(image_name_substr_to_find) =
      session_template_yaml.get("image")
    {
      // Backward compatibility
      // Validate image name (session_template.image.ims.name). Search in SAT file and CSM
      log::info!(
                "Searching image name '{}' related to session template '{}' in CSM - ('sessiontemplate' section in SAT file is outdated - switching to backward compatibility)",
                image_name_substr_to_find.as_str().unwrap(),
                session_template_yaml["name"].as_str().unwrap()
            );

      let image_found = ims::image::utils::get_fuzzy(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        hsm_group_available_vec,
        image_name_substr_to_find.as_str(),
        Some(&1),
      )
      .await
      .is_ok();

      if !image_found {
        // image not found in SAT file, looking in CSM
        return Err(Error::Message(format!(
          "Image name '{}' not found in CSM. Exit",
          image_name_substr_to_find.as_str().unwrap()
        )));
      }
    } else {
      return Err(Error::Message(format!(
                "Session template '{}' must have one of these entries 'image.ref_name', 'image.ims.name' or 'image.ims.id' values. Exit",
                session_template_yaml["name"].as_str().unwrap(),
            )));
    }

    // Validate configuration
    log::info!(
      "Validate 'session_template' '{}' configuration",
      session_template_name
    );

    if let Some(configuration_to_find_value) =
      session_template_yaml.get("configuration")
    {
      let configuration_to_find = configuration_to_find_value.as_str().unwrap();

      log::info!(
                "Searching configuration name '{}' related to session template '{}' in CSM in SAT file",
                configuration_to_find,
                session_template_yaml["name"].as_str().unwrap()
            );

      let mut configuration_found =
        configuration_yaml_vec_opt.is_some_and(|configuration_yaml_vec| {
          configuration_yaml_vec.iter().any(|configuration_yaml| {
            configuration_yaml["name"].eq(configuration_to_find_value)
          })
        });

      if !configuration_found {
        // CFS configuration in session_template not found in SAT file, searching in CSM
        log::warn!("Configuration not found in SAT file, looking in CSM");
        log::info!(
                    "Searching configuration name '{}' related to session_template '{}' in CSM",
                    configuration_to_find,
                    session_template_yaml["name"].as_str().unwrap()
                );

        configuration_found = cfs::configuration::http_client::v3::get(
          shasta_token,
          shasta_base_url,
          shasta_root_cert,
          Some(configuration_to_find),
        )
        .await
        .is_ok();

        if !configuration_found {
          return Err(Error::Message(format!(
            "Could not find configuration '{}' in session_template '{}'. Exit",
            configuration_to_find,
            session_template_yaml["name"].as_str().unwrap(),
          )));
        }
      }
    } else {
      return Err(Error::Message(format!(
        "Session template '{}' does not have 'configuration' value. Exit",
        session_template_yaml["name"].as_str().unwrap(),
      )));
    }
  }

  Ok(())
}

pub async fn process_session_template_section_in_sat_file(
  shasta_token: &str,
  shasta_base_url: &str,
  shasta_root_cert: &[u8],
  ref_name_processed_hashmap: HashMap<String, String>,
  hsm_group_available_vec: &[&str],
  sat_file_yaml: Value,
  reboot: bool,
  dry_run: bool,
) -> Result<(), Error> {
  let empty_vec = Vec::new();
  let bos_session_template_list_yaml = sat_file_yaml["session_templates"]
    .as_sequence()
    .unwrap_or(&empty_vec);

  if bos_session_template_list_yaml.is_empty() {
    log::warn!("No 'session_templates' section found in SAT file. Skipping session template processing");
    return Ok(());
  }

  let mut bos_st_created_vec: Vec<String> = Vec::new();

  for bos_sessiontemplate_yaml in bos_session_template_list_yaml {
    let _bos_sessiontemplate: BosSessionTemplate =
      serde_yaml::from_value(bos_sessiontemplate_yaml.clone())
        .map_err(|e| Error::Message(e.to_string()))?;

    // Get boot image details in BOS sessiontemplate. This is needed to create the BOS
    // sessiontemplate BootSets
    let image_details: ims::image::http_client::types::Image =
      if let Some(bos_sessiontemplate_image) =
        bos_sessiontemplate_yaml.get("image")
      {
        let (image_reference, is_image_id) =
          get_image_reference_from_bos_sessiontemplate_yaml(
            bos_sessiontemplate_image,
            &ref_name_processed_hashmap,
          )?;
        if dry_run {
          let dry_run_mock_image =
            get_image_details_from_bos_sessiontemplate_yaml(
              shasta_token,
              shasta_base_url,
              shasta_root_cert,
              &hsm_group_available_vec,
              &image_reference,
              is_image_id,
            )
            .await
            .unwrap_or_else(|_| {
              // In dry run mode, generate a mock image
              let dry_run_mock_image = if is_image_id {
                // Image reference is an image ID
                ims::image::http_client::types::Image {
                  id: Some(image_reference.to_string()),
                  created: None,
                  name: "dryrun_image".to_string(),
                  link: Some(Link {
                    path: "dryrun_path".to_string(),
                    etag: Some("dryrun_etag".to_string()),
                    r#type: "dryrun_type".to_string(),
                  }),
                  arch: None,
                  metadata: None,
                }
              } else {
                // Image reference is an image name
                ims::image::http_client::types::Image {
                  id: None,
                  created: None,
                  name: image_reference.to_string(),
                  link: Some(Link {
                    path: "dryrun_path".to_string(),
                    etag: Some("dryrun_etag".to_string()),
                    r#type: "dryrun_type".to_string(),
                  }),
                  arch: None,
                  metadata: None,
                }
              };

              dry_run_mock_image
            });

          println!(
            "Dry run mode: Generate mock Image\n{}",
            serde_json::to_string_pretty(&dry_run_mock_image)?
          );

          dry_run_mock_image
        } else {
          get_image_details_from_bos_sessiontemplate_yaml(
            shasta_token,
            shasta_base_url,
            shasta_root_cert,
            &hsm_group_available_vec,
            &image_reference,
            is_image_id,
          )
          .await?
        }
      } else {
        return Err(Error::Message(
          "ERROR: no 'image' section in session_template.\nExit".to_string(),
        ));
        /* eprintln!("ERROR: no 'image' section in session_template.\nExit");
        std::process::exit(1); */
      };

    log::info!("Image with name '{}' found", image_details.name);

    // Get CFS configuration to configure the nodes
    let bos_session_template_configuration_name = bos_sessiontemplate_yaml
      ["configuration"]
      .as_str()
      .unwrap()
      .to_string();

    // Check CFS configuration exists in CSM
    log::info!(
      "Looking for CFS configuration with name: {}",
      bos_session_template_configuration_name
    );

    if dry_run {
      println!(
        "Dry run mode: CFS configuration '{}' found in CSM.",
        bos_session_template_configuration_name
      );
    } else {
      cfs::configuration::http_client::v3::get(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        Some(&bos_session_template_configuration_name),
      )
      .await?;
    };

    // let ims_image_name = image_details.name.to_string();
    let ims_image_etag =
      image_details.link.as_ref().unwrap().etag.as_ref().unwrap();
    let ims_image_path = &image_details.link.as_ref().unwrap().path;
    let ims_image_type = &image_details.link.as_ref().unwrap().r#type;

    let bos_sessiontemplate_name = bos_sessiontemplate_yaml["name"]
      .as_str()
      .unwrap_or("")
      .to_string();

    let mut boot_set_vec: HashMap<String, BootSet> = HashMap::new();

    for (parameter, boot_set) in bos_sessiontemplate_yaml["bos_parameters"]
      ["boot_sets"]
      .as_mapping()
      .unwrap()
    {
      let kernel_parameters = boot_set["kernel_parameters"].as_str().unwrap();
      let arch_opt = boot_set["arch"].as_str().map(|value| value.to_string());

      let node_roles_groups_opt: Option<Vec<String>> = boot_set
        .get("node_roles_groups")
        .and_then(|node_roles_groups| {
          node_roles_groups
            .as_sequence()
            .and_then(|node_role_groups| {
              node_role_groups
                .iter()
                .map(|hsm_group_value| {
                  hsm_group_value
                    .as_str()
                    .map(|hsm_group| hsm_group.to_string())
                })
                .collect()
            })
        });

      // Validate/check user can create BOS sessiontemplates based on node roles. Users
      // with tenant role are not allowed to create BOS sessiontemplates based on node roles
      // however admin tenants are allowed to create BOS sessiontemplates based on node roles
      if !hsm_group_available_vec.is_empty()
        && node_roles_groups_opt
          .clone()
          .is_some_and(|node_roles_groups| !node_roles_groups.is_empty())
      {
        return Err(Error::Message(
          "User type tenant can't user node roles in BOS sessiontemplate. Exit"
            .to_string(),
        ));
      }

      let node_groups_opt: Option<Vec<String>> =
        boot_set.get("node_groups").and_then(|node_groups_value| {
          node_groups_value.as_sequence().and_then(|node_group| {
            node_group
              .iter()
              .map(|hsm_group_value| {
                hsm_group_value
                  .as_str()
                  .map(|hsm_group| hsm_group.to_string())
              })
              .collect()
          })
        });

      //FIXME: Get rid of this by making sure CSM admins don't create HSM groups for system
      //wide operations instead of using roles
      let node_groups_opt = node_groups_opt.map(|node_groups| {
        hsm::group::hacks::filter_system_hsm_group_names(node_groups)
      });

      // Validate/check HSM groups in YAML file session_templates.bos_parameters.boot_sets.<parameter>.node_groups matches with
      // Check hsm groups in SAT file includes the hsm_group_param
      for node_group in node_groups_opt.clone().unwrap_or_default() {
        if !hsm_group_available_vec.contains(&node_group.as_str()) {
          return Err(Error::Message(format!("User does not have access to HSM group '{}' in SAT file under session_templates.bos_parameters.boot_sets.compute.node_groups section. Exit", node_group)));
        }
      }

      // Validate user has access to the xnames in the BOS sessiontemplate
      let node_list_opt: Option<Vec<String>> =
        boot_set.get("node_list").and_then(|node_list_value| {
          node_list_value.as_sequence().and_then(|node_list| {
            node_list
              .into_iter()
              .map(|node_value_value| {
                node_value_value
                  .as_str()
                  .map(|node_value| node_value.to_string())
              })
              .collect()
          })
        });

      // Validate user has access to the list of nodes in BOS sessiontemplate
      if let Some(node_list) = &node_list_opt {
        validate_target_hsm_members(
          shasta_token,
          shasta_base_url,
          shasta_root_cert,
          &node_list.iter().map(|s| s.as_str()).collect::<Vec<&str>>(),
        )
        .await?;
      }

      let cfs = Cfs {
        // clone_url: None,
        // branch: None,
        // commit: None,
        // playbook: None,
        configuration: Some(bos_session_template_configuration_name.clone()),
      };

      let rootfs_provider = boot_set["rootfs_provider"]
        .as_str()
        .map(|value| value.to_string());
      let rootfs_provider_passthrough = boot_set["rootfs_provider_passthrough"]
        .as_str()
        .map(|value| value.to_string());

      let boot_set = BootSet {
        name: None,
        // boot_ordinal: Some(2),
        // shutdown_ordinal: None,
        path: Some(ims_image_path.to_string()),
        r#type: Some(ims_image_type.to_string()),
        etag: Some(ims_image_etag.to_string()),
        kernel_parameters: Some(kernel_parameters.to_string()),
        node_list: node_list_opt,
        node_roles_groups: node_roles_groups_opt,
        node_groups: node_groups_opt,
        rootfs_provider,
        rootfs_provider_passthrough,
        cfs: Some(cfs),
        arch: arch_opt,
      };

      boot_set_vec.insert(parameter.as_str().unwrap().to_string(), boot_set);
    }

    let cfs = Cfs {
      // clone_url: None,
      // branch: None,
      // commit: None,
      // playbook: None,
      configuration: Some(bos_session_template_configuration_name),
    };

    let create_bos_session_template_payload = BosSessionTemplate {
      // template_url: None,
      // name: Some(bos_sessiontemplate_name.clone()),
      name: None,
      description: None,
      // cfs_url: None,
      // cfs_branch: None,
      enable_cfs: Some(true),
      cfs: Some(cfs),
      // partition: None,
      boot_sets: Some(boot_set_vec),
      links: None,
      tenant: None,
    };

    if dry_run {
      println!(
        "Dry run mode: Create BOS sessiontemplate:\n{}",
        serde_json::to_string_pretty(&create_bos_session_template_payload)?
      );

      // Generate a mock name for the BOS session template
      let dry_run_bos_sessiontemplate_name =
        format!("DRYRUN_{}", Uuid::new_v4().to_string());
      println!(
        "Dry Run Mode: BOS sessiontemplate name '{}' created",
        dry_run_bos_sessiontemplate_name
      );
      bos_st_created_vec.push(dry_run_bos_sessiontemplate_name);
    } else {
      let bos_sessiontemplate = bos::template::http_client::v2::put(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        &create_bos_session_template_payload,
        // &create_bos_session_template_payload.name.as_ref().unwrap(),
        &bos_sessiontemplate_name,
      )
      .await?;

      println!(
        "BOS sessiontemplate name '{}' created",
        bos_sessiontemplate_name
      );

      bos_st_created_vec.push(bos_sessiontemplate.name.unwrap())
    }
  }

  // Create BOS session. Note: reboot operation shuts down the nodes and they may not start
  // up... hence we will split the reboot into 2 operations shutdown and start

  if reboot {
    log::info!("Rebooting");

    for bos_st_name in bos_st_created_vec {
      log::info!(
                "Creating BOS session for BOS sessiontemplate '{}' with action 'reboot'",
                bos_st_name
            );

      // BOS session v2
      let bos_session = BosSession {
        name: None,
        tenant: None,
        operation: Some(Operation::Reboot),
        template_name: bos_st_name.clone(),
        limit: None,
        stage: None,
        include_disabled: None,
        status: None,
        components: None,
      };

      if dry_run {
        println!(
          "Dry run mode: Create BOS session:\n{}",
          serde_json::to_string_pretty(&bos_session)?
        );
      } else {
        bos::session::http_client::v2::post(
          shasta_token,
          shasta_base_url,
          shasta_root_cert,
          bos_session,
        )
        .await?;
      }
    }
  }

  // Audit
  let user = common::jwt_ops::get_name(shasta_token).unwrap();
  let username = common::jwt_ops::get_preferred_username(shasta_token).unwrap();

  log::info!(target: "app::audit", "User: {} ({}) ; Operation: Apply cluster", user, username);

  Ok(())
}

/// Returns image reference related to a session template in SAT file.
/// An image refenrece can be:
///     - image_name
///     - image_id
/// Image names are supposed to be fetched using 'get_fuzzy' function (so we increase the probablity of finding the image in CSM if it was created using 'sat bootprep --overwrite-images') while image ids can be fetched
/// by just 'get' function
/// This function returns a tuple with the image reference and a boolean indicating whether the image is
/// an image id or not
fn get_image_reference_from_bos_sessiontemplate_yaml(
  bos_sessiontemplate_image: &Value,
  ref_name_processed_hashmap: &HashMap<String, String>,
) -> Result<(String, bool), Error> {
  if let Some(bos_sessiontemplate_image_ims) =
    bos_sessiontemplate_image.get("ims")
  {
    // Get boot image to configure the nodes
    if let Some(bos_session_template_image_ims_name) =
      bos_sessiontemplate_image_ims.get("name")
    {
      // BOS sessiontemplate boot image defined by name
      let image_name = bos_session_template_image_ims_name
        .as_str()
        .unwrap()
        .to_string();

      Ok((image_name, false))
    } else if let Some(bos_session_template_image_ims_id) =
      bos_sessiontemplate_image_ims.get("id")
    {
      // BOS sessiontemplate boot image defined by id
      let image_id = bos_session_template_image_ims_id
        .as_str()
        .unwrap()
        .to_string();

      Ok((image_id, true))
    } else {
      return Err(Error::Message("ERROR: neither 'image.ims.name' nor 'image.ims.id' fields defined in session_template.".to_string()));
    }
  } else if let Some(bos_session_template_image_image_ref) =
    bos_sessiontemplate_image.get("image_ref")
  {
    // BOS sessiontemplate boot image defined by image_ref
    let image_ref = bos_session_template_image_image_ref
      .as_str()
      .unwrap()
      .to_string();

    let image_id = ref_name_processed_hashmap
      .get(&image_ref)
      .unwrap()
      .to_string();

    Ok((image_id, true))
  } else if let Some(image_name_substring) = bos_sessiontemplate_image.as_str()
  {
    let image_name = image_name_substring;
    // Backward compatibility
    // Get base image details

    Ok((image_name.to_string(), false))
  } else {
    return Err(Error::Message("ERROR: neither 'image.ims' nor 'image.image_ref' nor 'image.<image id>' sections found in session_template.image.\nExit".to_string()));
  }
}

async fn get_image_details_from_bos_sessiontemplate_yaml(
  shasta_token: &str,
  shasta_base_url: &str,
  shasta_root_cert: &[u8],
  hsm_group_available_vec: &[&str],
  image_reference: &str,
  is_image_id: bool,
) -> Result<ims::image::http_client::types::Image, Error> {
  let image = if is_image_id {
    ims::image::http_client::get(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      Some(&image_reference),
    )
    .await
    .map(|image_vec| image_vec.first().cloned().unwrap())
  } else {
    ims::image::utils::get_fuzzy(
      shasta_token,
      shasta_base_url,
      shasta_root_cert,
      hsm_group_available_vec,
      Some(&image_reference),
      Some(&1),
    )
    .await
    .map(|image_vec| image_vec.first().cloned().unwrap())
  };

  image

  /* if let Some(bos_sessiontemplate_image_ims) = bos_sessiontemplate_image.get("ims") {
      // Get boot image to configure the nodes
      if let Some(bos_session_template_image_ims_name) = bos_sessiontemplate_image_ims.get("name")
      {
          // BOS sessiontemplate boot image defined by name
          let bos_session_template_image_name = bos_session_template_image_ims_name
              .as_str()
              .unwrap()
              .to_string();

          // Get base image details
          ims::image::utils::get_fuzzy(
              shasta_token,
              shasta_base_url,
              shasta_root_cert,
              hsm_group_available_vec,
              Some(&bos_session_template_image_name),
              Some(&1),
          )
          .await
          .map(|image_vec| image_vec.first().cloned().unwrap())
      } else if let Some(bos_session_template_image_ims_id) =
          bos_sessiontemplate_image_ims.get("id")
      {
          // BOS sessiontemplate boot image defined by id
          let bos_session_template_image_id = bos_session_template_image_ims_id
              .as_str()
              .unwrap()
              .to_string();

          // Get base image details
          ims::image::http_client::get(
              shasta_token,
              shasta_base_url,
              shasta_root_cert,
              Some(&bos_session_template_image_id),
          )
          .await
          .map(|image_vec| image_vec.first().cloned().unwrap())
      } else {
          return Err(Error::Message("ERROR: neither 'image.ims.name' nor 'image.ims.id' fields defined in session_template.".to_string()));
      }
  } else if let Some(bos_session_template_image_image_ref) =
      bos_sessiontemplate_image.get("image_ref")
  {
      // BOS sessiontemplate boot image defined by image_ref
      let image_ref = bos_session_template_image_image_ref
          .as_str()
          .unwrap()
          .to_string();

      let image_id = ref_name_processed_hashmap
          .get(&image_ref)
          .unwrap()
          .to_string();

      // Get Image by id
      ims::image::http_client::get(
          shasta_token,
          shasta_base_url,
          shasta_root_cert,
          Some(&image_id),
      )
      .await
      .map(|image_vec| image_vec.first().cloned().unwrap())
  } else if let Some(image_name_substring) = bos_sessiontemplate_image.as_str() {
      let image_name = image_name_substring;
      // Backward compatibility
      // Get base image details
      log::info!("Looking for IMS image which name contains '{}'", image_name);

      ims::image::utils::get_fuzzy(
          shasta_token,
          shasta_base_url,
          shasta_root_cert,
          hsm_group_available_vec,
          Some(&image_name),
          None,
      )
      .await
      .map(|image_vec| image_vec.first().cloned().unwrap())
  } else {
      return Err(Error::Message("ERROR: neither 'image.ims' nor 'image.image_ref' nor 'image.<image id>' sections found in session_template.image.\nExit".to_string()));
  } */
}
