#[cfg(test)]
#[allow(clippy::module_inception)]
pub mod ansi_test_utils {
    use ansi_term;

    use crate::ansi;
    use crate::config::Config;
    use crate::delta::State;
    use crate::paint;
    use crate::style::Style;

    // Check if `output[line_number]` start with `expected_prefix`
    // Then check if the first style in the line is the `expected_style`
    pub fn assert_line_has_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert!(_line_has_style(
            output,
            line_number,
            expected_prefix,
            expected_style,
            config,
            false,
        ));
    }

    // Check if `output[line_number]` start with `expected_prefix`
    // Then check if it contains the `expected_substring` with the corresponding `expected_style`
    // If the line contains multiples times the `expected_style`, will only compare with the first
    // item found
    pub fn assert_line_contain_substring_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_substring: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert_eq!(
            expected_substring,
            _line_get_substring_matching_style(
                output,
                line_number,
                expected_prefix,
                expected_style,
                config,
            )
            .unwrap()
        );
    }

    // Check if `output[line_number]` start with `expected_prefix`
    // Then check if the line does not contains the `expected_style`
    pub fn assert_line_does_not_contain_substring_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert!(_line_get_substring_matching_style(
            output,
            line_number,
            expected_prefix,
            expected_style,
            config,
        )
        .is_none());
    }

    pub fn assert_line_does_not_have_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert!(!_line_has_style(
            output,
            line_number,
            expected_prefix,
            expected_style,
            config,
            false,
        ));
    }

    pub fn assert_line_has_4_bit_color_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) {
        assert!(_line_has_style(
            output,
            line_number,
            expected_prefix,
            expected_style,
            config,
            true,
        ));
    }

    pub fn assert_line_has_no_color(output: &str, line_number: usize, expected_prefix: &str) {
        let line = output.lines().nth(line_number).unwrap();
        let stripped_line = ansi::strip_ansi_codes(line);
        assert!(stripped_line.starts_with(expected_prefix));
        assert_eq!(line, stripped_line);
    }

    /// Assert that the specified line number of output (a) has, after stripping ANSI codes, a
    /// substring starting at `substring_begin` equal to `expected_substring` and (b) in its raw
    /// form contains a version of that substring syntax-highlighted according to
    /// `language_extension`.
    pub fn assert_line_has_syntax_highlighted_substring(
        output: &str,
        line_number: usize,
        substring_begin: usize,
        expected_substring: &str,
        filename_for_highlighting: &str,
        state: State,
        config: &Config,
    ) {
        assert!(
            filename_for_highlighting.contains('.'),
            "expecting filename, not just a file extension"
        );
        let line = output.lines().nth(line_number).unwrap();
        let substring_end = substring_begin + expected_substring.len();
        let substring = &ansi::strip_ansi_codes(line)[substring_begin..substring_end];
        assert_eq!(substring, expected_substring);
        let painted_substring = paint_line(substring, filename_for_highlighting, state, config);
        // remove trailing newline appended by paint::paint_lines.
        assert!(line.contains(painted_substring.trim_end()));
    }

    pub fn assert_has_color_other_than_plus_color(string: &str, config: &Config) {
        let (string_without_any_color, string_with_plus_color_only) =
            get_color_variants(string, config);
        assert_ne!(string, string_without_any_color);
        assert_ne!(string, string_with_plus_color_only);
    }

    pub fn assert_has_plus_color_only(string: &str, config: &Config) {
        let (string_without_any_color, string_with_plus_color_only) =
            get_color_variants(string, config);
        assert_ne!(string, string_without_any_color);
        assert_eq!(string, string_with_plus_color_only);
    }

    pub fn get_color_variants(string: &str, config: &Config) -> (String, String) {
        let string_without_any_color = ansi::strip_ansi_codes(string);
        let string_with_plus_color_only = config
            .plus_style
            .ansi_term_style
            .paint(&string_without_any_color);
        (
            string_without_any_color.to_string(),
            string_with_plus_color_only.to_string(),
        )
    }

    pub fn paint_line(
        line: &str,
        filename_for_highlighting: &str,
        state: State,
        config: &Config,
    ) -> String {
        let mut output_buffer = String::new();
        let mut unused_writer = Vec::<u8>::new();
        let mut painter = paint::Painter::new(&mut unused_writer, config);
        let syntax_highlighted_style = Style {
            is_syntax_highlighted: true,
            ..Style::new()
        };
        painter.set_syntax(Some(filename_for_highlighting));
        painter.set_highlighter();
        let lines = vec![(line.to_string(), state)];
        let syntax_style_sections = paint::get_syntax_style_sections_for_lines(
            &lines,
            painter.highlighter.as_mut(),
            config,
        );
        let diff_style_sections = vec![vec![(syntax_highlighted_style, lines[0].0.as_str())]];
        paint::Painter::paint_lines(
            &lines,
            &syntax_style_sections,
            &diff_style_sections,
            &[false],
            &mut output_buffer,
            config,
            &mut None,
            None,
            paint::BgShouldFill::default(),
        );
        output_buffer
    }

    fn _line_extract<'a>(output: &'a str, line_number: usize, expected_prefix: &str) -> &'a str {
        let line = output.lines().nth(line_number).unwrap();
        assert!(ansi::strip_ansi_codes(line).starts_with(expected_prefix));
        line
    }

    fn _line_get_substring_matching_style<'a>(
        output: &'a str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
    ) -> Option<&'a str> {
        let line = _line_extract(output, line_number, expected_prefix);
        let style = Style::from_str(
            expected_style,
            None,
            None,
            config.true_color,
            config.git_config.as_ref(),
        );
        style.get_matching_substring(line)
    }

    fn _line_has_style(
        output: &str,
        line_number: usize,
        expected_prefix: &str,
        expected_style: &str,
        config: &Config,
        _4_bit_color: bool,
    ) -> bool {
        let line = _line_extract(output, line_number, expected_prefix);
        let mut style = Style::from_str(
            expected_style,
            None,
            None,
            config.true_color,
            config.git_config(),
        );
        if _4_bit_color {
            style.ansi_term_style.foreground = style
                .ansi_term_style
                .foreground
                .map(ansi_term_fixed_foreground_to_4_bit_color);
        }
        style.is_applied_to(line)
    }

    fn ansi_term_fixed_foreground_to_4_bit_color(color: ansi_term::Color) -> ansi_term::Color {
        match color {
            ansi_term::Color::Fixed(30) => ansi_term::Color::Black,
            ansi_term::Color::Fixed(31) => ansi_term::Color::Red,
            ansi_term::Color::Fixed(32) => ansi_term::Color::Green,
            ansi_term::Color::Fixed(33) => ansi_term::Color::Yellow,
            ansi_term::Color::Fixed(34) => ansi_term::Color::Blue,
            ansi_term::Color::Fixed(35) => ansi_term::Color::Purple,
            ansi_term::Color::Fixed(36) => ansi_term::Color::Cyan,
            ansi_term::Color::Fixed(37) => ansi_term::Color::White,
            color => panic!(
                "Invalid 4-bit color: {:?}. \
                 (Add bright color entries to this map if needed for tests.",
                color
            ),
        }
    }
}
