use std::string::ToString;

use super::{Context, Module, ModuleConfig};

use crate::configs::status::StatusConfig;
use crate::formatter::{StringFormatter, string_formatter::StringFormatterError};
use crate::segment::Segment;

type ExitCode = i32;
type SignalNumber = u32;
#[derive(PartialEq)]
enum PipeStatusStatus<'a> {
    Disabled,
    NoPipe,
    Pipe(&'a Vec<std::string::String>),
}

/// Creates a module with the status of the last command
///
/// Will display the status
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
    let mut module = context.new_module("status");
    let config = StatusConfig::try_load(module.config);

    // As we default to disabled=true, we have to check here after loading our config module,
    // before it was only checking against whatever is in the config starship.toml
    if config.disabled {
        return None;
    }

    let exit_code = context.properties.status_code.as_deref().unwrap_or("0");

    let pipestatus_status = match &context.properties.pipestatus {
        None => PipeStatusStatus::Disabled,
        Some(ps) => {
            if ps.len() > 1 {
                PipeStatusStatus::Pipe(ps)
            } else {
                PipeStatusStatus::NoPipe
            }
        }
    };

    let pipestatus_status = if config.pipestatus {
        pipestatus_status
    } else {
        PipeStatusStatus::Disabled
    };

    // Exit code is zero while success_symbol and pipestatus are all zero or disabled/missing
    if exit_code == "0"
        && config.success_symbol.is_empty()
        && (match pipestatus_status {
            PipeStatusStatus::Pipe(ps) => ps.iter().all(|s| s == "0"),
            _ => true,
        })
    {
        return None;
    }

    let segment_format = config.pipestatus_segment_format.unwrap_or(config.format);
    let segment_format_with_separator = [segment_format, config.pipestatus_separator].join("");

    // Create pipestatus segments
    let pipestatus = match pipestatus_status {
        PipeStatusStatus::Pipe(ps) => ps
            .iter()
            .enumerate()
            .filter_map(|(i, ec)| {
                let formatted = format_exit_code(
                    ec.as_str(),
                    if i == ps.len() - 1 {
                        segment_format
                    } else {
                        &segment_format_with_separator
                    },
                    None,
                    &config,
                    context,
                );
                match formatted {
                    Ok(segments) => Some(segments),
                    Err(e) => {
                        log::warn!("Error parsing format string in `status.pipestatus_segment_format`: {e:?}");
                        None
                    }
                }
            })
            .flatten()
            .collect(),
        _ => Vec::new(),
    };

    let main_format = match pipestatus_status {
        PipeStatusStatus::Pipe(_) => config.pipestatus_format,
        _ => config.format,
    };
    let parsed = format_exit_code(exit_code, main_format, Some(&pipestatus), &config, context);

    module.set_segments(match parsed {
        Ok(segments) => segments,
        Err(_error) => {
            log::warn!("Error parsing format string in `status.format`");
            return None;
        }
    });
    Some(module)
}

fn format_exit_code<'a>(
    exit_code: &'a str,
    format: &'a str,
    pipestatus: Option<&Vec<Segment>>,
    config: &'a StatusConfig,
    context: &'a Context,
) -> Result<Vec<Segment>, StringFormatterError> {
    // First, parse as i64 to accept both i32 or u32, then normalize to i32.
    let exit_code_int = if let Ok(i) = exit_code.parse::<i64>() {
        i as ExitCode
    } else {
        log::warn!("Error parsing exit_code string to int");
        return Ok(Vec::new());
    };

    let hex_status = format!("0x{exit_code_int:X}");

    let common_meaning = status_common_meaning(exit_code_int);

    let raw_signal_number = if config.recognize_signal_code {
        status_to_signal(exit_code_int)
    } else {
        None
    };
    let signal_number = raw_signal_number.map(|sn| sn.to_string());
    let signal_name =
        raw_signal_number.and_then(|sn| status_signal_name(sn).or(signal_number.as_deref()));

    // If not a signal and not a common meaning, it should at least print the raw exit code number
    let maybe_exit_code_number = if common_meaning.is_none() && signal_name.is_none() {
        Some(exit_code)
    } else {
        None
    };

    StringFormatter::new(format).and_then(|formatter| {
        formatter
            .map_meta(|var, _| match var {
                "symbol" => match exit_code_int {
                    0 => Some(config.success_symbol),
                    126 if config.map_symbol => Some(config.not_executable_symbol),
                    127 if config.map_symbol => Some(config.not_found_symbol),
                    130 if config.recognize_signal_code && config.map_symbol => {
                        Some(config.sigint_symbol)
                    }
                    x if (129..256).contains(&x)
                        && config.recognize_signal_code
                        && config.map_symbol =>
                    {
                        Some(config.signal_symbol)
                    }
                    _ => Some(config.symbol),
                },
                _ => None,
            })
            .map_style(|variable| match variable {
                "style" => Some(Ok(if exit_code_int == 0 {
                    config.success_style
                } else {
                    config.failure_style
                }
                .unwrap_or(config.style))),
                _ => None,
            })
            .map(|variable| match variable {
                "status" => Some(Ok(exit_code)),
                "hex_status" => Some(Ok(hex_status.as_ref())),
                "int" => Some(Ok(exit_code)),
                "maybe_int" => Ok(maybe_exit_code_number).transpose(),
                "common_meaning" => Ok(common_meaning).transpose(),
                "signal_number" => Ok(signal_number.as_deref()).transpose(),
                "signal_name" => Ok(signal_name).transpose(),
                _ => None,
            })
            .map_variables_to_segments(|variable| match variable {
                "pipestatus" if pipestatus.is_none() => {
                    // We might enter this case if pipestatus hasn't
                    // been processed yet, which means that it has been
                    // set in format
                    log::warn!("pipestatus variable is only available in pipestatus_format");
                    None
                }
                "pipestatus" => pipestatus.cloned().map(Ok),
                _ => None,
            })
            .parse(None, Some(context))
    })
}

fn status_common_meaning(ex: ExitCode) -> Option<&'static str> {
    // Over 128 are Signal exit code
    if ex > 128 {
        return None;
    }
    match ex {
        0 => Some(""), // SUCCESS can be defined by $success_symbol if the user wishes too.
        1 => Some("ERROR"),
        2 => Some("USAGE"),

        // status codes 64-78 from libc
        64 => Some("USAGE"),
        65 => Some("DATAERR"),
        66 => Some("NOINPUT"),
        67 => Some("NOUSER"),
        68 => Some("NOHOST"),
        69 => Some("UNAVAILABLE"),
        70 => Some("SOFTWARE"),
        71 => Some("OSERR"),
        72 => Some("OSFILE"),
        73 => Some("CANTCREAT"),
        74 => Some("IOERR"),
        75 => Some("TEMPFAIL"),
        76 => Some("PROTOCOL"),
        77 => Some("NOPERM"),
        78 => Some("CONFIG"),

        126 => Some("NOPERM"),
        127 => Some("NOTFOUND"),
        _ => None,
    }
}

fn status_to_signal(ex: ExitCode) -> Option<SignalNumber> {
    if ex < 129 {
        return None;
    }
    let sn = ex - 128;
    Some(sn as u32)
}

fn status_signal_name(signal: SignalNumber) -> Option<&'static str> {
    match signal {
        1 => Some("HUP"),     // 128 + 1
        2 => Some("INT"),     // 128 + 2
        3 => Some("QUIT"),    // 128 + 3
        4 => Some("ILL"),     // 128 + 4
        5 => Some("TRAP"),    // 128 + 5
        6 => Some("IOT"),     // 128 + 6
        7 => Some("BUS"),     // 128 + 7
        8 => Some("FPE"),     // 128 + 8
        9 => Some("KILL"),    // 128 + 9
        10 => Some("USR1"),   // 128 + 10
        11 => Some("SEGV"),   // 128 + 11
        12 => Some("USR2"),   // 128 + 12
        13 => Some("PIPE"),   // 128 + 13
        14 => Some("ALRM"),   // 128 + 14
        15 => Some("TERM"),   // 128 + 15
        16 => Some("STKFLT"), // 128 + 16
        17 => Some("CHLD"),   // 128 + 17
        18 => Some("CONT"),   // 128 + 18
        19 => Some("STOP"),   // 128 + 19
        20 => Some("TSTP"),   // 128 + 20
        21 => Some("TTIN"),   // 128 + 21
        22 => Some("TTOU"),   // 128 + 22
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use nu_ansi_term::{Color, Style};

    use crate::test::ModuleRenderer;

    #[test]
    fn success_status_success_symbol_empty() {
        let expected = None;

        // Status code 0 and success_symbol = ""
        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                success_symbol = ""
                disabled = false
            })
            .status(0)
            .collect();
        assert_eq!(expected, actual);

        // Status code 0 and success_symbol is missing
        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                disabled = false
            })
            .status(0)
            .collect();
        assert_eq!(expected, actual);

        // No status code and success_symbol = ""
        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                success_symbol = ""
                disabled = false
            })
            .collect();
        assert_eq!(expected, actual);

        // No status code and success_symbol is missing
        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                disabled = false
            })
            .collect();
        assert_eq!(expected, actual);
    }

    #[test]
    fn success_status_success_symbol_filled() {
        let expected = Some(format!("{} ", Color::Red.bold().paint("✔️0")));

        // Status code 0
        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                success_symbol = "✔️"
                disabled = false
            })
            .status(0)
            .collect();
        assert_eq!(expected, actual);

        // No status code
        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                success_symbol = "✔️"
                disabled = false
            })
            .collect();
        assert_eq!(expected, actual);
    }

    #[test]
    fn not_enabled() {
        let expected = None;

        let actual = ModuleRenderer::new("status").status(1).collect();
        assert_eq!(expected, actual);
    }

    #[test]
    fn failure_status() {
        let exit_values = [1, 2, 130];

        for status in &exit_values {
            let expected = Some(format!(
                "{} ",
                Color::Red.bold().paint(format!("❌{status}"))
            ));
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    symbol = "❌"
                    disabled = false
                })
                .status(*status)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn failure_plaintext_status() {
        let exit_values = [1, 2, 130];

        for status in &exit_values {
            let expected = Some(format!(
                "{} ",
                Color::Red.bold().paint(format!("x {status}"))
            ));
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    symbol = "[x](bold red) "
                    disabled = false
                })
                .status(*status)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn failure_hex_status() {
        let exit_values = [1, 2, 130, -2_147_467_260, 2_147_500_036];
        let string_values = ["0x1", "0x2", "0x82", "0x80004004", "0x80004004"];

        for (exit_value, string_value) in exit_values.iter().zip(string_values) {
            let expected = Some(format!(
                "{} ",
                Color::Red.bold().paint(format!("❌{string_value}"))
            ));
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    symbol = "❌"
                    disabled = false
                    format = "[${symbol}${hex_status}]($style) "
                })
                .status(*exit_value)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn signal_name() {
        let exit_values = [1, 2, 126, 127, 130, 101];
        let exit_values_name = [
            Some("ERROR"),
            Some("USAGE"),
            Some("NOPERM"),
            Some("NOTFOUND"),
            Some("INT"),
            None,
        ];

        for (status, name) in exit_values.iter().zip(&exit_values_name) {
            let expected = name.map(std::string::ToString::to_string);
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    format = "$common_meaning$signal_name"
                    disabled = false
                })
                .status(*status)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn exit_code_name_no_signal() {
        let exit_values = [
            1, 2, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 126, 127, 130, 101,
            132,
        ];
        let exit_values_name = [
            Some("ERROR"),
            Some("USAGE"),
            Some("USAGE"),
            Some("DATAERR"),
            Some("NOINPUT"),
            Some("NOUSER"),
            Some("NOHOST"),
            Some("UNAVAILABLE"),
            Some("SOFTWARE"),
            Some("OSERR"),
            Some("OSFILE"),
            Some("CANTCREAT"),
            Some("IOERR"),
            Some("TEMPFAIL"),
            Some("PROTOCOL"),
            Some("NOPERM"),
            Some("CONFIG"),
            Some("NOPERM"),
            Some("NOTFOUND"),
            None,
            None,
            None,
        ];

        for (status, name) in exit_values.iter().zip(&exit_values_name) {
            let expected = name.map(std::string::ToString::to_string);
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    format = "$common_meaning$signal_name"
                    recognize_signal_code = false
                    disabled = false
                })
                .status(*status)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn maybe_exit_code_number() {
        let exit_values = [1, 2, 126, 127, 130, 101, 6, -3];
        let exit_values_name = [
            None,
            None,
            None,
            None,
            None,
            Some("101"),
            Some("6"),
            Some("-3"),
        ];

        for (status, name) in exit_values.iter().zip(&exit_values_name) {
            let expected = name.map(std::string::ToString::to_string);
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    format = "$maybe_int"
                    disabled = false
                })
                .status(*status)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn special_symbols() {
        let exit_values = [1, 126, 127, 130, 131];
        let exit_values_name = ["🔴", "🚫", "🔍", "🧱", "⚡"];

        for (status, name) in exit_values.iter().zip(&exit_values_name) {
            let expected = Some((*name).to_string());
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    format = "$symbol"
                    symbol = "🔴"
                    not_executable_symbol = "🚫"
                    not_found_symbol = "🔍"
                    sigint_symbol = "🧱"
                    signal_symbol = "⚡"
                    recognize_signal_code = true
                    map_symbol = true
                    disabled = false
                })
                .status(*status)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn special_symbols_no_signals() {
        let exit_values = [1, 126, 127, 130, 131];
        let exit_values_name = ["🔴", "🚫", "🔍", "🔴", "🔴"];

        for (status, name) in exit_values.iter().zip(&exit_values_name) {
            let expected = Some((*name).to_string());
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    format = "$symbol"
                    symbol = "🔴"
                    not_executable_symbol = "🚫"
                    not_found_symbol = "🔍"
                    sigint_symbol = "🧱"
                    signal_symbol = "⚡"
                    recognize_signal_code = false
                    map_symbol = true
                    disabled = false
                })
                .status(*status)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn pipeline_uses_pipestatus_format() {
        let exit_values = [
            [0, 1, 0, 0],
            [0, 1, 2, 3],
            [130, 126, 131, 127],
            [1, 1, 1, 1],
        ];
        let exit_values_rendered = [
            "PSF 🟢=🔴 🟢 🟢",
            "PSF 🟢=🔴 🔴 🔴",
            "PSF 🧱=🚫 ⚡ 🔍",
            "PSF 🔴=🔴 🔴 🔴",
        ];

        for (status, rendered) in exit_values.iter().zip(&exit_values_rendered) {
            let main_exit_code = status[0];
            let pipe_exit_code = &status[1..];

            let expected = Some((*rendered).to_string());
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    format = "$symbol"
                    symbol = "🔴"
                    success_symbol = "🟢"
                    not_executable_symbol = "🚫"
                    not_found_symbol = "🔍"
                    sigint_symbol = "🧱"
                    signal_symbol = "⚡"
                    recognize_signal_code = true
                    map_symbol = true
                    pipestatus = true
                    pipestatus_separator = " "
                    pipestatus_format = "PSF $symbol=$pipestatus"
                    disabled = false
                })
                .status(main_exit_code)
                .pipestatus(pipe_exit_code)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn pipeline_no_map_symbols() {
        let exit_values = [
            [0, 1, 0, 0],
            [0, 1, 2, 3],
            [130, 126, 131, 127],
            [1, 1, 1, 1],
        ];
        let exit_values_rendered = [
            "PSF 🟢=🔴1 🟢0 🟢0",
            "PSF 🟢=🔴1 🔴2 🔴3",
            "PSF INT🔴=🔴126 🔴1313 🔴127",
            "PSF 🔴=🔴1 🔴1 🔴1",
        ];

        for (status, rendered) in exit_values.iter().zip(&exit_values_rendered) {
            let main_exit_code = status[0];
            let pipe_exit_code = &status[1..];

            let expected = Some((*rendered).to_string());
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    format = "$symbol$int$signal_number"
                    symbol = "🔴"
                    success_symbol = "🟢"
                    not_executable_symbol = "🚫"
                    not_found_symbol = "🔍"
                    sigint_symbol = "🧱"
                    signal_symbol = "⚡"
                    recognize_signal_code = true
                    map_symbol = false
                    pipestatus = true
                    pipestatus_separator = " "
                    pipestatus_format = "PSF $signal_name$symbol=$pipestatus"
                    disabled = false
                })
                .status(main_exit_code)
                .pipestatus(pipe_exit_code)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn successful_pipeline() {
        let pipe_exit_code = [0, 0, 0];

        let main_exit_code = 0;

        let expected = None;

        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                disabled = false
            })
            .status(main_exit_code)
            .pipestatus(&pipe_exit_code)
            .collect();
        assert_eq!(expected, actual);
    }

    #[test]
    fn successful_pipeline_pipestatus_enabled() {
        let pipe_exit_code = [0, 0, 0];

        let main_exit_code = 0;

        let expected = None;

        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                disabled = false
                pipestatus = true
            })
            .status(main_exit_code)
            .pipestatus(&pipe_exit_code)
            .collect();
        assert_eq!(expected, actual);
    }

    #[test]
    fn pipeline_disabled() {
        let exit_values = [[130, 126, 131, 127], [1, 1, 1, 1]];
        let exit_values_rendered = ["F 🧱", "F 🔴"];

        for (status, rendered) in exit_values.iter().zip(&exit_values_rendered) {
            let main_exit_code = status[0];
            let pipe_exit_code = &status[1..];

            let expected = Some((*rendered).to_string());
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    format = "F $symbol"
                    symbol = "🔴"
                    success_symbol = "🟢"
                    not_executable_symbol = "🚫"
                    not_found_symbol = "🔍"
                    sigint_symbol = "🧱"
                    signal_symbol = "⚡"
                    recognize_signal_code = true
                    map_symbol = true
                    pipestatus = false
                    pipestatus_separator = " "
                    pipestatus_format = "PSF $symbol=$pipestatus"
                    disabled = false
                })
                .status(main_exit_code)
                .pipestatus(pipe_exit_code)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn pipeline_long() {
        let exit_values = [
            [130, 0, 0, 0, 30, 1, 2, 3, 142, 0, 0, 0, 130],
            [1, 0, 0, 0, 30, 127, 126, 3, 142, 0, 230, 0, 2],
        ];
        let exit_values_rendered = [
            "PSF 130INT=🟢|🟢|🟢|🔴30|🔴|🔴|🔴3|⚡|🟢|🟢|🟢|🧱",
            "PSF 1ERROR=🟢|🟢|🟢|🔴30|🔍|🚫|🔴3|⚡|🟢|⚡|🟢|🔴",
        ];

        for (status, rendered) in exit_values.iter().zip(&exit_values_rendered) {
            let main_exit_code = status[0];
            let pipe_exit_code = &status[1..];

            let expected = Some((*rendered).to_string());
            let actual = ModuleRenderer::new("status")
                .config(toml::toml! {
                    [status]
                    format = "$symbol$maybe_int"
                    symbol = "🔴"
                    success_symbol = "🟢"
                    not_executable_symbol = "🚫"
                    not_found_symbol = "🔍"
                    sigint_symbol = "🧱"
                    signal_symbol = "⚡"
                    recognize_signal_code = true
                    map_symbol = true
                    pipestatus = true
                    pipestatus_separator = "|"
                    pipestatus_format = "PSF $int$common_meaning$signal_name=$pipestatus"
                    disabled = false
                })
                .status(main_exit_code)
                .pipestatus(pipe_exit_code)
                .collect();
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn pipestatus_segment_format() {
        let pipe_exit_code = &[0, 1];
        let main_exit_code = 1;

        let expected = Some("[0]|[1] => <1>".to_string());
        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                format = "\\($status\\)"
                pipestatus = true
                pipestatus_separator = "|"
                pipestatus_format = "$pipestatus => <$status>"
                pipestatus_segment_format = "\\[$status\\]"
                disabled = false
            })
            .status(main_exit_code)
            .pipestatus(pipe_exit_code)
            .collect();
        assert_eq!(expected, actual);
    }

    #[test]
    fn pipestatus_separator_format() {
        let pipe_exit_code = &[0, 1, 2];
        let main_exit_code = 2;

        let style = Style::new().on(Color::Red).fg(Color::White).bold();
        let sep_style = Style::new().on(Color::Green).fg(Color::White).italic();
        let expected = Some(format!(
            "{}{}{}{}{}",
            style.paint("[0"),
            sep_style.paint("|"),
            style.paint("1"),
            sep_style.paint("|"),
            style.paint("2] => <2>"),
        ));
        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                format = "\\($status\\)"
                style = "fg:white bg:red bold"
                pipestatus = true
                pipestatus_separator = "[|](fg:white bg:green italic)"
                pipestatus_format = "[\\[]($style)$pipestatus[\\] => <$status>]($style)"
                pipestatus_segment_format = "[$status]($style)"
                disabled = false
            })
            .status(main_exit_code)
            .pipestatus(pipe_exit_code)
            .collect();
        assert_eq!(expected, actual);
    }

    #[test]
    fn pipestatus_width() {
        let pipe_exit_code = &[0, 1, 2];
        let main_exit_code = 2;

        let renderer = ModuleRenderer::new("status")
            .config(toml::toml! {
                format = "$fill$status"
                [status]
                style = "fg:white bg:red bold"
                pipestatus = true
                pipestatus_segment_format = "[$status](bg:blue fg:yellow)"
                disabled = false
            })
            .status(main_exit_code)
            .pipestatus(pipe_exit_code)
            .width(100);
        let context = crate::modules::Context::from(renderer);
        let actual = crate::print::get_prompt(&context);

        let mut escaping = false;
        let mut width = 0;
        for c in actual.chars() {
            if c == '\x1B' {
                escaping = true;
            }
            if escaping {
                escaping = !c.is_ascii_alphabetic();
                continue;
            }
            width += 1;
        }
        assert_eq!(width, 100);
    }

    #[test]
    fn pipestatus_segment_format_err() {
        let pipe_exit_code = &[0, 1, 2];
        let main_exit_code = 2;

        let expected = Some(format!(
            "{}",
            Style::new()
                .on(Color::Red)
                .fg(Color::White)
                .bold()
                .paint("[] => <2>"),
        ));
        let actual = ModuleRenderer::new("status")
            .config(toml::toml! {
                [status]
                style = "fg:white bg:red bold"
                pipestatus = true
                pipestatus_format = "[\\[]($style)$pipestatus[\\] => <$status>]($style)"
                pipestatus_segment_format = "${"
                disabled = false
            })
            .status(main_exit_code)
            .pipestatus(pipe_exit_code)
            .collect();
        assert_eq!(expected, actual);
    }
}
