#![deny(clippy::mem_forget)]

/// Mitre database name
pub const MITRE_DB_NAME: &str = "mitre.db";

/// NVD link Rest API for CVE request
pub const NVD_CVE_LINK: &str = "https://services.nvd.nist.gov/rest/json/cves/2.0";

/// NVD api key name to use to by the underlying OS
pub const NVD_KEY_NAME: &str = "NVD_API_KEY";

/// Space substitution in URL
const URL_SPACE: &str = "%20";

/// The `package` module provides all the necessary components
/// for managing and manipulating software packages.
/// This module includes handling errors, defining package concepts,
/// managing facilities, and other related utilities.
pub mod package {
    /// The `concepts` module defines core structures and traits for handling
    /// package information, changelogs, and CVE (Common Vulnerabilities and Exposures) management
    ///
    pub mod concepts {
        pub use crate::cve::Cve;
        use crate::facilities::Uppercase;
        use crate::package::errors::write_error_message_and_exit;
        use crate::{header, section};
        use regex::Regex;
        use std::fmt::{Display, Formatter};
        use std::ops::Add;
        use std::process::Output;
        use termint::enums::Color;
        use termint::enums::Modifier;
        use termint::widgets::ToSpan;

        /// Represents a package status that's representing the supported status.
        ///
        #[derive(Eq, PartialEq, Copy, Clone)]
        pub enum PackageStatus {
            /// The package is currently installed on the system.
            Installed,

            /// The package is not found on the system.
            NotFound,

            /// The package has been uninstalled from the system. In some system, this status can be
            /// appearing after a package uninstallation.
            Uninstalled,
        }

        /// The `Package` struct is used to encapsulate information about a software package,
        /// such as the package's name, its version, and its current status in the system.
        #[derive(PartialEq, Eq, Clone)]
        pub struct Package {
            /// The name of the package.
            pub name: String,

            /// The version of the package.
            pub version: String,

            /// The current status of the package (e.g., see corresponding enum).
            pub status: PackageStatus,
        }

        impl Display for Package {
            fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
                write!(formatter, "{} => {}", self.name, self.version)
            }
        }

        /// Creates a default `Package` instance.
        ///
        /// This function initializes a `Package` with default values for its fields.
        /// It can be used when a default or placeholder instance of `Package` is required.
        ///
        /// # Returns
        ///
        /// A `Package` instance with default values:
        /// * name as an empty string,
        /// * version as an empty string,
        /// * status set to `PackageStatus::NotFound`.
        pub fn default_package() -> Package {
            Package {
                name: String::new(),
                version: String::new(),
                status: PackageStatus::NotFound,
            }
        }

        /// Represents a repository that can hold any type of repository type. The repository type
        /// depends on the underlying operating system.
        pub struct Repository<T> {
            /// The effective repository
            pub repos: T,
        }

        /// Trait defining standard requests related to packages.
        pub trait Request {
            /// Asks for a package given its name and version, returning a `Package` object.
            ///
            /// # Arguments
            ///
            /// * `package` - A string slice that holds the name of the package.
            /// * `version` - A string slice that holds the version of the package.
            ///
            /// # Returns
            ///
            /// A Package object.
            ///
            fn ask_package(&self, package: &str, version: &str) -> Package;

            /// Asks for the changelog of a package. The aim of this command is the access of a standard
            /// way to the changelog's package. The standard way means the changelog content thanks to
            /// the packaging standard feature of the current system.
            ///
            /// # Arguments
            ///
            /// * `package` - The `Package` object for which the changelog is requested.
            ///
            /// # Returns
            ///
            /// The changelog content.
            ///
            fn ask_changelog_package(&self, package: Package) -> String;

            /// Reads the changelog local files of a package. As the opposite of `ask_changelog_package`
            /// this method is an alternative way to access to local changelogs. Thanks to this method,
            /// it's possible to access to local changelog files if the standard way give no data.
            ///
            /// # Arguments
            ///
            /// * `package` - The `Package` object for which the changelog files are to be read.
            ///
            /// # Returns
            ///
            /// The local changelog files content.
            ///
            fn read_changelog_files(&self, package: Package) -> String;
        }

        /// Displays a list of CVEs (Common Vulnerabilities and Exposures) for a given package.
        ///
        /// # Arguments
        /// * `cve_list` - A vector containing CVE entries.
        /// * `package` - The package for which the CVEs are being shown.
        ///
        /// # Examples
        ///
        /// The arguments are showing with the form like this (eg: with command `get-cve less`):
        /// ```shell
        /// [less => 2.9+b1]
        /// CVE-2023-1234
        /// CVE-2024-2232
        /// CVE-2024-5664
        /// ```
        ///
        pub fn show_cve_list(cve_list: Vec<Cve>, package: Package) {
            if cve_list.is_empty() {
                println!("No cve for: {}", package);
            } else {
                println!("{}", header!(package));
                cve_list.iter().for_each(print_cve);
            }
        }

        /// Print the cve
        ///
        /// # Arguments
        ///
        /// * `cve`- The cve to display
        ///
        pub fn print_cve(cve: &Cve) {
            println!(" {}", cve);
        }

        /// Filters a list of CVEs based on an optional filter string.
        ///
        /// # Arguments
        ///
        /// * `cve_list` - A vector of `Cve` objects to be filtered.
        ///   - Each `Cve` represents a Common Vulnerabilities and Exposures entry.
        ///
        /// * `filter` - An optional reference to a string to filter the `cve_list`.
        ///   - If `Some` filter is provided, it will be used for filtering the list of CVEs.
        ///   - If `None`, the original list will be returned with no filter applyied.
        ///
        /// # Returns
        ///
        /// * A vector of filtered `Cve` objects based on the provided filter string or the original list if no filter is provided.
        ///
        /// # Examples
        ///
        /// ```shell
        /// get-cve less --filter 2023
        ///
        /// [less => 2.9+b1]
        /// CVE-2023-1234
        /// ```
        ///
        pub fn filtering_cve(cve_list: Vec<Cve>, filter: Option<&String>) -> Vec<Cve> {
            match filter {
                Some(filter) => filter_cve_list(cve_list, &filter.to_uppercase()),
                None => cve_list,
            }
        }

        fn filter_cve_list(cve_list: Vec<Cve>, filter: &str) -> Vec<Cve> {
            let filter_regex = Regex::new(filter).unwrap();
            cve_list
                .into_iter()
                .filter(|cve| filter_regex.is_match(&cve.reference))
                .collect()
        }

        /// Trait defining a contract for extracting CVEs from changelog content.
        ///
        pub trait Changelog {
            /// Extracts a list of CVEs from the given changelog content.
            ///
            /// # Arguments
            ///
            /// * `changelog_content` - A string slice that holds the content of the changelog.
            ///
            /// # Returns
            ///
            /// A vector of `Cve` structs that were found in the changelog content.
            fn cve_list(&self, changelog_content: &str) -> Vec<Cve>;
        }

        impl Changelog for Package {
            /// Implementation of the `cve_list` method for the `Package` struct.
            ///
            /// This method parses the provided changelog content and returns a list of CVEs.
            ///
            /// # Note
            ///
            /// The actual implementation uses a regex to search for CVE entries in the provided
            /// changelog content.
            fn cve_list(&self, changelog_content: &str) -> Vec<Cve> {
                const CVE_REGEX_PATTERN: &str = "CVE-[0-9]{4}-[0-9]+";
                let cve_regex = Regex::new(CVE_REGEX_PATTERN).unwrap();

                let mut cve_found: Vec<Cve> = extract_cves(&cve_regex, changelog_content);
                simplify_cve_list_content(&mut cve_found);

                cve_found
            }
        }

        fn extract_cves(cve_regex: &Regex, content: &str) -> Vec<Cve> {
            let mut cve_list = Vec::new();
            if cve_regex.is_match(&content.to_uppercase()) {
                for cve in cve_regex
                    .captures_iter(content)
                    .filter_map(|c| c.get(0).map(|m| m.as_str().to_string()))
                {
                    cve_list.push(Cve::new(cve));
                }
            }
            cve_list
        }

        /// Generates a changelog result based on the output of a changelog command. The alternative
        /// changelog files content are using in complement of the standard command uses to ensure to collect
        /// the maximum of cve data.
        ///
        /// # Arguments
        ///
        /// * `package` - A `Package` struct representing the package for which the changelog is searched.
        /// * `changelog_command` - An `Output` struct representing the output of the changelog command.
        /// * `local_changelog_content` - A `String` that represents the local changelog files content to use to
        ///   with the standard changelog command access.
        ///
        /// # Returns
        ///
        /// Returns a `String` containing the changelog result if the command was successful, or an empty string otherwise.
        ///
        pub fn changelog_result(
            package: Package,
            changelog_command: Output,
            local_changelog_content: String,
        ) -> String {
            let is_success = changelog_command.status.success();
            if is_success {
                handle_success(
                    changelog_command.stdout,
                    package.name.as_str(),
                    &local_changelog_content,
                )
            } else {
                String::new()
            }
        }

        fn handle_success(stdout: Vec<u8>, package_name: &str, alternative: &str) -> String {
            handle_stdout(stdout, package_name).add(alternative)
        }

        fn handle_stdout(stdout: Vec<u8>, package_name: &str) -> String {
            String::from_utf8(stdout).unwrap_or_else(|_| {
                handle_output_error(package_name);
                String::new()
            })
        }

        fn handle_output_error(package_name: &str) {
            write_error_message_and_exit("Output error", Some(package_name));
        }

        /// Simplifies the content of a list of CVEs by sorting and removing duplicates.
        ///
        /// # Arguments
        ///
        /// * `cve_list` - A mutable reference to a vector of `Cve` objects to be simplified.
        ///   - This vector will be sorted in place and then deduplicated, removing any duplicate `Cve` entries.
        ///
        pub fn simplify_cve_list_content(cve_list: &mut Vec<Cve>) {
            cve_list.sort();
            cve_list.dedup();
        }
    }

    /// The `errors` module contains utilities for handling errors related to packages.
    ///
    pub mod errors {
        use crate::package::concepts::Package;
        use std::process::exit;

        /// Stops the execution if the package is invalid.
        ///
        /// # Arguments
        /// * `package` - A reference to the `Package` to be validated.
        ///
        /// An invalid package is one where both name and version are empty.
        /// In such cases, an error message is written and the process exits.
        ///
        pub fn stop_if_invalid_package(package: &Package) {
            if package.name.is_empty() && package.version.is_empty() {
                write_error_message_and_exit("Unsupported OS", None);
            }
        }

        /// Writes an error message with an optional argument and exits the process with a status code of 1.
        ///
        /// # Arguments
        /// * `message` - The error message to be written.
        /// * `argument` - An optional argument to be included in the error message.
        ///
        pub fn write_error_message_and_exit(message: &str, argument: Option<&str>) {
            write_error_message(message, argument);
            exit(1);
        }

        /// Writes an error message to standard error with an optional argument. No exit value.
        ///
        /// # Arguments
        /// * `argument` - An optional argument to be included in the error message.
        /// * `message` - The error message to be written.
        ///
        pub fn write_error_message(message: &str, argument: Option<&str>) {
            let final_message = match argument {
                Some(a) => format!("{}: {}", message, a),
                None => message.to_string(),
            };

            eprintln!("{final_message}");
        }
    }
}

/// The `facilities` module provides utility functions for handling
/// temporary directories and processing command results.
pub mod facilities {
    use crate::package::errors::write_error_message_and_exit;
    use chrono::{Datelike, Days, Local};
    use csv::{Reader, ReaderBuilder};
    use enable_ansi_support::enable_ansi_support;
    use regex::Regex;
    use serde_json::Value;
    use std::fs::File;
    use std::ops::Add;
    use std::path::{Path, PathBuf};
    use std::process::Output;
    use std::string::FromUtf8Error;
    use std::{env, io};
    use termint::enums::Color;
    use termint::enums::Modifier;
    use termint::widgets::ToSpan;

    /// Shows a section for presentation.
    ///
    /// # Argument
    ///
    /// * `title`- Title's section
    /// * `text_color`- Text's color. If `None` uses the default color.
    /// * `background_color`- Text background's color. If `None` uses the default color.
    #[macro_export]
    macro_rules! section {
        ($value:expr) => {
            format!(" [ {} ]", $value.to_string().first_uppercase())
                .as_str()
                .modifier(Modifier::BOLD)
        };

        ($value:expr, $text:expr) => {
            section!($value).fg($text)
        };

        ($value:expr, $text:expr, $back:expr) => {
            section!($value, $text).bg($back)
        };
    }

    #[macro_export]
    macro_rules! header {
        ($value:expr) => {
            section!($value.to_string().to_ascii_uppercase()).fg(Color::Green)
        };
    }

    /// Prints a slice of `Value` items, cleaning each value and separating them by commas.
    ///
    /// # Arguments
    ///
    /// * `values` - A reference to a slice of `Value` objects that will be printed.
    ///
    pub fn print_values(values: &[Value]) {
        for value in values {
            if !value.is_object() {
                let cleaned_value = String::cleaning(format!("{value}"));
                println!("{:>4} {}", "-", cleaned_value);
            } else if value.is_object() {
                let map_value = value.as_object().unwrap();
                for (key, value) in map_value {
                    println!("{:>3} {}:", ">", String::wording(key));
                    if value.is_array() {
                        print_values(value.as_array().unwrap());
                    } else {
                        print_values(std::slice::from_ref(value));
                    }
                }
            }
        }
    }

    /// Returns the current date as a formatted string.
    ///
    /// # Examples
    /// ```
    /// use fenir::facilities::get_current_date;
    /// let current_date = get_current_date();
    /// println!("Today's date is: {}", current_date); // Output: "2023-10-05" (value may vary based on the current date)
    /// ```
    ///
    /// # Returns
    /// A `String` containing the current date in the format "YYYY-MM-DD".
    ///
    /// # Dependencies
    /// - `chrono`: This function uses the `chrono` crate for date and time operations.
    ///
    /// # Panics
    /// This function should not panic under normal circumstances.
    pub fn get_current_date() -> String {
        let local_date = Local::now();
        format!(
            "{}-{:02}-{:02}",
            local_date.year(),
            local_date.month(),
            local_date.day()
        )
    }

    /// Constructs a date range string from the provided arguments.
    ///
    /// This function extracts two dates from the `args` vector based on the
    /// index provided by `option_new_date`, and returns them concatenated
    /// with a semicolon (`;`) delimiter.
    ///
    /// # Arguments
    ///
    /// * `option_new_date` - An optional index pointing to the position
    ///   in `args` where the dates should be extracted.
    /// * `args` - A vector of strings from which the dates are to be extracted.
    ///
    /// # Returns
    ///
    /// A `String` containing the concatenated date range, or an empty string
    /// if the dates aren't found.
    ///
    /// # Examples
    ///
    /// ```
    /// use fenir::facilities::build_dates_range;
    /// let dates = build_dates_range(Some(0), &["".to_string(), "2023-01-01".to_string(), "2023-12-31".to_string()]);
    /// assert_eq!(dates, "2023-01-01;2023-12-31");
    /// ```
    pub fn build_dates_range(option_new_date: Option<usize>, args: &[String]) -> String {
        let mut dates = String::new();
        let regex = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
        if let Some(date_first) = args.get(option_new_date.unwrap() + 1) {
            if regex.is_match(date_first.as_str()) {
                dates = dates.add(date_first);
            } else {
                dates = create_default_date();
            }
        } else {
            dates = create_default_date();
        }

        if let Some(date_last) = args.get(option_new_date.unwrap() + 2) {
            if regex.is_match(date_last.as_str()) {
                dates = dates.add(";").add(date_last);
            } else {
                dates = create_next_date(dates);
            }
        } else {
            dates = create_next_date(dates);
        }

        dates
    }

    fn create_next_date(dates: String) -> String {
        let date_next = Local::now();
        let mut date_result = String::new();
        let result = date_next.checked_add_days(Days::new(1));
        if let Some(date) = result {
            date_result = dates
                .add(";")
                .add(date.format("%Y-%m-%d").to_string().as_str());
        }

        date_result
    }

    fn create_default_date() -> String {
        let date_current = Local::now();
        date_current.format("%Y-%m-%d").to_string()
    }

    /// Restores the current directory to its previous state and removes the specified temporary directory.
    ///
    /// # Arguments
    /// * `current_dir` - A result containing the path to the current directory.
    /// * `temp_dir_name` - The name of the temporary directory to be removed.
    ///
    /// This function sets the current directory to the one provided in `current_dir`,
    /// and then removes the temporary directory specified by `temp_dir_name`
    ///
    pub fn restore_current_dir_and_remove_temp_dir(
        current_dir: io::Result<PathBuf>,
        temp_dir_name: &str,
    ) {
        env::set_current_dir(current_dir.unwrap()).expect(
            "Can't restore initial \
                directory",
        );
        let _ = std::fs::remove_dir_all(format!("/var/tmp/{}", temp_dir_name).as_str());
    }

    /// Displays the result of a command execution.
    ///
    /// # Arguments
    /// * `command_result` - An `Output` struct representing the result of a command execution.
    ///
    /// # Returns
    /// * `Result<String, FromUtf8Error>` - The output of the command as a `String` if successful,
    ///   or an error if the output cannot be converted from UTF-8.
    ///
    /// This function checks if the command was successful.
    /// If the command failed, it writes an error message and exits.
    /// If the output of the command cannot be converted to a string, it also writes an error message and exits.
    ///
    pub fn show_command_result(command_result: Output) -> Result<String, FromUtf8Error> {
        if !command_result.status.success() {
            write_error_message_and_exit("Package not found", None);
        }

        let command_output = String::from_utf8(command_result.stdout);
        if command_output.is_err() {
            write_error_message_and_exit("Error on output parsing", None);
        }
        command_output
    }

    /// Trait for cleaning string content. Used for remove the `"`, `FIELD_SEPARATOR_CHAR` characters
    /// from value string.
    pub trait Cleaning {
        fn cleaning(value: String) -> String;
    }

    impl Cleaning for String {
        fn cleaning(value: String) -> String {
            let cleaning_char = ['"', FIELD_SEPARATOR_CHAR];

            let mut final_result = value.clone();

            if value.starts_with(cleaning_char) {
                final_result.remove(0);
            }

            if value.ends_with(cleaning_char) {
                final_result.remove(final_result.len() - 1);
            }

            final_result.replace("\\n", "\n")
        }
    }

    pub trait Wording {
        fn wording(&self) -> String;
    }

    impl Wording for String {
        fn wording(&self) -> String {
            let mut result = String::new();

            let mut chars = self.chars();
            let first = chars.nth(0);

            if let Some(c) = first {
                if c.is_ascii_lowercase() {
                    result.push(c.to_ascii_uppercase());
                } else {
                    result.push(c);
                }

                let mut previous = ' ';

                for x in chars {
                    if x.is_ascii_lowercase() {
                        if previous.is_ascii_uppercase() {
                            result.push(' ');
                        }
                        result.push(x);
                        previous = x;
                    } else if x.is_ascii_uppercase() {
                        if previous.is_ascii_lowercase() {
                            result.push(' ');
                            previous = ' ';
                        }
                        result.push(x.to_ascii_lowercase());
                    } else {
                        result.push(x);
                        previous = x;
                    }
                }
            }

            result
        }
    }

    /// Categorize the String content
    pub trait Categorize {
        /// Verify if the string content is composing only of upper cases.
        ///
        /// # Arguments
        ///
        /// * `string` - The string for analyzing.
        ///
        /// # Returns
        ///
        /// `true` if all string characters are uppercases, otherwise `false`.
        ///
        /// # Notes
        ///
        /// If the string is an empty string, will return `false`.
        fn only_uppercases(string: String) -> bool;
    }

    impl Categorize for String {
        fn only_uppercases(string: String) -> bool {
            let mut status = false;
            for chr in string.chars() {
                if chr == ' ' {
                    continue;
                }

                status = chr.is_ascii_uppercase();

                if !status {
                    break;
                }
            }

            status
        }
    }

    /// String normalization for data displaying. This trait prepares a string into the
    /// format awaiting by `hierarchical_show()` function.
    pub trait Normalize {
        /// Normalize is a process that allows to build a valid String format for hierarchical showing
        ///
        /// # Arguments
        ///
        /// * `self` - Self element on which the normalization will be applied
        /// * `keywords`- Keywords list to take account during the normalization
        ///
        /// # Return
        ///
        /// The new String with the normalization
        ///
        fn normalize(&self, keywords: Vec<&str>) -> String;
    }

    /// Char field separator for fields content long string from database
    const FIELD_SEPARATOR_CHAR: char = ':';

    /// String field separator for fields content long string from database
    pub const FIELD_SEPARATOR_STRING: &str = ":";

    pub const FIELD_SEPARATOR_STRING_LONG: &str = "::";

    impl Normalize for String {
        fn normalize(&self, keywords: Vec<&str>) -> String {
            if self.is_empty() {
                return String::new();
            }

            let elements = self.split(FIELD_SEPARATOR_CHAR).collect::<Vec<&str>>();
            let ref_value = 2;
            let mut counter = 0;
            let mut result = Vec::<String>::new();

            elements.iter().for_each(|s| {
                if s.is_empty() {
                    if counter != ref_value {
                        counter += 1;
                        result.push(FIELD_SEPARATOR_STRING.to_string());
                    } else {
                        counter = 0;
                    }
                } else {
                    if result.last().is_some() && result.last().unwrap() != FIELD_SEPARATOR_STRING {
                        result.push(FIELD_SEPARATOR_STRING.to_string());
                    }
                    if keywords.contains(s) {
                        result.push(s.to_string().to_uppercase());
                    } else if String::only_uppercases(s.to_string()) {
                        result.push(s.to_string().to_lowercase());
                    } else {
                        result.push(s.to_string());
                    }

                    counter = 0;
                }
            });

            begin_complement(&mut result);

            end_complement(&mut result);

            String::from_iter(result)
        }
    }

    fn begin_complement(result: &mut Vec<String>) {
        for x in 0..2 {
            if result[x] != FIELD_SEPARATOR_STRING {
                result.insert(0, FIELD_SEPARATOR_STRING.to_string());
            }
        }
    }

    fn end_complement(result: &mut Vec<String>) {
        for x in result.len() - 2..result.len() {
            if result[x] != FIELD_SEPARATOR_STRING {
                result.push(FIELD_SEPARATOR_STRING.to_string());
            }
        }
    }

    pub trait Transform {
        fn transform(&self, keywords: Vec<&str>) -> String;
    }

    impl Transform for String {
        fn transform(&self, keywords: Vec<&str>) -> String {
            let mut result = String::new();
            let tmp = format!("{}{}", FIELD_SEPARATOR_STRING, FIELD_SEPARATOR_STRING);
            let mark = tmp.as_str();

            let mut previous_key = "";
            for keyword in keywords {
                let regex = Regex::new(keyword.to_string().as_str())
                    .unwrap_or_else(|_| panic!("Invalid regular expression pattern: {}", keyword));
                for sentence in regex.split(self) {
                    if !sentence.is_empty() {
                        if sentence == mark {
                            result.push_str(mark);
                            result.push_str(keyword);
                            previous_key = keyword;
                        } else if sentence.chars().nth(0).unwrap() == FIELD_SEPARATOR_CHAR {
                            if previous_key.is_empty() {
                                result.push_str(mark);
                                result.push_str(keyword);
                            }
                            result.push_str(sentence);
                            previous_key = "";
                        } else {
                            result = result.replace(keyword, "");
                            result.push_str(mark);
                            result.push_str(keyword);
                            result.push(FIELD_SEPARATOR_CHAR);

                            if !sentence.contains(previous_key) || previous_key.is_empty() {
                                result.push_str(sentence);
                            }

                            previous_key = keyword;
                        }
                    }
                }
            }

            if !result.ends_with(mark) && result.ends_with(FIELD_SEPARATOR_STRING) {
                result.push(FIELD_SEPARATOR_CHAR);
            }

            result
        }
    }

    pub trait Reduce {
        fn reduce(&self, size: usize) -> String;
    }

    impl Reduce for String {
        fn reduce(&self, size: usize) -> String {
            if self.is_empty()
                || size == 0
                || size > self.len()
                || self == Value::Null.to_string().as_str()
            {
                self.clone()
            } else if size <= "...".len() {
                String::from("...")
            } else {
                let reduced =
                    self.escape_default().to_string()[0..(size - "...".len())].to_string();
                format!("{reduced}...")
            }
        }
    }

    /// Configures and returns a CSV reader with the specific settings.
    ///
    /// This function sets up a CSV reader with custom options such as delimiter,
    /// headers, and flexible end-of-line handling.
    ///
    /// # Arguments
    /// * `csv_path`- Path to the csv file.
    ///
    /// # Settings
    ///
    /// * `delimiter` - A `u8` byte set on `,` separator.
    /// * `has_headers`- A bool that specifies the CSV contains some header, set to `true`.
    /// * `flexible` - A `bool` that specifies whether to handle flexible end-of-line characters, set to `true`.
    ///
    /// # Returns
    ///
    /// * `csv::Reader<File>` - A `csv::Reader` configured based on the given settings.
    pub fn configure_csv_reader(csv_path: &Path) -> csv::Result<Reader<File>> {
        ReaderBuilder::new()
            .delimiter(b',')
            .flexible(true)
            .has_headers(true)
            .from_path(csv_path)
    }

    /// Shows the element list as a hierarchical design.
    ///
    /// # Arguments
    /// * `section` - The section name for this hierarchy.
    /// * `elements` - List of elements separated by the ':' separator.
    ///
    pub fn hierarchical_show(section: &str, elements: String) {
        if elements.is_empty() {
            return;
        }

        println!();
        let mut width = 0;

        let str_cleaning = String::cleaning(elements);

        if !str_cleaning.contains(FIELD_SEPARATOR_STRING) {
            println!("{:>2} {}", " ", section!(section, Color::Green));
            let w = width + 7;
            println!("{:>w$} {}", "-", str_cleaning.trim());
            return;
        } else {
            println!("{:>2} {}", " ", section!(section, Color::Green));
        }

        let parts: Vec<&str> = str_cleaning.split(FIELD_SEPARATOR_STRING).collect();
        let ref_section = match parts.len() {
            1 => "",
            _ => parts[1],
        };

        let mut old_value = vec![];
        section_show(ref_section, 0);

        let mut last_section = ref_section;

        for part in parts.iter() {
            if part.is_empty() || last_section == *part {
                continue;
            } else if String::only_uppercases(String::from(*part)) {
                last_section = *part;
                if last_section == ref_section {
                    section_show(last_section, 0);
                    width = 0;
                } else {
                    width += 1;
                    section_show(part, width);
                }
                old_value.clear();
            } else if !part.is_empty() && !old_value.contains(part) {
                let w = width + 7;
                println!("{:>w$} {}", "-", part.trim());
                old_value.push(*part);
            }
        }
    }

    fn section_show(value: &str, width: usize) {
        let lower = String::from(value).to_ascii_lowercase();
        let wording = lower.wording();
        let words: Vec<&str> = wording.as_str().split(' ').collect();
        match words.len() {
            1..3 => {
                if !value.is_empty() {
                    let w = width + 3;
                    println!("{:>w$} {}", " ", section!(wording, Color::Cyan));
                }
            }
            _ => {
                if !value.is_empty() {
                    let w = width + 7;
                    println!("{:>w$} {}", "-", value.trim());
                }
            }
        };
    }

    pub trait Uppercase {
        /// Transform the first character of a String to an uppercase
        ///
        /// # Returns
        /// The new string with the first character in uppercase
        fn first_uppercase(&self) -> String;
    }

    impl Uppercase for String {
        fn first_uppercase(&self) -> String {
            let c = self.chars().nth(0).unwrap();
            format!("{}{}", c.to_ascii_uppercase(), &self[1..])
        }
    }

    /// Configure the terminal for insure the ansi support colors
    /// for all platforms.
    pub fn configure_terminal() {
        let _ = enable_ansi_support();
    }
}

/// Several features for specific Os implementation. Provide several methods that can be used
/// for Os support implementations.
pub mod os {
    use std::path::{Path, PathBuf};
    use std::{env, fs};

    /// The `OSFamily` enum represents different families of operating systems.
    ///
    /// This enum provides several variants:
    /// - `Debian`: Represents Debian-based operating systems.
    /// - `macOS`: Represents the macOS operating systems.
    /// - `RedHat`: Represents RedHat-based operating systems.
    /// - `Unsupported`: Represents unsupported or unrecognized operating systems.
    /// - `Windows`: Represents the Windows operating systems.
    ///
    #[derive(PartialEq, Eq)]
    pub enum OSFamily {
        /// Represents Debian-based operating systems.
        Debian,

        /// Represents Macos-based operating systems.
        MacOS,

        /// Represents RedHat-based operating systems.
        RedHat,

        /// Represents unsupported or unrecognized operating systems.
        Unsupported,

        ///Represents the Windows-based operating systems.
        Windows,
    }

    // Constants for file paths
    const DEBIAN_PATH: &str = "/usr/bin/apt-get";
    const MACOS_PATH: &str = "/usr/bin/sw_vers";
    const REDHAT_PATH: &str = "/usr/bin/rpm";
    const WINDOWS_PATH: &str = "c:\\";

    /// Detects the operating system by checking for the existence of certain files.
    ///
    /// # Returns
    ///
    /// * `OSFamily::Debian` - if the system has `/usr/bin/apt-get`.
    /// * `OSFamily::macOS` - if the system has `/usr/bin/sw_vers`.
    /// * `OSFamily::RedHat` - if the system has `/usr/bin/rpm`.
    /// * `OSFamily::Windows` - if the system has `c:\\`.
    /// * `OSFamily::Unsupported` - if none of those files are found.
    pub fn detect_os() -> OSFamily {
        if file_exists(DEBIAN_PATH) {
            OSFamily::Debian
        } else if file_exists(MACOS_PATH) {
            OSFamily::MacOS
        } else if file_exists(REDHAT_PATH) {
            OSFamily::RedHat
        } else if file_exists(WINDOWS_PATH) {
            OSFamily::Windows
        } else {
            OSFamily::Unsupported
        }
    }

    /// Checks if a file exists at the given path.
    fn file_exists(path: &str) -> bool {
        Path::new(path).exists()
    }

    /// Find the tyr home's path directory according to current OS.
    ///
    /// # Returns
    ///
    /// The home path directory. If the OS is not supported, the home tyr path directory will be defined on the current dir.
    pub fn find_tyr_home_path() -> PathBuf {
        match detect_os() {
            OSFamily::Windows => Path::new(Option::unwrap(env::var_os("HOMEDRIVE")).as_os_str())
                .join(Option::unwrap(env::var_os("HOMEPATH")).as_os_str())
                .join(".tyr"),
            OSFamily::Unsupported => Path::new(".").join(".tyr"),
            _ => Path::new(Option::unwrap(env::var_os("HOME")).as_os_str()).join(".tyr"),
        }
    }

    /// Create the tyr home path directory.
    ///
    /// # Arguments
    ///
    /// * `tyr_path` - the tyr path that will be used as tyr home path.
    ///
    /// # Notes
    ///
    /// If the tyr home path directory exists yet, it will not be created.
    pub fn create_tyr_home(tyr_path: &Path) {
        if !tyr_path.exists() {
            fs::create_dir(tyr_path).expect("Can't create tyr home path");
        }
    }

    /// Parse the raw id given as parameter and return the found id as u32
    ///
    /// # Arguments
    ///
    /// * `raw_id` - The raw cwe id of the form of `CWE-<num>` or `CAPEC-<num>`.
    ///
    /// # Results
    ///
    /// The id that is corresponding to the `<num>` part.
    pub fn parse_id(raw_id: &str, prefix: &str) -> Option<u32> {
        let parts: Vec<&str> = raw_id.split('-').collect();
        if parts.len() == 2 && parts[0].eq_ignore_ascii_case(prefix) {
            parts[1].parse().ok()
        } else {
            None
        }
    }
}

/// Module for database features (based on SQLite)
pub mod database {
    use crate::capec::CapecRecord;
    use crate::cwe::CweRecord;
    use crate::download::{download_mitre_archive, QueryMitre};
    use crate::facilities::Uppercase;
    use crate::facilities::FIELD_SEPARATOR_STRING;
    use crate::network::{fetch_response, parse_response};
    use crate::os::{create_tyr_home, find_tyr_home_path};
    use crate::package::errors::write_error_message_and_exit;
    use crate::{section, MITRE_DB_NAME};
    use regex::Regex;
    use rusqlite::{Connection, Rows, Statement};
    use serde_json::Value;
    use std::error::Error;
    use std::path::{Path, PathBuf};
    use termint::enums::Modifier;
    use termint::widgets::ToSpan;

    /// Executes a given query and processes the response.
    ///
    /// This function takes a `Result` containing a `String` query or an error message.
    /// It unwraps the query string, fetches the response from the URL specified by the query,
    /// and then parses this response into a `Value`.
    ///
    /// # Arguments
    ///
    /// * `query` - A `Result` wrapping a `String` query or an error message.
    ///
    /// # Returns
    ///
    /// * A `Value` obtained from parsing the response. If the response fetching or parsing
    ///   fails, it returns a default `Value`.
    ///
    /// # Panics
    ///
    /// This function will panic if `query` is an `Err` variant.
    ///
    /// # Example
    ///
    /// ```rust
    /// use fenir::database::execute_query;
    /// let query = Ok(String::from("http://example.com/api"));
    /// let value = execute_query(query);
    /// ```
    pub fn execute_query(query: Result<String, &str>) -> Value {
        let query_str = query.unwrap();
        let response = fetch_response(&query_str);

        parse_response(response)
    }

    /// Checks if the database is valid or not.
    ///
    /// A valid database is a database with the awaiting `table_name` given as parameter.
    ///
    /// # Arguments
    ///
    /// * `tyr_home`- Tyr home's path where the database can be accessed.
    /// * `table_name`- The table's name to control to.
    ///
    /// # Returns
    ///
    /// The checking status as boolean value.
    ///
    pub fn check_database(tyr_home: &Path, table_name: &str) -> bool {
        let db_path = tyr_home.join(MITRE_DB_NAME);
        if db_path.exists() {
            let conn = Connection::open(db_path).unwrap();
            let query = format!("SELECT COUNT(*) FROM {}", table_name);
            let result = conn.query_row(query.as_str(), [], |row| row.get::<usize, u32>(0));
            result.is_ok()
        } else {
            false
        }
    }

    /// New type for injector function that will be used for injecting csv data into the local
    /// SQLite database.
    type InjectorFunction = fn(&Path, &Path) -> Result<(), Box<dyn Error>>;

    /// Refresh the csv mitre that will be injected into the mitre local database.
    ///
    /// # Arguments
    ///
    /// * `query_mitre` - The query mitre associated to the csv for injection
    /// * `injector`- The method used for injecting the csv into the local database.
    ///
    /// # Results
    ///
    /// The final result of these operations.
    pub fn refresh_csv_mitre_into_database(query_mitre: QueryMitre, injector: InjectorFunction) {
        // Download and extract the archive
        let env_path = find_tyr_home_path();
        create_tyr_home(&env_path);
        match download_mitre_archive(query_mitre.clone(), &env_path) {
            Err(e) => {
                write_error_message_and_exit("Error during download", Some(e.to_string().as_str()));
            }
            _ => {
                println!(
                    "Archive downloaded and extracted successfully: {}",
                    query_mitre.csv.clone()
                );
            }
        }

        match injector(
            &env_path.clone().join(query_mitre.csv.clone()),
            &env_path.clone().join(MITRE_DB_NAME),
        ) {
            Err(e) => {
                write_error_message_and_exit(
                    "Error during archive injection",
                    Some(e.to_string().as_str()),
                );
            }
            _ => {
                let _ = std::fs::remove_file(env_path.join(query_mitre.csv.clone()));
                let _ = std::fs::remove_file(env_path.join(query_mitre.zip.clone()));
                println!("Operation completed successfully: {}", query_mitre.csv);
            }
        }
    }

    /// find the cwe by its id.
    ///
    /// # Arguments
    ///
    /// * `cwe_id`- CWE's id
    ///
    /// # Returns
    ///
    /// A `CweRecord` if CWE found or `None`.
    ///
    pub fn find_cwe_by_id(cwe_id: u32) -> Option<CweRecord> {
        let tyr_db = find_tyr_home_path().join(MITRE_DB_NAME);
        let connection = Connection::open(tyr_db).ok()?;
        let mut statement = connection
            .prepare("SELECT * FROM cwe WHERE id= :id_cwe")
            .ok()?;
        let mut rows = statement.query(&[(":id_cwe", &cwe_id)]).ok()?;
        let row = rows.next().ok()?;
        if row.is_some() {
            Some(CweRecord {
                id: row?.get(0).unwrap(),
                name: row?.get(1).unwrap(),
                description: row?.get(2).unwrap(),
                extended: row?.get(3).unwrap(),
                consequences: row?.get(4).unwrap(),
                detections: row?.get(5).unwrap(),
                mitigations: row?.get(6).unwrap(),
                attacks: row?.get(7).unwrap(),
            })
        } else {
            None
        }
    }

    /// find the capec by its id.
    ///
    /// # Arguments
    ///
    /// * `capec_id`- Capec's id
    ///
    /// # Returns
    ///
    /// A `CapecRecord` if CAPEC found or `None`.
    ///
    pub fn find_capec_by_id(capec_id: u32) -> Option<CapecRecord> {
        let tyr_db = find_tyr_home_path().join(MITRE_DB_NAME);
        let connection = Connection::open(tyr_db).ok()?;
        let mut statement = connection
            .prepare("SELECT * FROM capec WHERE id= :id_capec")
            .ok()?;
        let mut rows = statement.query(&[(":id_capec", &capec_id)]).ok()?;
        let row = rows.next().ok()?;
        if row.is_some() {
            Some(CapecRecord {
                id: row?.get(0).unwrap(),
                name: row?.get(1).unwrap(),
                abstraction: row?.get(2).unwrap(),
                status: row?.get(3).unwrap(),
                description: row?.get(4).unwrap(),
                alternates: row?.get(5).unwrap(),
                likelihood: row?.get(6).unwrap(),
                severity: row?.get(7).unwrap(),
                attacks: row?.get(8).unwrap(),
                flow: row?.get(9).unwrap(),
                prerequisites: row?.get(10).unwrap(),
                skills: row?.get(11).unwrap(),
                resources: row?.get(12).unwrap(),
                indicators: row?.get(13).unwrap(),
                consequences: row?.get(14).unwrap(),
                mitigations: row?.get(15).unwrap(),
                instances: row?.get(16).unwrap(),
                weaknesses: row?.get(17).unwrap(),
                taxonomy: row?.get(18).unwrap(),
            })
        } else {
            None
        }
    }

    /// Analyze a string criteria and split each element by separating the words criteria from the operators.
    ///
    /// # Arguments
    ///
    /// * `criteria` - Criteria string for analysis
    ///
    /// # Results
    ///
    /// A couple of values where the first argument is the list of operators and the second the list of words of the criteria
    ///
    pub fn criteria_analysis(criteria: String) -> Option<(Vec<String>, Vec<String>)> {
        if criteria.is_empty() {
            None
        } else {
            let regex = Regex::new(r"\b(?:and|not|or)\b");
            let result = regex
                .clone()
                .unwrap()
                .captures_iter(&criteria)
                .collect::<Vec<_>>();
            let mut operators = Vec::<String>::new();
            for cap in result {
                operators.push(cap.get(0).unwrap().as_str().to_string());
            }

            let mut crit = criteria.clone();
            ["not ", "and ", "or "].iter().for_each(|o| {
                crit = crit.replace(o, "^");
            });

            let mut words = Vec::<String>::new();
            let chars = crit.chars();
            let mut others = Vec::<char>::new();
            let mut keep_space = false;
            chars.for_each(|c| {
                if c != '\'' {
                    if c == '^' {
                        if !others.is_empty() {
                            words.push(others.iter().collect());
                            others.clear();
                        }
                        words.push(String::new());
                    } else if keep_space || c != ' ' {
                        others.push(c);
                    }
                } else {
                    keep_space = !keep_space;
                }
            });

            if !others.is_empty() {
                words.push(others.iter().collect());
            }
            Some((words, operators))
        }
    }

    /// Split the arguments list to `domain` and `criteria`. `domain` is corresponding to
    /// the name of field into the concerning table's database.
    ///
    /// # Arguments
    ///
    ///  * `arguments` - List of arguments that's composing the query
    ///
    /// # Results
    ///
    /// A couple of values with the domain for the first field, and left of criteria for the
    /// second field.
    pub fn find_domain_and_criteria(arguments: &[String]) -> Option<(String, String)> {
        let string_args = arguments.iter().last().unwrap();
        let mut pattern = "=";
        let is_sep = match string_args.find(pattern) {
            Some(s) => Some(s),
            None => {
                pattern = "not";
                string_args.find(pattern)
            }
        };

        if let Some(pos_sep) = is_sep {
            let domain = String::from(string_args[0..pos_sep].trim());
            let criteria = match pattern {
                "not" => format!("not {}", string_args[pos_sep + pattern.len()..].trim()),
                _ => String::from(string_args[pos_sep + pattern.len()..].trim()),
            };

            match domain.is_empty() || criteria.is_empty() {
                true => None,
                false => Some((domain, criteria)),
            }
        } else {
            None
        }
    }

    /// Associate the list of arguments with the sql query in construction
    ///
    /// # Arguments
    ///
    /// * `words` - list of arguments words
    /// * `statement`- statement sql in construction
    ///
    /// # Results
    ///
    /// The statement completed by the criteria binding
    ///
    pub fn bind_criteria<'a>(
        words: &[String],
        statement: Result<Statement<'a>, rusqlite::Error>,
    ) -> Statement<'a> {
        let mut counter = 1;
        let mut statement = statement.unwrap();
        let nb = words.len();
        for word in words.iter().take(nb) {
            if !word.trim().is_empty() {
                statement
                    .raw_bind_parameter(counter, format!("%{}%", word.to_lowercase().trim()))
                    .unwrap_or_else(|_| {
                        panic!(
                            "{}",
                            format!("Can't bind parameter: {}", word.to_lowercase()).trim()
                        )
                    });
                counter += 1;
            }
        }

        statement
    }

    /// Prepare the query model given as parameter with the elements of criteria
    ///
    /// # Arguments
    ///
    /// * `query_template`- Query model used to prepare the final query
    /// * `criteria`- criteria elements composed of the list of words (argument.0) and the list of operators (argument.1)
    ///
    /// # Results
    ///
    /// The final query
    pub fn prepare_query(
        query_template: String,
        words: Vec<String>,
        operators: &[String],
    ) -> FinalQuery {
        let mut query = query_template.clone();
        let domain = query_template.rsplit_once(' ').unwrap().1;

        let mut count_word = 0;
        let mut count_like = 1;
        let mut count_op = 0;

        loop {
            if count_word == words.len() {
                break;
            }

            let word = words[count_word].clone();
            count_word += 1;
            if !word.trim().is_empty() {
                query.push_str(format!(" LIKE ?{}", count_like).as_str());
                count_like += 1;
            } else if count_op < operators.len() {
                let op = operators[count_op].clone();
                count_op += 1;
                if op.as_str() != "not" {
                    query.push_str(format!(" {op} {domain} ").as_str());
                } else {
                    query.push_str(format!(" {} ", op.trim()).as_str());
                }
            }
        }
        (query, words)
    }

    /// Return the path of mitre database
    ///
    /// # Results
    ///
    /// Mitre database path
    pub fn db_mitre_path() -> PathBuf {
        let tyr_home = find_tyr_home_path();
        tyr_home.join(MITRE_DB_NAME)
    }

    /// Record extractor from the search result
    pub type SearchResultExtractor<T> = fn(&mut Rows) -> Vec<T>;

    type FinalQuery = (String, Vec<String>);

    /// Search the list of words into the database.
    ///
    /// <T> is the type of record as the result of the extraction.
    ///
    /// # Arguments
    /// * `query_string` - sql string query
    /// * `words` - list of words for this query
    /// * `extractor`- function use to extract the searching result
    ///
    /// # Result
    /// A list of records according to T type
    ///
    pub fn search<T>(final_query: FinalQuery, extractor: SearchResultExtractor<T>) -> Vec<T> {
        let mitre_db = db_mitre_path();
        let db_connection = Connection::open(mitre_db).ok();
        let connection = db_connection.unwrap();
        let statement = connection.prepare(final_query.0.as_str());
        let mut binding = bind_criteria(&final_query.1, statement);
        let mut rows = binding.raw_query();

        extractor(&mut rows)
    }

    /// Parse the string values to a list of unsigned 32 bits.
    /// The string must be formatted like this: `::<num>::<num>::...::`
    ///
    /// # Argument
    /// * `values` - raw string values to parse to
    ///
    /// # Result
    /// The list of unsigned 32 values.
    pub fn parse_id_to_u32(values: String) -> Vec<u32> {
        let mut id_list: Vec<u32> = values
            .split(FIELD_SEPARATOR_STRING)
            .map(|v| {
                if !v.is_empty() {
                    let chars = v.chars();
                    let length = chars.clone().count();
                    let mut counter: usize = 0;
                    chars.for_each(|c| {
                        if c.is_ascii_digit() {
                            counter += 1;
                        }
                    });

                    if counter == length {
                        v.parse::<u32>().unwrap()
                    } else {
                        0
                    }
                } else {
                    0
                }
            })
            .collect();

        id_list.sort();
        id_list.dedup();
        id_list.retain(|x| *x != 0);
        id_list
    }

    /// Finds the list of columns for the given table name
    ///
    /// # Argument
    ///
    /// * `table_name` - The table's name to explore.
    ///
    /// # Return
    ///
    /// The list of column's names for this table.
    pub fn find_columns(table_name: &str) -> Vec<String> {
        let db = Connection::open(db_mitre_path()).unwrap();
        let sql_select = format!("SELECT * FROM {table_name}");
        let stmt = db.prepare(sql_select.as_str()).unwrap();

        stmt.column_names()
            .iter()
            .map(|x| x.to_string())
            .collect::<Vec<String>>()
    }

    type Searcher = fn(args: &[String]);
    pub fn searching(args: &[String], columns: &[String], searcher: Searcher) {
        let args_id = args.iter().position(|x| x == "search" || x == "s").unwrap();
        let criteria = &args[args_id + 1..];
        if criteria[0].contains(&"all".to_string()) {
            let args_left = criteria[0]["all".len()..].to_string();
            columns.iter().for_each(|x| {
                if *x != "id" {
                    println!("{}", section!(format!("Searching for domain: {x}")));
                    let args = format!("{} {}", x, &args_left);
                    searcher(&[args]);
                }
            });
        } else {
            searcher(args);
        }
    }

    /// Refresh the mitre database. the function takes a string that contains a list of csv files and the associated url.
    /// The method converts this string in an internal vector and create the query to download the CSV file from the
    /// associated url and inject these values into the local mitre database.
    ///
    /// # Argument
    ///
    /// * `csv_file_list` - a string that contains a liste of csv file separated by a comma.
    /// * `base_url` - the associated url that allows to download each csv file for injection into the local database.
    /// * `injector` - the associated injector function that allows to store the downloaded data into the local database.
    ///
    pub fn refresh_mitre_database(
        csv_files_list: &str,
        base_url: &str,
        injector: InjectorFunction,
    ) {
        let csv_files = csv_files_list.split(",").collect::<Vec<&str>>();
        csv_files.iter().for_each(|x| {
            let query_mitre = QueryMitre {
                url: format!("{}/{}.zip", base_url, x.trim()),
                zip: format!("{}.zip", x.trim()),
                csv: x.trim().to_string(),
            };
            refresh_csv_mitre_into_database(query_mitre, injector);
        });
    }
}

/// Tools for download support
pub mod download {
    use std::error::Error;
    use std::fs::File;
    use std::io::{Read, Write};
    use std::path::Path;
    use ureq::http::Response;
    use ureq::Body;
    use zip::ZipArchive;

    /// The `QueryMitre` struct represents the necessary information to query
    /// and process data from the MITRE organization.
    ///
    /// # Fields
    ///
    /// * `url` - A `String` that holds the URL for querying MITRE data.
    /// * `zip` - A `String` that specifies the path or name of the ZIP file
    ///   to be used.
    /// * `csv` - A `String` that denotes the path or name of the CSV file
    ///   to be processed.
    #[derive(Clone)]
    pub struct QueryMitre {
        /// The url to use for the mitre query
        pub url: String,

        /// The zip file for mitre download archive
        pub zip: String,

        /// The CSV file for extracting from the zip file
        pub csv: String,
    }

    /// Downloads a MITRE archive from the given query URL, saves it as a ZIP file,
    /// and extracts the specified CSV file from the ZIP archive.
    ///
    /// This function performs the following steps:
    /// 1. Fetches the CSV content from the `query` URL.
    /// 2. Saves the downloaded content as a ZIP file specified by `query.zip`.
    /// 3. Extracts the required CSV file from the ZIP archive specified by `query.csv`.
    ///
    /// # Arguments
    ///
    /// * `query` - An instance of `QueryMitre` struct containing the required URL,
    ///   ZIP file path, and CSV file path.
    /// * `env_path`- The tyr home path.
    ///
    /// # Returns
    ///
    /// This function returns a `Result` type:
    /// * `Ok(())` - If the process was successful.
    /// * `Err(Box<dyn std::error::Error>)` - If any error occurred during downloading, saving, or extracting.
    ///
    /// # Errors
    ///
    /// The function will return an error in cases such as:
    /// * Network issues while downloading the CSV file.
    /// * Problems with creating or writing to the ZIP file.
    /// * Issues while extracting the CSV file from the ZIP archive.
    ///
    /// # Example
    ///
    /// ```rust
    /// use fenir::download::{download_mitre_archive, QueryMitre};
    /// use fenir::os::find_tyr_home_path;
    /// let query = QueryMitre {
    ///     url: String::from("https://example.com/data.zip"),
    ///     zip: String::from("downloaded.zip"),
    ///     csv: String::from("data.csv"),
    /// };
    /// if let Err(e) = download_mitre_archive(query, &find_tyr_home_path()) {
    ///     eprintln!("Error: {}", e);
    /// }
    /// ```
    pub fn download_mitre_archive(
        query: QueryMitre,
        env_path: &Path,
    ) -> Result<(), Box<dyn Error>> {
        let response = fetch_csv(query.url.as_str());

        save_downloaded_archive(response, query.zip.as_str(), env_path)?;

        extract_zip_file(query.zip.as_str(), query.csv.as_str(), env_path)?;

        Ok(())
    }

    /// Fetches a CSV file from a given URL.
    ///
    /// This function sends a GET request to the specified URL and attempts to fetch the CSV file.
    /// It checks if the request was successful by verifying the HTTP status code.
    ///
    /// # Arguments
    ///
    /// * `url` - A string slice that holds the URL of the CSV file to be fetched.
    ///
    /// # Returns
    ///
    /// This function returns a `Result` containing a `Response` object on success, or a `Box<dyn Error>` on failure.
    ///
    /// # Errors
    ///
    /// This function will return an error if:
    ///
    /// * The request fails (e.g., due to network issues).
    /// * The server responds with a non-200 HTTP status code.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use fenir::download::fetch_csv;
    /// let url = "https://example.com/data.csv";
    /// let response = fetch_csv(url);
    /// // Process the response...
    /// ```
    ///
    /// # Dependencies
    ///
    /// This function relies on the `ureq` crate for HTTP requests.
    pub fn fetch_csv(url: &str) -> Result<Response<Body>, Box<dyn Error>> {
        // Send GET request to the URL
        let response = ureq::get(url).call()?;

        // Ensure the request was successful
        if response.status() != 200 {
            return Err(format!("Failed to download archive: HTTP {}", response.status()).into());
        }

        Ok(response)
    }

    /// Saves a downloaded archive to a specified file.
    ///
    /// # Arguments
    ///
    /// * `response` - A `Result` wrapping the HTTP `Response` object if the request was successful,
    ///   or an error if the request failed.
    /// * `zip_file_name` - A string slice that holds the name of the file where the downloaded archive will be saved.
    /// * `env_path`- The tyr home path.
    ///
    /// # Returns
    ///
    /// This function returns a `Result`:
    ///
    /// * `Ok(())` if the file was successfully saved.
    /// * `Err(Box<dyn Error>)` if any error occurred during the process.
    /// * `env_path`- The tyr home path.
    ///
    /// # Errors
    ///
    /// This function will return an error if:
    ///
    /// * The file cannot be created.
    /// * Reading the response content fails.
    /// * Writing the content to the file fails.
    ///
    pub fn save_downloaded_archive(
        response: Result<Response<Body>, Box<dyn Error>>,
        zip_file_name: &str,
        env_path: &Path,
    ) -> Result<(), Box<dyn Error>> {
        let mut out = File::create(env_path.join(zip_file_name))?;
        let mut content = vec![];
        response?
            .into_body()
            .as_reader()
            .read_to_end(&mut content)?;
        out.write_all(&content)?;
        Ok(())
    }

    /// Extracts a specified CSV file from a ZIP file and saves it to the filesystem.
    ///
    /// # Arguments
    ///
    /// * `from_zip_file_name` - A string slice that holds the name of the ZIP file to be read.
    /// * `to_csv_file_name` - A string slice that holds the name of the CSV file inside the ZIP to be extracted and saved.
    /// * `destination_path`- The path where to store the extracted zip.
    ///
    /// # Returns
    ///
    /// This function returns a `Result` type. On success, it returns an empty tuple `()`. On failure, it returns an error wrapped in a `Box<dyn Error>`.
    ///
    /// # Example
    ///
    /// ```
    /// use fenir::download::extract_zip_file;
    /// use fenir::os::find_tyr_home_path;
    /// let result = extract_zip_file("data.zip", "data.csv", &find_tyr_home_path());
    /// if let Err(e) = result {
    ///     println!("Error extracting file: {}", e);
    /// }
    /// ```
    ///
    /// # Errors
    ///
    /// The function will return an error in the following cases:
    /// * If the ZIP file cannot be opened.
    /// * If the specified CSV file does not exist in the ZIP.
    /// * If the CSV file cannot be read or written to the filesystem.
    pub fn extract_zip_file(
        from_zip_file_name: &str,
        to_csv_file_name: &str,
        destination_path: &Path,
    ) -> Result<(), Box<dyn Error>> {
        // Extract the CSV file from the ZIP
        let file = File::open(destination_path.join(from_zip_file_name))?;
        let mut archive = ZipArchive::new(file)?;
        let mut csv_file = archive.by_name(to_csv_file_name)?;
        let mut csv_content = String::new();
        csv_file.read_to_string(&mut csv_content)?;
        // Save the CSV content to a file
        let mut extracted_csv = File::create(destination_path.join(to_csv_file_name))?;
        extracted_csv.write_all(csv_content.as_bytes())?;
        Ok(())
    }
}

pub mod query {
    use crate::cpe::NVD_CPE_LINK;
    use crate::facilities::get_current_date;
    use crate::query::QueryType::{ByCpeSearchWords, ByCveNew, ByCveUpdated};
    use crate::{NVD_CVE_LINK, URL_SPACE};

    /// `QueryType` is an enumeration representing the types of queries that can be made
    /// for Common Vulnerabilities and Exposures (CVE). It derives `Eq`, `PartialEq`, and `Clone`
    /// traits for equality comparisons and cloning capabilities.
    #[derive(Eq, PartialEq, Clone)]
    pub enum QueryType {
        /// Query by a specific CVE identifier.
        ByCve,

        /// Query by a CVE for a CPE name (string description)
        ByCveCpeName,

        /// Query for a known exploited CVE list
        ByCveExploited,

        /// Query to retrieve new CVEs.
        ByCveNew,

        /// Query by search strings
        ByCveSearchString,

        /// Query by search strings with strict mode
        ByCveSearchStringStrict,

        /// Query to retrieve updated CVEs.
        ByCveUpdated,

        /// Query by a Common Platform Enumeration (CPE) identifier.
        ByCpe,

        /// Query to perform CPE matching.
        ByCpeMatch,

        /// Query to perform CPE searching by considering the word arguments as a single criteria.
        ByCpeSearchStrict,

        /// Query to perform CPE searching according to a list of words.
        ByCpeSearchWords,

        /// Query about vulnerable CPE
        ByCpeVulnerable,

        /// Query by a Common Weakness Enumeration (CWE) identifier.
        ByCwe,

        /// Cve http link test
        LinkTest,
    }

    /// Generates a query string for CVE (Common Vulnerabilities and Exposures) based on the provided query date type
    /// and date values.
    ///
    /// # Arguments
    ///
    /// * `query_date` - An instance of `CveQueryType` enum specifying the type of date query (new CVEs or updated CVEs).
    /// * `value` - A string containing date values separated by a semicolon (`;`). The values represent start and end dates
    ///   respectively. If only one date is provided, it will be considered as both the start and end date.
    ///
    /// # Returns
    ///
    /// A `String` containing the constructed query with appropriate start and end dates.
    ///
    /// - For `CveNew`, it constructs `pubStartDate` and `pubEndDate` parameters.
    /// - For `CveUpdated`, it constructs `lastModStartDate` and `lastModEndDate` parameters.
    ///
    /// If a date is empty, the current date is used as a fallback.
    ///
    /// # Examples
    ///
    /// ```
    /// use fenir::query::{QueryType, create_query_dates};
    /// use fenir::facilities::get_current_date;
    /// let result = create_query_dates(QueryType::ByCveNew, "2023-01-01;2023-12-31");
    /// assert_eq!(result, "pubStartDate=2023-01-01T00:00:00.000&pubEndDate=2023-12-31T00:00:00.000");
    ///
    /// let result = create_query_dates(QueryType::ByCveUpdated, "2023-01-01;");
    /// let current_date = get_current_date();
    /// assert_eq!(result, format!("lastModStartDate=2023-01-01T00:00:00.000&lastModEndDate={}T00:00:00.000", current_date));
    /// ```
    pub fn create_query_dates(query_date: QueryType, value: &str) -> String {
        let dates: Vec<&str> = value.split(";").collect();
        match dates.len() {
            2 => match query_date {
                ByCveNew => {
                    if !dates[0].is_empty() && !dates[1].is_empty() {
                        format!(
                            "pubStartDate={}T00:00:00.000&pubEndDate={}T00:00:00.000",
                            dates[0], dates[1]
                        )
                    } else if dates[0].is_empty() {
                        format!(
                            "pubStartDate={}T00:00:00.000&pubEndDate={}T00:00:00.000",
                            get_current_date(),
                            dates[1]
                        )
                    } else {
                        format!(
                            "pubStartDate={}T00:00:00.000&pubEndDate={}T00:00:00.000",
                            dates[0],
                            get_current_date()
                        )
                    }
                }

                ByCveUpdated => {
                    if !dates[0].is_empty() && !dates[1].is_empty() {
                        format!(
                            "lastModStartDate={}T00:00:00.000&lastModEndDate={}T00:00:00.000",
                            dates[0], dates[1]
                        )
                    } else if dates[0].is_empty() {
                        format!(
                            "lastModStartDate={}T00:00:00.000&lastModEndDate={}T00:00:00.000",
                            get_current_date(),
                            dates[1]
                        )
                    } else {
                        format!(
                            "lastModStartDate={}T00:00:00.000&lastModEndDate={}T00:00:00.000",
                            dates[0],
                            get_current_date()
                        )
                    }
                }
                _ => String::new(),
            },

            1 => match query_date {
                ByCveNew => {
                    if !dates[0].is_empty() {
                        format!(
                            "pubStartDate={}T00:00:00.000&pubEndDate={}T00:00:00.000",
                            dates[0],
                            get_current_date()
                        )
                    } else {
                        format!(
                            "pubStartDate={}T00:00:00.000&pubEndDate={}T00:00:00.000",
                            get_current_date(),
                            get_current_date()
                        )
                    }
                }

                ByCveUpdated => {
                    if !dates[0].is_empty() {
                        format!(
                            "lastModStartDate={}T00:00:00.000&lastModEndDate={}T00:00:00.000",
                            dates[0],
                            get_current_date()
                        )
                    } else {
                        format!(
                            "lastModStartDate={}T00:00:00.000&lastModEndDate={}T00:00:00.000",
                            get_current_date(),
                            get_current_date()
                        )
                    }
                }

                _ => String::new(),
            },
            _ => String::new(),
        }
    }

    /// Builds a query URL based on the type of query and a URL argument. Thanks to the `query_type`
    /// argument, the details for full URL constructing is taking in charge automatically.
    ///
    /// # Arguments
    ///
    /// * `query_type` - A variant of the `CveQueryType` enum specifying the type of CVE query.
    /// * `url_argument` - A string slice that holds the argument to be passed in the URL query.
    ///
    /// # Returns
    ///
    /// If successful, returns `Ok` containing the constructed query URL as a `String`.
    /// Otherwise, returns an `Err` with a static string slice indicating the error.
    ///
    /// # Examples
    /// ```
    /// use fenir::NVD_CVE_LINK;
    /// use fenir::query::{QueryType, build_query};
    /// let query_url = build_query(QueryType::ByCve, "CVE-2021-3456").unwrap();
    /// assert_eq!(query_url, format!("{}?cveId=CVE-2021-3456", NVD_CVE_LINK));
    /// ```
    pub fn build_query(query_type: QueryType, url_argument: &str) -> Result<String, &'static str> {
        match query_type {
            QueryType::ByCve => Ok(format!(
                "{NVD_CVE_LINK}?cveId={}",
                url_argument.to_uppercase()
            )),

            QueryType::ByCveCpeName => Ok(format!(
                "{NVD_CVE_LINK}?virtualMatchString={}",
                url_argument
            )),

            QueryType::ByCveExploited => Ok(format!("{NVD_CVE_LINK}?hasKev")),

            ByCveNew => Ok(format!(
                "{NVD_CVE_LINK}?{}",
                create_query_dates(ByCveNew, url_argument)
            )),

            QueryType::ByCwe => Ok(format!(
                "{NVD_CVE_LINK}?cweId={}",
                url_argument.to_uppercase()
            )),

            QueryType::ByCpeVulnerable => {
                let query = build_query(QueryType::ByCveCpeName, url_argument);
                let query_str = query?;
                Ok(format!("{}&isVulnerable", query_str))
            }

            QueryType::ByCveSearchString => {
                Ok(format!("{NVD_CVE_LINK}?keywordSearch={}", url_argument))
            }

            QueryType::ByCveSearchStringStrict => {
                let query = build_query(QueryType::ByCveSearchString, url_argument);
                let query_str = query?;
                Ok(format!("{}&keywordExactMatch", query_str))
            }

            ByCveUpdated => Ok(format!(
                "{NVD_CVE_LINK}?{}",
                create_query_dates(ByCveUpdated, url_argument)
            )),

            QueryType::ByCpe => Ok(format!("{NVD_CPE_LINK}?cpeName={}", url_argument)),

            QueryType::ByCpeMatch => Ok(format!(
                "{NVD_CPE_LINK}?cpeMatchString={}",
                url_argument.to_lowercase()
            )),

            ByCpeSearchWords => Ok(format!(
                "{NVD_CPE_LINK}?keywordSearch={}",
                url_argument.replace(' ', URL_SPACE)
            )),

            QueryType::ByCpeSearchStrict => {
                let first_part = build_query(ByCpeSearchWords, url_argument)?;
                Ok(format!("{first_part}&keywordExactMatch"))
            }

            QueryType::LinkTest => Ok(format!("{}?resultsPerPage=1", url_argument)),
        }
    }
}

pub mod network {
    use crate::package::errors::write_error_message_and_exit;
    use crate::query::build_query;
    use crate::query::QueryType::LinkTest;
    use crate::NVD_KEY_NAME;
    use serde_json::Value;
    use std::env;
    use std::ffi::OsString;
    use termint::enums::Color;
    use termint::widgets::ToSpan;
    use ureq::Error;

    /// Parses the given response from a `ureq` request.
    ///
    /// This function takes a `Result` containing either a `serde_json::Value` or a `ureq::Error`.
    /// If the result is an error, it returns an empty `Value`.
    /// If the result is a valid response, it returns a JSON `Value`.
    ///
    /// # Arguments
    ///
    /// * `response` - A `Result` containing either a `serde_json::Value` or `ureq::Error`.
    ///
    /// # Returns
    ///
    /// * A `serde_json::Value` parsed from the response, or an empty `Value::Null` if an error occurred.
    ///
    pub fn parse_response(response: Result<Value, Box<Error>>) -> Value {
        response.unwrap_or(Value::Null)
    }

    /// Fetches a response from the given query URL. If the variable environment contains the
    /// `NVD_API_KEY`, this value will be used as NVD api key.
    ///
    /// This function uses the `ureq` crate to send an HTTP GET request to the specified URL.
    /// It returns the response if the request is successful, otherwise, it returns an error.
    /// The HTTP request is force to HTTPS only for secure reason.
    ///
    /// # Arguments
    ///
    /// * `query` - A string slice that holds the URL to which the GET request should be sent.
    ///
    /// # Returns
    ///
    /// This function returns a `Result` which is:
    /// - `Ok(serde_json::Value)` if the request is successful.
    /// - `Err(ureq::Error)` if there is an error during the request.
    ///
    /// # Examples
    ///
    /// ```
    /// use fenir::network::fetch_response;
    /// let url = "https://api.example.com/data";
    /// match fetch_response(url) {
    ///     Ok(response) => println!("Success: {:?}", response),
    ///     Err(error) => eprintln!("Error: {:?}", error),
    /// }
    /// ```
    ///
    /// # Errors
    ///
    /// This function will return an `Err` variant if the GET request fails for any reason,
    /// such as network issues or invalid URLs.
    ///
    pub fn fetch_response(query: &str) -> Result<Value, Box<Error>> {
        let apikey = check_nvd_api_key();
        if apikey.is_some() {
            let result: Value = ureq::get(query)
                .header("apiKey", apikey.unwrap().to_str().unwrap())
                .call()?
                .body_mut()
                .with_config()
                .read_json()?;
            Ok(result)
        } else {
            let result = ureq::get(query)
                .call()?
                .body_mut()
                .with_config()
                .read_json()?;

            Ok(result)
        }
    }

    /// Check if the nvd api key is exiting
    ///
    /// # Returns
    ///
    /// - The result of checking. The presence of the NVD_API_KEY is optional
    pub fn check_nvd_api_key() -> Option<OsString> {
        env::var_os(NVD_KEY_NAME)
    }

    /// Warning missing API key message
    pub fn check_nvd_api_key_warning() {
        eprintln!("{}", "WARNING: NVD api key not found. For a better performance, in your environment set NVD_API_KEY=<your_api_key_value>.".to_string().fg(Color::DarkYellow));
    }

    /// Checks the validity of a connection by sending a GET request to the provided URL.
    ///
    /// # Arguments
    ///
    /// * `query` - A string slice that represents the URL to which the GET request is made.
    ///
    /// # Returns
    ///
    /// * `true` if the response from the GET request is successful (status code 200).
    /// * `false` if there is any error in making the request or the response indicates a failure.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use fenir::NVD_CVE_LINK;
    /// use fenir::network::validate_connection;
    /// let is_valid = validate_connection(NVD_CVE_LINK);
    /// assert!(is_valid);
    /// ```
    pub fn validate_connection(query: &str) -> bool {
        // Simple URL validation
        if !query.starts_with("http://") && !query.starts_with("https://") {
            eprintln!("Invalid URL: {}", query);
            return false;
        }

        let agent = ureq::Agent::new_with_defaults();

        let query_test = build_query(LinkTest, query);
        let query_info_for_error = query_test.clone().unwrap();

        match agent.get(query_test.unwrap().as_str()).call() {
            Ok(res) => {
                if res.status() == 200 {
                    true
                } else {
                    eprintln!(
                        "Request failed with status: {} ({})",
                        res.status(),
                        query_info_for_error
                    );
                    false
                }
            }
            Err(Error::StatusCode(res)) => {
                eprintln!(
                    "Request failed with response: {:?} ({})",
                    res, query_info_for_error
                );
                false
            }
            Err(Error::Http(transport)) => {
                eprintln!(
                    "Request http error: {} ({})",
                    transport, query_info_for_error
                );
                false
            }
            _ => false,
        }
    }

    /// Runs a test to check if the connection to the NVD CVE API is valid.
    ///
    /// This function tests the connectivity to the NVD CVE API by making a GET request to `NVD_CVE_LINK`
    /// and `NVD_CPE_LINK` and evaluating if the response is successful. Depending on the result, it outputs
    /// a message to the standard output or standard error and exits the program with a corresponding exit code.
    ///
    /// If the connection is valid, it prints "valid".
    /// If the connection is invalid, it prints "invalid" to the standard error and exits with code `1`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use fenir::NVD_CVE_LINK;
    /// use fenir::network::check_api;
    /// check_api(NVD_CVE_LINK);
    /// ```
    pub fn check_api(link: &str) {
        print!("Test: {link}... ");
        if !validate_connection(link) {
            write_error_message_and_exit("Invalid", None);
        } else {
            println!("valid");
        }
    }
}

pub mod cwe {
    use crate::database::{check_database, find_cwe_by_id, parse_id_to_u32};
    use crate::facilities::{Cleaning, Uppercase};
    use crate::os::find_tyr_home_path;
    use crate::{header, section};
    use serde_json::Value;
    use std::fmt::{Display, Formatter};
    use termint::enums::{Color, Modifier};
    use termint::widgets::ToSpan;

    /// Represents a CweRecord that's containing  all content of an existent CWE.
    #[derive(Clone, Eq, PartialEq, Debug)]
    pub struct CweRecord {
        /// the CWE id (without CWE string prefix)
        pub id: u32,

        /// the name of the CWE
        pub name: String,

        /// The primary description of the CWE
        pub description: String,

        /// The full description of the CWE
        pub extended: String,

        /// The associated capec
        pub attacks: String,

        /// The consequences of the CWE
        pub consequences: String,

        /// The detections feature for this CWE
        pub detections: String,

        /// The mitigations for this CWE
        pub mitigations: String,
    }

    impl Display for CweRecord {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            write!(
                f,
                "{}\n{} {}",
                header!(format!("CWE-{}", self.id)),
                format!("{:>4} {}", "->", "Name:").fg(Color::Rgb(178, 189, 29)),
                self.name
            )
        }
    }

    pub fn show_weaknesses(section: &str, raw_weaknesses: String) {
        let cwe_list = parse_id_to_u32(raw_weaknesses);

        println!("\n{}", section!(section, Color::Red));
        for cwe in cwe_list {
            if check_database(&find_tyr_home_path(), "cwe") {
                let result = find_cwe_by_id(cwe);
                if let Some(record) = result {
                    println!("{:>4} CWE-{} - {}", "-", cwe, record.name);
                } else {
                    println!("{:>4} CWE-{} - ???", "-", cwe);
                }
            } else {
                println!("{:>4} CWE-{}", "-", cwe);
            }
        }
    }

    /// Flat the cwe values to a string value
    ///
    /// # Arguments
    ///
    /// * `values` - List of cwe values
    ///
    /// # Returns
    ///
    /// A string with all cwe id separate by a ','.
    pub fn join_cwe_list(values: &[Value]) -> String {
        let mut result = Vec::new();
        values
            .iter()
            .for_each(|v| result.push(String::cleaning(v["value"].to_string())));

        result.join(", ")
    }
}

pub mod cpe {
    use crate::package::errors::write_error_message_and_exit;
    use crate::NOT_ENOUGH_ELEMENTS_ERROR;
    use std::fmt::{Display, Formatter};

    /// NVD link Rest API for CPE request
    pub const NVD_CPE_LINK: &str = "https://services.nvd.nist.gov/rest/json/cpes/2.0";

    /// NVD link Rest API for CPE/CVE matching
    pub const NVD_CPE_MATCH_LINK: &str = "https://services.nvd.nist.gov/rest/json/cpematch/2.0";

    /// Represents a Common Platform Enumeration (CPE) identifier.
    ///
    /// CPE is a standardized method used to describe and identify classes of applications,
    /// operating systems, and hardware devices present within an enterprise's computing assets.
    ///
    /// This struct includes various fields that together form a complete CPE identifier.
    ///
    /// # Fields
    ///
    /// * `cpe_type` - The type of the CPE identifier.
    /// * `vendor` - The vendor of the product.
    /// * `product` - The product name.
    /// * `version` - The version of the product.
    /// * `update` - The update/version details of the product.
    /// * `edition` - A granularity describing the build of the system/package/component, beyond version.
    #[derive(Eq, PartialEq, Clone, Debug)]
    pub struct Cpe {
        pub cpe_type: CpeType,
        pub vendor: String,
        pub product: String,
        pub version: String,
        pub update: String,
        pub edition: String,
        pub language: String,
        pub sw_edition: String,
        pub target_sw: String,
        pub target_hw: String,
        pub other: String,
    }

    impl Cpe {
        /// Creates a new instance of the `cpe` struct with the provided `CpeType`.
        ///
        /// Fields such as `vendor`, `product`, `version`, `update`, and `edition` are initialized with default values (`"*"`).
        ///
        /// # Arguments
        ///
        /// * `cpe_type` - The type of the Common Platform Enumeration (CPE). This can be either `CpeType::Application` or `CpeType::OperatingSystem`.
        ///
        /// # Returns
        ///
        /// A new `cpe` instance with the specified CPE type and default field values.
        pub fn new(cpe_type: CpeType) -> Self {
            Cpe {
                cpe_type,
                vendor: "*".to_string(),
                product: "*".to_string(),
                version: "*".to_string(),
                update: "*".to_string(),
                edition: "*".to_string(),
                language: "*".to_string(),
                sw_edition: "*".to_string(),
                target_hw: "*".to_string(),
                target_sw: "*".to_string(),
                other: "*".to_string(),
            }
        }

        /// Returns a string representation of the instance. This is a convenience method for
        /// formats the instance into a string using the `Display` trait implementation
        /// of the type of `self`.
        ///
        /// # Returns
        ///
        /// A `String` containing the formatted representation of the instance.
        ///
        /// # Notes:
        ///
        /// - the output format is : cpe:2.3:&lt;part&gt;:&lt;vendor&gt;:&lt;product&gt;:&lt;version&gt;:&lt;update&gt;:&lt;edition&gt;
        /// - the output format is limited with elements only used with these tools:
        ///     - &lt;part&gt;: support only o (for operating system, a for application),
        ///     - the &lt;sw_edition&gt;:&lt;target_sw&gt;:&lt;target_hw&gt;:&lt;other&gt; of the CPE norm is not supported.
        ///
        pub fn name(&self) -> String {
            format!("{}", self)
        }

        /// Sets the vendor field for this `cpe` instance.
        ///
        /// This method updates the vendor field with the provided name, converting it to lowercase.
        ///
        /// # Arguments
        ///
        /// * `name` - A string slice that holds the name of the vendor.
        ///
        /// # Example
        ///
        /// ```
        /// use fenir::cpe::{Cpe, CpeType};
        /// let mut cpe = Cpe::new(CpeType::Application);
        ///
        /// cpe.vendor("NewVendor");
        /// assert_eq!(cpe.vendor, "newvendor");
        /// ```
        pub fn vendor(&mut self, name: &str) {
            self.vendor = String::from(name).to_lowercase();
        }

        /// Sets the product name for the CPE identifier.
        ///
        /// This method takes a string slice representing the product name,
        /// converts it to lowercase, and assigns it to the `product` field of the `cpe` struct.
        ///
        /// # Parameters
        ///
        /// * `name` - A string slice that holds the name of the product. This name will be converted to lowercase.
        ///
        /// # Example
        ///
        /// ```
        /// use fenir::cpe::{Cpe, CpeType};
        /// let mut cpe = Cpe::new(CpeType::Application);
        /// cpe.product("CoolProduct");
        /// assert_eq!(cpe.product, "coolproduct");
        /// ```
        pub fn product(&mut self, name: &str) {
            self.product = String::from(name).to_lowercase();
        }

        /// Sets the version for the `cpe` instance.
        ///
        /// This method updates the `version` field of the `cpe` struct, converting the provided
        /// version string to lowercase to ensure consistency.
        ///
        /// # Parameters
        ///
        /// * `value` - A reference to a string slice representing the new version to be set.
        ///
        /// # Example
        ///
        /// ```
        /// use fenir::cpe::{Cpe, CpeType};
        /// let mut cpe = Cpe::new(CpeType::Application);
        ///
        /// cpe.version("2.0.0");
        /// assert_eq!(cpe.version, "2.0.0");
        /// ```
        ///
        /// # Note
        ///
        /// The input version string will be converted to lowercase.
        ///
        /// # Errors
        ///
        /// This method does not return an error but mutates the `cpe` instance in place.
        pub fn version(&mut self, value: &str) {
            self.version = String::from(value).to_lowercase();
        }

        /// Updates the `update` field of the `cpe` struct with the given value, converting it to lowercase.
        ///
        /// # Arguments
        ///
        /// * `value` - A string slice representing the new update/version details of the product.
        ///
        /// # Examples
        ///
        /// ```
        /// use fenir::cpe::{Cpe, CpeType};
        /// let mut cpe = Cpe::new(CpeType::Application);
        /// cpe.update("Update2");
        /// assert_eq!(cpe.update, "update2");
        /// ```
        pub fn update(&mut self, value: &str) {
            self.update = String::from(value).to_lowercase();
        }

        /// Sets the edition field of the `cpe` struct.
        ///
        /// This method takes a string slice, converts it to a `String`, transforms it to lowercase,
        /// and assigns it to the `edition` field.
        ///
        /// # Arguments
        ///
        /// * `value` - A string slice that holds the new value for the `edition` field.
        ///
        /// # Example
        ///
        /// ```rust
        /// use fenir::cpe::{Cpe, CpeType};
        /// let mut cpe = Cpe::new(CpeType::Application);
        /// cpe.edition("Premium");
        /// assert_eq!(cpe.edition, "premium");
        /// ```
        pub fn edition(&mut self, value: &str) {
            self.edition = String::from(value).to_lowercase();
        }

        pub fn language(&mut self, value: &str) {
            self.language = String::from(value).to_lowercase();
        }

        pub fn sw_edition(&mut self, value: &str) {
            self.sw_edition = String::from(value).to_lowercase();
        }

        pub fn target_sw(&mut self, value: &str) {
            self.target_sw = String::from(value).to_lowercase();
        }

        pub fn target_hw(&mut self, value: &str) {
            self.target_hw = String::from(value).to_lowercase();
        }

        pub fn other(&mut self, value: &str) {
            self.other = String::from(value).to_lowercase();
        }

        /// Parses a CPE (Common Platform Enumeration) from a given string. Support only cpe version 2.3.
        ///
        /// # Arguments
        /// * `cpe_string` - A string in the CPE format to be parsed
        ///
        /// # Returns
        /// * `cpe` - A struct containing the parsed components of the CPE string
        ///
        /// # Notes:
        ///
        /// - the output format is : cpe:2.3:&lt;part&gt;:&lt;vendor&gt;:&lt;product&gt;:&lt;version&gt;:&lt;update&gt;:&lt;edition&gt;
        /// - the output format is limited with elements only used with these tools:
        ///     - &lt;part&gt;: support only o (for operating system, a for application),
        ///     - the &lt;sw_edition&gt;:&lt;target_sw&gt;:&lt;target_hw&gt;:&lt;other&gt; of the CPE norm is not supported.
        ///
        pub fn parse_cpe_from_string(cpe_string: &str) -> Cpe {
            if !cpe_string.contains(CPE_HEADER) {
                write_error_message_and_exit("CPE string header not found: cpe:2.3", None);
            }

            let elements = extract_elements(cpe_string);
            let mut cpe = match elements[2].as_str() {
                "o" => Cpe::new(CpeType::OperatingSystem),
                "a" => Cpe::new(CpeType::Application),
                "h" => Cpe::new(CpeType::Hardware),
                _ => Cpe::new(CpeType::All),
            };

            cpe.vendor(&elements[3]);
            cpe.product(&elements[4]);
            cpe.version(&elements[5]);
            cpe.update(&elements[6]);
            cpe.edition(&elements[7]);
            cpe.language(&elements[8]);
            cpe.sw_edition(&elements[9]);
            cpe.target_sw(&elements[10]);
            cpe.target_hw(&elements[11]);
            cpe.other(&elements[12]);
            cpe
        }

        /// Describes the CPE (Common Platform Enumeration) attributes.
        ///
        /// This method prints out a human-readable description of the CPE,
        /// detailing its type, vendor, product, version, edition, and update.
        ///
        /// The type is either "soft" for applications or "os" for operating systems,
        /// inferred from the `cpe_type` field.
        ///
        /// # Examples
        ///
        /// ```rust
        /// use fenir::cpe::{Cpe, CpeType};
        /// let cpe = Cpe {
        ///     cpe_type: CpeType::Application,
        ///     vendor: String::from("exampleVendor"),
        ///     product: String::from("exampleProduct"),
        ///     version: String::from("1.0"),
        ///     edition: String::from("Standard"),
        ///     update: String::from("R1"),
        ///     language: String::from("fr"),
        ///     sw_edition: String::from("Ed1"),
        ///     target_sw: String::from("framework"),
        ///     target_hw: String::from("pc"),
        ///     other: String::from("some_value"),
        /// };
        /// cpe.describe();
        /// ```
        /// Will produce the output:
        /// ```shell
        ///   type: soft
        ///   vendor: exampleVendor
        ///   product: exampleProduct
        ///   version: 1.0
        ///   edition: Standard
        ///   update: R1
        /// ```
        pub fn describe(&self) {
            Cpe::cpe_describe_formatted("id", &self.name());
            Cpe::cpe_describe_formatted("type", self.cpe_type());
            Cpe::cpe_describe_formatted("vendor", &self.vendor);
            Cpe::cpe_describe_formatted("product", &self.product);
            Cpe::cpe_describe_formatted("version", &self.version);
            Cpe::cpe_describe_formatted("edition", &self.edition);
            Cpe::cpe_describe_formatted("update", &self.update);
            Cpe::cpe_describe_formatted("language", &self.language);
            Cpe::cpe_describe_formatted("sw edition", &self.sw_edition);
            Cpe::cpe_describe_formatted("target sw", &self.target_sw);
            Cpe::cpe_describe_formatted("target hw", &self.target_hw);
            Cpe::cpe_describe_formatted("other", &self.other);
        }

        fn cpe_describe_formatted(label: &str, value: &str) {
            println!("{:>3} {}: {}", ">", label, value);
        }

        /// Returns the CPE type description
        ///
        /// # Returns
        ///
        ///  * "soft" - if type is application (a)
        ///  * "hardware" -  if type is hardware (h)
        ///  * "os" - if type is operating system (o)
        ///  * "all" - if type is all (*)
        pub fn cpe_type(&self) -> &str {
            match self.cpe_type {
                CpeType::Application => "soft",
                CpeType::OperatingSystem => "os",
                CpeType::Hardware => "hard",
                CpeType::All => "all",
            }
        }
    }

    /// Operating system type
    pub const CPE_OS_LABEL: &str = "os";
    // Hardware type
    pub const CPE_HARDWARE_LABEL: &str = "h";
    /// Application/software type
    pub const CPE_SOFT_LABEL: &str = "soft";
    /// All type
    pub const CPE_ALL_LABEL: &str = "*";

    /// Creates a `cpe` instance based on the provided application type.
    ///
    /// This function checks if the `apps_type` matches the constant value `OS_TYPE`.
    /// If it does, it creates a `cpe` instance of type `OperatingSystem`, otherwise it creates a
    /// `cpe` instance of type `Application`.
    ///
    /// # Arguments
    ///
    /// * `apps_type` - A string slice representing the type of the application. It can be either
    ///   `"os"` for an operating system or any other value for an application.
    ///
    /// # Returns
    ///
    /// A `cpe` instance with the appropriate `CpeType`.
    ///
    /// # Examples
    ///
    /// ```
    /// use fenir::cpe::{create_cpe, Cpe, CpeType};
    /// let cpe_os = create_cpe("os");
    /// assert_eq!(cpe_os.cpe_type, CpeType::OperatingSystem);
    ///
    /// let cpe_app = create_cpe("soft");
    /// assert_eq!(cpe_app.cpe_type, CpeType::Application);
    /// ```
    pub fn create_cpe(apps_type: &str) -> Cpe {
        match apps_type {
            CPE_OS_LABEL => Cpe::new(CpeType::OperatingSystem),
            CPE_SOFT_LABEL => Cpe::new(CpeType::Application),
            CPE_HARDWARE_LABEL => Cpe::new(CpeType::Hardware),
            _ => Cpe::new(CpeType::All),
        }
    }

    /// Returns a string representation of the instance.
    ///
    /// # Returns
    ///
    /// A `String` containing the formatted representation of the instance.
    ///
    /// # Notes:
    ///
    /// - the output format is : cpe:2.3:&lt;part&gt;:&lt;vendor&gt;:&lt;product&gt;:&lt;version&gt;:&lt;update&gt;:&lt;edition&gt;
    /// - the output format is limited with elements only used with these tools:
    ///     - &lt;part&gt;: support only o (for operating system, a for application),
    ///     - the &lt;sw_edition&gt;:&lt;target_sw&gt;:&lt;target_hw&gt;:&lt;other&gt; of the CPE norm is not supported.
    ///
    impl Display for Cpe {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            let soft_type = match self.cpe_type {
                CpeType::Application => "a",
                CpeType::OperatingSystem => "o",
                CpeType::Hardware => "h",
                CpeType::All => "*",
            };

            write!(
                f,
                "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
                CPE_HEADER,
                soft_type,
                self.vendor,
                self.product,
                self.version,
                self.update,
                self.edition,
                self.language,
                self.sw_edition,
                self.target_sw,
                self.target_hw,
                self.other
            )
        }
    }

    /// Represents different types of Common Platform Enumeration (CPE) identifiers.
    ///
    /// CPE is a standardized method of describing and identifying classes of
    /// applications, operating systems, and hardware devices present among an enterprise's computing assets.
    ///
    /// # Variants
    ///
    /// - `All`: Reprents all type of products.
    /// - `Application`: Represents a software application.
    /// - `OperatingSystem`: Represents an operating system.
    ///
    #[derive(Eq, PartialEq, Clone, Debug)]
    pub enum CpeType {
        All,
        Application,
        OperatingSystem,
        Hardware,
    }

    impl Display for CpeType {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            let value = match self {
                CpeType::All => "*",
                CpeType::Application => "application",
                CpeType::OperatingSystem => "Os",
                CpeType::Hardware => "hardware",
            };

            f.write_str(value)
        }
    }

    /// CPE request header that's identifying the release used (cpe v 2.3)
    const CPE_HEADER: &str = "cpe:2.3";

    /// Extracts elements from a Common Platform Enumeration (CPE) string by splitting it on colons (`:`)
    /// and converting each element to lowercase.
    ///
    /// If the number of elements in the CPE string is less than or equal to 8, this function will write an
    /// error message and exit the process with a status code of 1.
    ///
    /// # Arguments
    ///
    /// * `cpe_string` - A string slice representing the CPE string to be parsed.
    ///
    /// # Returns
    ///
    /// A vector of strings, each representing an element of the CPE string.
    ///
    /// # Panics
    ///
    /// This function will exit the process if the number of elements is less than or equal to 8.
    ///
    /// # Examples
    ///
    /// ```
    /// use fenir::cpe::extract_elements;
    /// let cpe = "cpe:2.3:o:vendor:product:version:update:edition:language:*:*:*";
    /// let elements = extract_elements(cpe);
    /// assert!(elements.len() > 8);
    /// ```
    pub fn extract_elements(cpe_string: &str) -> Vec<String> {
        let elements: Vec<String> = cpe_string.split(':').map(|s| s.to_lowercase()).collect();
        if elements.len() <= 8 {
            write_error_message_and_exit(NOT_ENOUGH_ELEMENTS_ERROR, None);
        }
        elements
    }
}
/// Shows the version given as argument
///
/// # Arguments
///
/// * `version` - A string that's describing the version.
///
pub fn show_version(version: &str) {
    println!("Version: {}", version);
    println!("(fenir lib version: {})", env!("CARGO_PKG_VERSION"));
}

pub mod cve {
    use crate::cwe::join_cwe_list;
    use crate::facilities::Cleaning;
    use serde_json::Value;
    use std::fmt::{Display, Formatter};

    #[derive(Default, PartialEq, Clone, PartialOrd, Ord, Eq)]
    pub struct Score {
        score: String,
    }

    impl Score {
        pub fn new(value: String) -> Score {
            Self { score: value }
        }

        pub fn value(&self) -> f64 {
            self.score.parse::<f64>().unwrap()
        }

        pub fn label(&self) -> &str {
            match self.value() {
                0.0 => "None",
                value if value > 0.0 && value < 4.0 => "LOW",
                value if (4.0..7.0).contains(&value) => "MEDIUM",
                value if (7.0..9.0).contains(&value) => "HIGH",
                _ => "CRITICAL",
            }
        }
    }

    /// Represents a CVE with a reference.
    #[derive(PartialEq, PartialOrd, Ord, Eq, Default)]
    pub struct Cve {
        pub reference: String,
        pub description: Option<String>,
        pub score_v3: Option<Score>,
        pub score_v4: Option<Score>,
        pub cpe_id: Option<String>,
        pub weaknesses: Option<String>,
    }

    impl Cve {
        pub fn new(reference: String) -> Self {
            Self {
                reference,
                ..Default::default()
            }
        }
    }

    impl Display for Cve {
        fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
            write!(formatter, "{}", String::cleaning(self.reference.clone()))
        }
    }

    /// Create a CVE from the raw value
    ///
    /// # Arguments
    ///
    /// * `value`- Raw value
    ///
    /// # Returns
    ///
    /// The CVE
    pub fn create_cve_from(value: &Value) -> Cve {
        let mut cve = Cve::new(String::cleaning(value["cve"]["id"].to_string()));
        cve.description = Some(String::cleaning(
            value["cve"]["descriptions"][0]["value"].to_string(),
        ));
        let score_value =
            value["cve"]["metrics"]["cvssMetricV31"][0]["cvssData"]["baseScore"].to_string();
        cve.score_v3 = match score_value.as_str() {
            "null" | "None" => Some(Score::new("0.0".to_string())),
            _ => Some(Score::new(score_value)),
        };

        let score_value =
            value["cve"]["metrics"]["cvssMetricV40"][0]["cvssData"]["baseScore"].to_string();
        cve.score_v4 = match score_value.as_str() {
            "null" | "None" => Some(Score::new("0.0".to_string())),
            _ => Some(Score::new(score_value)),
        };

        let cwe = value["cve"]["weaknesses"][0]["description"].as_array();
        cve.weaknesses = match cwe {
            Some(_) => Some(String::cleaning(join_cwe_list(cwe.unwrap()))),
            None => Some(String::from("None")),
        };

        let optional_cpe = value["cve"]["configurations"][0]["nodes"][0]["cpeMatch"].as_array();
        if let Some(cpe_id) = optional_cpe {
            cve.cpe_id = Some(String::cleaning(
                cpe_id.first().unwrap()["criteria"].to_string(),
            ));
        } else {
            cve.cpe_id = Some(String::from("None"));
        }

        cve
    }


    /// Extracts a list of CVE (Common Vulnerabilities and Exposures) references
    /// from a given JSON value that contains CWE (Common Weakness Enumeration) data.
    ///
    /// # Arguments
    ///
    /// * `cwe_list` - A JSON value that contains a list of vulnerabilities under the key `"vulnerabilities"`.
    ///
    /// # Returns
    ///
    /// This function returns a vector of `Cve` structs, each containing the reference (`id`) of a CVE.
    ///
    /// # Example
    ///
    /// ```rust
    /// use serde_json::json;
    /// use fenir::cve_from_cwe;
    ///
    /// let cwe_json = json!({
    ///     "vulnerabilities": [
    ///         { "cve": { "id": "CVE-2021-1234" }},
    ///         { "cve": { "id": "CVE-2021-5678" }}
    ///     ]
    /// });
    ///
    /// let cves = cve_from_cwe(&cwe_json);
    /// assert_eq!(cves.len(), 2);
    /// assert_eq!(cves[0].reference, "\"CVE-2021-1234\"");
    /// assert_eq!(cves[1].reference, "\"CVE-2021-5678\"");
    /// ```
    ///
    /// # Note
    ///
    /// It is assumed that the `Cve` struct is defined elsewhere in your codebase.
    /// Below is an example of how `Cve` might be defined:
    ///
    /// ```rust
    /// #[derive(PartialEq, PartialOrd, Ord, Eq)]
    /// pub struct Cve {
    ///     pub reference: String,
    /// }
    /// ```
    pub fn cve_from_cwe(cwe_list: &Value) -> Vec<Cve> {
        let mut result: Vec<Cve> = vec![];
        let binding = cwe_list["vulnerabilities"].as_array();
        if let Some(_item) = binding {
            let lines = binding.unwrap();
            lines
                .iter()
                .for_each(|line| result.push(create_cve_from(line)))
        }

        result
    }
}

pub mod capec {
    use crate::database::{check_database, find_capec_by_id, parse_id_to_u32};
    use crate::facilities::Uppercase;
    use crate::os::find_tyr_home_path;
    use crate::{header, section};
    use std::fmt::{Display, Formatter};
    use termint::enums::Color;
    use termint::enums::Modifier;
    use termint::widgets::ToSpan;

    /// Structure for a CAPEC record
    #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
    pub struct CapecRecord {
        /// Capec's id
        pub id: u32,

        /// Capec's name
        pub name: String,

        /// Capec's abstraction
        pub abstraction: String,

        /// Capec's status
        pub status: String,

        /// Capec's description
        pub description: String,

        /// Capec's Likelihood
        pub likelihood: String,

        /// Capec's alternate terms
        pub alternates: String,

        /// Capec's severity
        pub severity: String,

        /// Capec's flow
        pub flow: String,

        /// Capec's related attack terms
        pub attacks: String,

        /// Capec prerequisites
        pub prerequisites: String,

        /// Capec's consequences
        pub consequences: String,

        /// Capec's skills
        pub skills: String,

        /// Capec's resources
        pub resources: String,

        /// Capec's indicators
        pub indicators: String,

        /// Capec mitigations
        pub mitigations: String,

        /// Capec weaknesses
        pub weaknesses: String,

        /// Capec's instances
        pub instances: String,

        /// Capec's taxonomy
        pub taxonomy: String,
    }

    impl Display for CapecRecord {
        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
            write!(
                f,
                "{}\n{} {} (Abstraction: {}. Status: {}.)",
                header!(format!("CAPEC-{}", self.id)),
                format!("{:>4} {}", "︎->", "Name:").fg(Color::Rgb(178, 189, 29)),
                self.name,
                self.abstraction,
                self.status
            )
        }
    }

    pub fn show_attacks(attacks: String) {
        let capec_list = parse_id_to_u32(attacks);
        if capec_list.is_empty() {
            println!("{:>4} None", "-");
            return;
        }

        for capec_id in capec_list {
            if check_database(&find_tyr_home_path(), "capec") {
                let result = find_capec_by_id(capec_id);
                if let Some(record) = result {
                    println!("{:>4} CAPEC-{} - {}", "-", capec_id, record.name);
                } else {
                    println!("{:>4} CAPEC-{} - ???", "-", capec_id);
                }
            } else {
                println!("{:>4} CAPEC-{}", "-", capec_id);
            }
        }
    }
}

/// Error message for error on not enough element during parsing
const NOT_ENOUGH_ELEMENTS_ERROR: &str = "Not enough elements for parsing";

#[cfg(test)]
mod tests {
    use std::thread::sleep;
    use std::time::Duration;

    fn setup() {
        let waiting = Duration::from_millis(30);
        sleep(waiting);
    }

    mod query {
        use crate::network::validate_connection;
        use crate::tests::setup;
        use crate::{NVD_CVE_LINK, NVD_KEY_NAME};
        use std::env;

        #[test]
        fn nvd_api_key_valid() {
            setup();

            let env_var = env::var_os(NVD_KEY_NAME);
            if env_var.is_some() {
                let result = validate_connection(NVD_CVE_LINK);
                assert_eq!(result, true);
            } else {
                panic!("No NVD_API_KEY found");
            }
        }

        #[test]
        fn is_connection_valid_url_validate() {
            setup();

            assert!(validate_connection(NVD_CVE_LINK));
        }

        #[test]
        fn is_connection_invalid_url_invalidate() {
            assert!(!validate_connection("http://foobar.local"));
        }

        mod build_query {
            use crate::facilities::get_current_date;
            use crate::query::build_query;
            use crate::query::QueryType::{ByCve, ByCveNew, ByCveUpdated, ByCwe};
            use crate::tests::setup;
            use crate::NVD_CVE_LINK;

            #[test]
            fn build_query_cve_valid() {
                setup();

                let result = format!("{NVD_CVE_LINK}?cveId=CVE-2020-2102");

                assert_eq!(result, build_query(ByCve, "CVE-2020-2102").unwrap());
            }

            #[test]
            fn build_query_cve_lowercase_valid() {
                setup();

                let result = format!("{NVD_CVE_LINK}?cveId=CVE-2020-2102");

                assert_eq!(result, build_query(ByCve, "cve-2020-2102").unwrap());
            }

            #[test]
            fn build_query_cwe_valid() {
                setup();

                let result = format!("{NVD_CVE_LINK}?cweId=CWE-287");

                assert_eq!(result, build_query(ByCwe, "CWE-287").unwrap());
            }

            #[test]
            fn build_query_cwe_lowercase_valid() {
                setup();

                let result = format!("{NVD_CVE_LINK}?cweId=CWE-287");

                assert_eq!(result, build_query(ByCwe, "cwe-287").unwrap());
            }

            #[test]
            fn build_query_new_dates_valid() {
                let result = build_query(ByCveNew, "2024-02-10;2024-03-10");

                assert_eq!(
                    "https://services.nvd.nist.gov/rest/json/cves/2.0?pubStartDate=2024-02-10T00:00:00.000&pubEndDate=2024-03-10T00:00:00.000",
                    result.unwrap().as_str()
                );
            }

            #[test]
            fn build_query_new_dates_default() {
                setup();

                let result = build_query(ByCveNew, "");
                let ref_date = format!(
                    "{NVD_CVE_LINK}?pubStartDate={}T00:00:00.000&pubEndDate={}T00:00:00.000",
                    get_current_date(),
                    get_current_date()
                );

                assert_eq!(ref_date, result.unwrap());
            }

            #[test]
            fn build_query_partial_date_first_default() {
                setup();

                let result = build_query(ByCveNew, ";2024-01-01");
                let ref_date = format!(
                    "{NVD_CVE_LINK}?pubStartDate={}T00:00:00.000&pubEndDate=2024-01-01T00:00:00.000",
                    get_current_date()
                );

                assert_eq!(ref_date, result.unwrap());
            }

            #[test]
            fn build_query_partial_date_last_default() {
                setup();

                let result = build_query(ByCveNew, "2024-01-01;");
                let ref_date = format!(
                    "{NVD_CVE_LINK}?pubStartDate=2024-01-01T00:00:00.000&pubEndDate={}T00:00:00.000",
                    get_current_date()
                );

                assert_eq!(ref_date, result.unwrap());
            }

            #[test]
            fn build_query_updated_valid() {
                setup();

                let result = build_query(ByCveUpdated, "2024-01-01;2024-02-01");

                assert_eq!(
                    format!(
                        "{NVD_CVE_LINK}?lastModStartDate=2024-01-01T00:00:00.000&lastModEndDate=2024-02-01T00:00:00.000"
                    ),
                    result.unwrap()
                );
            }

            #[test]
            fn build_query_updated_default_valid() {
                setup();

                let result = build_query(ByCveUpdated, "");

                assert_eq!(
                    format!(
                        "{NVD_CVE_LINK}?lastModStartDate={}T00:00:00.000&lastModEndDate={}T00:00:00.000",
                        get_current_date(),
                        get_current_date()
                    ),
                    result.unwrap()
                );
            }

            #[test]
            fn build_query_updated_first_valid() {
                setup();

                let result = build_query(ByCveUpdated, "2023-01-02;");

                assert_eq!(
                    format!(
                        "{NVD_CVE_LINK}?lastModStartDate=2023-01-02T00:00:00.000&lastModEndDate={}T00:00:00.000",
                        get_current_date()
                    ),
                    result.unwrap()
                );
            }

            #[test]
            fn build_query_updated_last_valid() {
                setup();

                let result = build_query(ByCveUpdated, ";2032-12-12");

                assert_eq!(
                    format!(
                        "{NVD_CVE_LINK}?lastModStartDate={}T00:00:00.000&lastModEndDate=2032-12-12T00:00:00.000",
                        get_current_date()
                    ),
                    result.unwrap()
                );
            }
        }

        mod execute_query {
            use crate::database::execute_query;
            use crate::query::build_query;
            use crate::query::QueryType::{ByCve, ByCveNew, ByCveUpdated};
            use crate::tests::setup;
            use serde_json::Value::Null;

            #[test]
            fn execute_query_valid_cve_found() {
                setup();

                let result = execute_query(build_query(ByCve, "CVE-2019-1010218"));

                assert_ne!(Null, result["resultsPerPage"]);
            }

            #[test]
            fn execute_query_invalid_cve_not_found() {
                setup();

                let result = execute_query(build_query(ByCve, "CVE-1980-0000"));

                assert_eq!(0, result["resultsPerPage"]);
            }

            #[test]
            fn execute_query_new_dates_valid() {
                setup();

                let result = execute_query(build_query(ByCveNew, "2024-01-01;2024-02-01"));

                assert_ne!(0, result["resultsPerPage"]);
            }

            #[test]
            fn execute_query_new_dates_swapped_not_valid() {
                setup();

                let result = execute_query(build_query(ByCveNew, "2024-02-01;2024-01-01"));

                assert_eq!(Null, result["resultsPerPage"]);
            }

            #[test]
            fn execute_query_new_dates_malformed_no_valid() {
                setup();

                let result = execute_query(build_query(ByCveNew, "202402-01;2024-03-01"));

                assert_eq!(Null, result["resultPerPage"]);
            }

            #[test]
            fn execute_query_update_dates_valid() {
                setup();

                let result = execute_query(build_query(ByCveUpdated, "2024-01-01;2024-02-01"));

                assert_ne!(0, result["resultsPerPage"]);
            }

            #[test]
            fn execute_query_updated_dates_swapped_not_valid() {
                setup();

                let result = execute_query(build_query(ByCveUpdated, "2024-02-01;2024-01-01"));

                assert_eq!(Null, result["resultsPerPage"]);
            }

            #[test]
            fn execute_query_updated_dates_malformed_no_valid() {
                setup();

                let result = execute_query(build_query(ByCveUpdated, "202402-01;2024-03-01"));

                assert_eq!(Null, result["resultPerPage"]);
            }
        }
    }

    mod string {
        use crate::facilities::{Categorize, Cleaning, Normalize, Transform, Uppercase, Wording};

        #[test]
        fn cleaning_ok() {
            let result = String::cleaning(String::from("\"value\""));

            assert_eq!("value", result);
        }

        #[test]
        fn cleaning_dots_ok() {
            let result = String::cleaning(String::from(":value:"));

            assert_eq!("value", result);
        }

        #[test]
        fn cleaning_no_cleaning() {
            let result = String::cleaning(String::from("value"));

            assert_eq!("value", result);
        }

        #[test]
        fn cleaning_empty_string_not_updated() {
            let result = String::cleaning(String::from(""));

            assert_eq!("", result);
        }

        #[test]
        fn only_uppercases_valid() {
            assert!(String::only_uppercases(String::from("BJDKE")));
        }

        #[test]
        fn only_uppercases_invalid() {
            assert_eq!(false, String::only_uppercases(String::from("ABDdD")));
        }

        #[test]
        fn only_uppercases_empty_invalid() {
            assert!(!String::only_uppercases(String::from("")));
        }

        #[test]
        fn only_uppercases_with_space_valid() {
            assert!(String::only_uppercases(String::from("TECHNICAL PART")));
        }

        #[test]
        fn smoothing_valid() {
            let original = String::from("::KKKK:fgte::::DFDF:fff::FFF:E::");
            let result = original.normalize(vec!["KKKK", "DFDF"]);

            assert_eq!("::KKKK:fgte::DFDF:fff:fff:e::", result);
        }

        #[test]
        fn smoothing_not_updated() {
            let original = String::from("::DKDKDKD::");
            let result = original.normalize(vec![""]);

            assert_eq!("::dkdkdkd::", result);
        }

        #[test]
        fn transform_valid() {
            let original = String::from("KKKKK fgte. DFDF");
            let result = original.transform(vec!["KKKKK", "DFDF"]);

            assert_eq!("::KKKKK: fgte. ::DFDF::", result);
        }

        #[test]
        fn wording_empty_string_valid() {
            let original = String::from("");
            let result = original.wording();

            assert_eq!("", result);
        }

        #[test]
        fn wording_associated_valid() {
            let original = String::from("wordAssociated");
            let result = original.wording();

            assert_eq!("Word associated", result);
        }

        #[test]
        fn wording_associated_inverse_valid() {
            let original = String::from("Wordassociation");
            let result = original.wording();

            assert_eq!("Wordassociation", result);
        }

        #[test]
        fn wording_not_associated_no_transformation() {
            let original = String::from("wordassociated");
            let result = original.wording();

            assert_eq!("Wordassociated", result);
        }

        #[test]
        fn wording_with_multi_words() {
            let original = String::from("OneWordAssociatedWithAnother");
            let result = original.wording();

            assert_eq!("One word associated with another", result);
        }

        #[test]
        fn wording_uppercases_lowercases_valid() {
            let original = String::from("WORDassociated");
            let result = original.wording();

            assert_eq!("Wordassociated", result);
        }

        #[test]
        fn wording_right_form_valid() {
            let original = String::from("Space");
            let result = original.wording();

            assert_eq!("Space", result);
        }

        #[test]
        fn wording_with_spaces_valid() {
            let original = String::from("Space From another World");
            let result = original.wording();

            assert_eq!("Space from another world", result);
        }

        #[test]
        fn first_uppercase_valid() {
            let original = String::from("first");
            let result = original.first_uppercase();

            assert_eq!("First", result);
        }

        #[test]
        fn first_already_uppercase_valid() {
            let original = String::from("First");
            let result = original.first_uppercase();

            assert_eq!("First", result);
        }
    }

    mod parse_id {
        use crate::os::parse_id;

        #[test]
        fn cwe_id_valid_id_parsed() {
            assert_eq!(Some(15), parse_id("CWE-15", "cwe"));
        }

        #[test]
        fn cwe_id_valid_lowercase_id_parsed() {
            assert_eq!(Some(118), parse_id("cwe-118", "cwe"));
        }

        #[test]
        fn cwe_id_invalid_id_not_parsed() {
            assert_eq!(None, parse_id("cwe116", "cwe"));
        }

        #[test]
        fn cwe_id_invalid_id_num_not_parsed() {
            assert_eq!(None, parse_id("CWE-A1", "cwe"));
        }

        #[test]
        fn cwe_id_invalid_id_prefix_not_parsed() {
            assert_eq!(None, parse_id("DFDF-12", "cwe"));
        }

        #[test]
        fn cwe_id_incomplete_id_not_parsed() {
            assert_eq!(None, parse_id("CWE-", "cwe"));
        }

        #[test]
        fn cwe_id_no_separator_not_parsed() {
            assert_eq!(None, parse_id("CWE", "cwe"));
        }
    }

    mod criteria {
        use crate::database::criteria_analysis;

        #[test]
        fn simple_no_error() -> Result<(), String> {
            let args = String::from("Masquerading");
            let result = criteria_analysis(args);

            match result {
                Some(_) => Ok(()),
                None => Err("Simple criteria analysis failed".to_string()),
            }
        }

        #[test]
        fn simple_right_content() -> Result<(), String> {
            let args = String::from("Masquerading");
            let result = criteria_analysis(args);

            match result.unwrap().0.contains(&String::from("Masquerading")) {
                true => Ok(()),
                false => Err("Simple criteria analysis failed: content not found".to_string()),
            }
        }

        #[test]
        fn analysis_empty_string() -> Result<(), String> {
            let result = criteria_analysis(String::from(""));

            match result {
                Some(_) => Err("Empty criteria analysis failed".to_string()),
                None => Ok(()),
            }
        }

        #[test]
        fn analysis_only_or_operator() -> Result<(), String> {
            let args = String::from("Masquerading or Hijack");
            let result = criteria_analysis(args);

            match result {
                Some(_) => Ok(()),
                None => Err("Error with or operator".to_string()),
            }
        }

        #[test]
        fn analysis_or_operator_right_content() -> Result<(), String> {
            let args = String::from("Masquerading or Hijack");
            let result = criteria_analysis(args);

            let mut count = 0;
            result.clone().unwrap().0.iter().for_each(|x| {
                if !x.trim().is_empty() {
                    if x.trim() == "Masquerading" || x.trim() == "Hijack" {
                        count += 1;
                    } else {
                        count -= 1;
                    }
                }
            });

            result.unwrap().1.iter().for_each(|x| {
                if x.trim() == "or" {
                    count += 1;
                } else {
                    count -= 1;
                }
            });

            match count {
                3 => Ok(()),
                _ => Err("Error with or operator".to_string()),
            }
        }

        #[test]
        fn analysis_only_and_operator() -> Result<(), String> {
            let args = String::from("Process and Hijacking");
            let result = criteria_analysis(args);

            match result {
                Some(_) => Ok(()),
                None => Err("Error with and operator".to_string()),
            }
        }

        #[test]
        fn analysis_and_operator_right_content() -> Result<(), String> {
            let args = String::from("Process and Hijacking");
            let result = criteria_analysis(args);

            let mut count = 0;
            result.clone().unwrap().0.iter().for_each(|x| {
                if !x.is_empty() {
                    if x.trim() == "Process" || x.trim() == "Hijacking" {
                        count += 1;
                    } else {
                        count -= 1;
                    }
                }
            });

            result.unwrap().1.iter().for_each(|x| {
                if x.trim() == "and" {
                    count += 1;
                } else {
                    count -= 1;
                }
            });

            match count {
                3 => Ok(()),
                _ => Err("Error with and operator".to_string()),
            }
        }

        #[test]
        fn analysis_only_not_operator() -> Result<(), String> {
            let args = String::from("not Masquerading");
            let result = criteria_analysis(args);

            match result {
                Some(_) => Ok(()),
                None => Err("Error with not operator".to_string()),
            }
        }

        #[test]
        fn analysis_not_operator_right_content() -> Result<(), String> {
            let args = String::from("not Masquerading");
            let result = criteria_analysis(args);

            let mut count = 0;
            result.clone().unwrap().0.iter().for_each(|x| {
                if x.trim() == "Masquerading" || x.trim() == "" {
                    count += 1;
                } else {
                    count -= 1;
                }
            });

            result.unwrap().1.iter().for_each(|x| {
                if x.trim() == "not" {
                    count += 1;
                } else {
                    count -= 1;
                }
            });

            match count {
                3 => Ok(()),
                _ => Err("Error with not operator".to_string()),
            }
        }

        #[test]
        fn analysis_with_and_or_operators() -> Result<(), String> {
            let args = String::from("Phishing or Process and Hijacking");
            let result = criteria_analysis(args);

            match result {
                Some(_) => Ok(()),
                None => Err("Error with and/or operators".to_string()),
            }
        }

        #[test]
        fn analysis_and_operators_right_content() -> Result<(), String> {
            let args = String::from("Phishing or Process and Hijacking");
            let result = criteria_analysis(args);

            let mut count = 0;
            result.clone().unwrap().0.iter().for_each(|x| {
                if !x.is_empty() {
                    if x.trim() == "Phishing" || x.trim() == "Process" || x.trim() == "Hijacking" {
                        count += 1;
                    } else {
                        count -= 1;
                    }
                }
            });

            result.unwrap().1.iter().for_each(|x| {
                if x.trim() == "and" || x.trim() == "or" {
                    count += 1;
                } else {
                    count -= 1;
                }
            });

            match count {
                5 => Ok(()),
                _ => Err("Error with or/and operators".to_string()),
            }
        }

        #[test]
        fn analysis_and_or_not_operators() -> Result<(), String> {
            let args = String::from("Phishing or Process and Hijacking and not Masquerading");
            let result = criteria_analysis(args);

            match result {
                Some(_) => Ok(()),
                None => Err("Error with and/not/or operators".to_string()),
            }
        }

        #[test]
        fn analysis_and_or_not_operators_right_content() -> Result<(), String> {
            let args = String::from("Phishing or Process and Hijacking and not Masquerading");
            let result = criteria_analysis(args);

            let mut count = 0;
            result.clone().unwrap().0.iter().for_each(|x| {
                if !x.trim().is_empty() {
                    if x.trim() == "Phishing"
                        || x.trim() == "Process"
                        || x.trim() == "Hijacking"
                        || x.trim() == "Masquerading"
                    {
                        count += 1;
                    } else {
                        count -= 1;
                    }
                }
            });

            result.unwrap().1.iter().for_each(|x| {
                if x.trim() == "and" || x.trim() == "or" || x.trim() == "not" {
                    count += 1;
                } else {
                    count -= 1;
                }
            });

            match count {
                8 => Ok(()),
                _ => Err("Error with and/or/not operators".to_string()),
            }
        }
    }

    mod find_domain {
        use crate::database::find_domain_and_criteria;

        #[test]
        fn criteria_simple_no_error() -> Result<(), String> {
            let args = &[String::from("taxonomy = Hijack")];
            let values = find_domain_and_criteria(args);

            match values {
                Some(_) => Ok(()),
                None => Err("An error was occurred".to_string()),
            }
        }

        #[test]
        fn criteria_simple_with_error() -> Result<(), String> {
            let args = &[String::from("taxonomy")];
            let values = find_domain_and_criteria(args);

            match values {
                None => Ok(()),
                Some(_) => Err("Invalid result".to_string()),
            }
        }

        #[test]
        fn criteria_simple_valid() -> Result<(), String> {
            let args = &[String::from("taxonomy = Masquerading")];
            let values = find_domain_and_criteria(args);

            let (domain, argument) = values.unwrap();

            if domain == "taxonomy" && argument == "Masquerading" {
                Ok(())
            } else {
                Err("Invalid domain or argument".to_string())
            }
        }

        #[test]
        fn criteria_complex() -> Result<(), String> {
            let args = &[String::from(
                "taxonomy = Masquerading and Hijack or impair defense",
            )];
            let values = find_domain_and_criteria(args);
            let (domain, argument) = values.unwrap();

            if domain == "taxonomy" && argument == "Masquerading and Hijack or impair defense" {
                Ok(())
            } else {
                Err("domain or argument invalid".to_string())
            }
        }

        #[test]
        fn criteria_malformed_error() -> Result<(), String> {
            let args = &[String::from("taxonomy = ")];
            let values = find_domain_and_criteria(args);

            match values {
                None => Ok(()),
                Some(_) => {
                    Err("The malformed expression 'taxonomy = ' would not be accepted".to_string())
                }
            }
        }

        #[test]
        fn criteria_malformed_domain_error() -> Result<(), String> {
            let args = &[String::from("= Masquerading ")];
            let values = find_domain_and_criteria(args);

            match values {
                None => Ok(()),
                Some(_) => {
                    Err("The malformed expression ' = Masquerading ' would be invalid".to_string())
                }
            }
        }

        #[test]
        fn criteria_malformed_equal_only_error() -> Result<(), String> {
            let args = &[String::from("=")];
            let values = find_domain_and_criteria(args);

            match values {
                None => Ok(()),
                Some(_) => Err("'=' alone would not be supported".to_string()),
            }
        }

        #[test]
        fn criteria_malformed_triple_equals_error() -> Result<(), String> {
            let args = &[String::from("===")];
            let values = find_domain_and_criteria(args);

            match values {
                None => Ok(()),
                Some(_) => Err("'===' would not be supported".to_string()),
            }
        }
    }
}
