use std::fmt::Display;

use ratatui::{
    Frame,
    layout::{Alignment, Direction, Layout, Rect},
    style::{Color, Modifier, Style, Stylize},
    symbols,
    text::Span,
    widgets::{Axis, Block, BorderType, Borders, Chart, Dataset, GraphType},
};

use super::{CONSTRAINT_50_50, FrameData};
use crate::{
    app_data::{State, Stats},
    config::AppColors,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ChartVariant {
    Cpu,
    Memory,
}

impl ChartVariant {
    const fn name(self) -> &'static str {
        match self {
            Self::Cpu => "cpu",
            Self::Memory => "memory",
        }
    }

    const fn get_title_color(self, colors: AppColors, state: State) -> Color {
        if state.is_healthy() {
            match self {
                Self::Cpu => colors.chart_cpu.title,
                Self::Memory => colors.chart_memory.title,
            }
        } else {
            state.get_color(colors)
        }
    }

    const fn get_bg_color(self, colors: AppColors) -> Color {
        match self {
            Self::Cpu => colors.chart_cpu.background,
            Self::Memory => colors.chart_memory.background,
        }
    }

    const fn get_border_color(self, colors: AppColors) -> Color {
        match self {
            Self::Cpu => colors.chart_cpu.border,
            Self::Memory => colors.chart_memory.border,
        }
    }

    const fn get_y_axis_color(self, colors: AppColors) -> Color {
        match self {
            Self::Cpu => colors.chart_cpu.y_axis,
            Self::Memory => colors.chart_memory.y_axis,
        }
    }

    const fn get_max_color(self, colors: AppColors, state: State) -> Color {
        if state.is_healthy() {
            match self {
                Self::Cpu => colors.chart_cpu.max,
                Self::Memory => colors.chart_memory.max,
            }
        } else {
            state.get_color(colors)
        }
    }
}

/// Create charts
fn make_chart<'a, T: Stats + Display>(
    chart_variant: ChartVariant,
    colors: AppColors,
    current: &'a T,
    dataset: Vec<Dataset<'a>>,
    max: &'a T,
    state: State,
) -> Chart<'a> {
    let max_color = chart_variant.get_max_color(colors, state);

    Chart::new(dataset)
        .bg(chart_variant.get_bg_color(colors))
        .block(
            Block::default()
                .style(Style::default().bg(chart_variant.get_bg_color(colors)))
                .title_alignment(Alignment::Center)
                .title(Span::styled(
                    format!(" {} {current} ", chart_variant.name()),
                    Style::default()
                        .fg(chart_variant.get_title_color(colors, state))
                        .add_modifier(Modifier::BOLD),
                ))
                .borders(Borders::ALL)
                .border_type(BorderType::Rounded)
                .border_style(Style::default().fg(chart_variant.get_border_color(colors))),
        )
        .x_axis(Axis::default().bounds([0.00, 60.0]))
        .y_axis(
            Axis::default()
                .labels(vec![
                    Span::styled("", Style::default().fg(max_color)),
                    Span::styled(
                        format!("{max}"),
                        Style::default().add_modifier(Modifier::BOLD).fg(max_color),
                    ),
                ])
                .style(Style::new().fg(chart_variant.get_y_axis_color(colors)))
                // Add 0.01, so that max point is always visible?
                .bounds([0.0, max.get_value() + 0.01]),
        )
}

/// Draw the cpu + mem charts
pub fn draw(area: Rect, colors: AppColors, f: &mut Frame, fd: &FrameData) {
    if let Some(x) = fd.chart_data.as_ref() {
        let area = Layout::default()
            .direction(Direction::Horizontal)
            .constraints(CONSTRAINT_50_50)
            .split(area);

        let cpu_dataset = vec![
            Dataset::default()
                .marker(symbols::Marker::Dot)
                .style(Style::default().fg(colors.chart_cpu.points))
                .graph_type(GraphType::Line)
                .data(&x.cpu.dataset),
        ];
        let mem_dataset = vec![
            Dataset::default()
                .marker(symbols::Marker::Dot)
                .style(Style::default().fg(colors.chart_memory.points))
                .graph_type(GraphType::Line)
                .data(&x.memory.dataset),
        ];

        // let cpu_stats = CpuStats::new(cpu.0.last().map_or(0.00, |f| f.1));
        // #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
        // let mem_stats = ByteStats::new(mem.0.last().map_or(0, |f| f.1 as u64));
        let cpu_chart = make_chart(
            ChartVariant::Cpu,
            colors,
            &x.cpu.current,
            cpu_dataset,
            &x.cpu.max,
            x.state,
        );
        let mem_chart = make_chart(
            ChartVariant::Memory,
            colors,
            &x.memory.current,
            mem_dataset,
            &x.memory.max,
            x.state,
        );

        f.render_widget(cpu_chart, area[0]);
        f.render_widget(mem_chart, area[1]);
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use insta::assert_snapshot;
    use ratatui::style::{Color, Modifier};

    use crate::{
        app_data::State,
        config::AppColors,
        ui::{
            FrameData,
            draw_blocks::tests::{COLOR_ORANGE, get_result, insert_all_chart_data, test_setup},
        },
    };

    /// CPU and Memory charts used in multiple tests, based on data from above insert_chart_data()
    const _EXPECTED: [&str; 10] = [
        "╭───────────── cpu 03.00% ─────────────╮╭────────── memory 30.00 kB ───────────╮",
        "│10.00%│    •                          ││100.00 kB│   ••                       │",
        "│      │   ••                          ││         │   ••                       │",
        "│      │  •••                          ││         │  • •                       │",
        "│      │  • •                          ││         │ •  •                       │",
        "│      │ •   ••                        ││         │••  ••                      │",
        "│      │•    •                         ││         │•   •                       │",
        "│      │•    •                         ││         │•   •                       │",
        "│      │                               ││         │                            │",
        "╰──────────────────────────────────────╯╰──────────────────────────────────────╯",
    ];

    // co-ordinates of the dots from the cpu chart
    const CPU_XY: [(usize, usize); 16] = [
        (1, 13),
        (2, 12),
        (2, 13),
        (3, 11),
        (3, 13),
        (4, 11),
        (4, 13),
        (5, 10),
        (5, 13),
        (6, 9),
        (6, 13),
        (6, 14),
        (7, 8),
        (7, 9),
        (7, 13),
        (7, 14),
    ];

    // co-ordinates of the dots from the memory chart
    const MEM_XY: [(usize, usize); 14] = [
        (1, 55),
        (2, 54),
        (2, 55),
        (3, 54),
        (3, 55),
        (4, 53),
        (4, 55),
        (5, 52),
        (5, 53),
        (5, 56),
        (6, 52),
        (6, 56),
        (7, 51),
        (7, 56),
    ];

    #[test]
    /// When status is Running, but not data, charts drawn without dots etc, colours correct
    fn test_draw_blocks_charts_running_none() {
        let mut setup = test_setup(80, 10, true, true);

        let fd = FrameData::from((&setup.app_data, &setup.gui_state));
        setup
            .terminal
            .draw(|f| {
                super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
            })
            .unwrap();
        assert_snapshot!(setup.terminal.backend());

        for (row_index, result_row) in get_result(&setup) {
            for (result_cell_index, result_cell) in result_row.iter().enumerate() {
                match (row_index, result_cell_index) {
                    (0, 14..=25 | 52..=67) => {
                        assert_eq!(result_cell.fg, Color::Green);
                        assert_eq!(result_cell.modifier, Modifier::BOLD);
                    }
                    (1, 1..=6 | 41..=47) => {
                        assert_eq!(result_cell.fg, COLOR_ORANGE);
                        assert_eq!(result_cell.modifier, Modifier::BOLD);
                    }
                    (2..=8, 1..=6 | 8..=38 | 49..=78 | 41..=47) | (1, 8..=38 | 49..=78) => {
                        assert_eq!(result_cell.fg, Color::Reset);
                        assert!(result_cell.modifier.is_empty());
                    }
                    _ => {
                        assert_eq!(result_cell.fg, Color::White);
                        assert!(result_cell.modifier.is_empty());
                    }
                }
            }
        }
    }

    #[test]
    /// When status is Running, charts correctly drawn
    fn test_draw_blocks_charts_running_some() {
        let mut setup = test_setup(80, 10, true, true);

        insert_all_chart_data(&setup);
        let fd = FrameData::from((&setup.app_data, &setup.gui_state));

        setup
            .terminal
            .draw(|f| {
                super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
            })
            .unwrap();

        assert_snapshot!(setup.terminal.backend());

        for (row_index, result_row) in get_result(&setup) {
            for (result_cell_index, result_cell) in result_row.iter().enumerate() {
                match (row_index, result_cell_index) {
                    (0, 14..=25 | 51..=67) => {
                        assert_eq!(result_cell.fg, Color::Green);
                        assert_eq!(result_cell.modifier, Modifier::BOLD);
                    }
                    (1, 1..=6 | 41..=49) => {
                        assert_eq!(result_cell.fg, COLOR_ORANGE);
                        assert_eq!(result_cell.modifier, Modifier::BOLD);
                    }
                    xy if CPU_XY.contains(&xy) => {
                        assert_eq!(result_cell.fg, Color::Magenta);
                        assert!(result_cell.modifier.is_empty());
                    }
                    xy if MEM_XY.contains(&xy) => {
                        assert_eq!(result_cell.fg, Color::Cyan);
                        assert!(result_cell.modifier.is_empty());
                    }
                    (0 | 9, 0..=80) | (1..=9, 0 | 7 | 39 | 40 | 50 | 79) => {
                        assert_eq!(result_cell.fg, Color::White);
                        assert!(result_cell.modifier.is_empty());
                    }
                    _ => {
                        assert_eq!(result_cell.fg, Color::Reset);
                        assert!(result_cell.modifier.is_empty());
                    }
                }
            }
        }
    }

    #[test]
    /// Whens status paused, some text is now Yellow
    fn test_draw_blocks_charts_paused() {
        let mut setup = test_setup(80, 10, true, true);

        insert_all_chart_data(&setup);
        setup.app_data.lock().containers.items[0].state = State::Paused;
        let fd = FrameData::from((&setup.app_data, &setup.gui_state));

        setup
            .terminal
            .draw(|f| {
                super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
            })
            .unwrap();

        assert_snapshot!(setup.terminal.backend());
        //

        for (row_index, result_row) in get_result(&setup) {
            for (result_cell_index, result_cell) in result_row.iter().enumerate() {
                match (row_index, result_cell_index) {
                    (0, 14..=25 | 51..=67) | (1, 1..=6 | 41..=49) => {
                        assert_eq!(result_cell.fg, Color::Yellow);
                        assert_eq!(result_cell.modifier, Modifier::BOLD);
                    }
                    xy if CPU_XY.contains(&xy) => {
                        assert_eq!(result_cell.fg, Color::Magenta);
                        assert!(result_cell.modifier.is_empty());
                    }
                    xy if MEM_XY.contains(&xy) => {
                        assert_eq!(result_cell.fg, Color::Cyan);
                        assert!(result_cell.modifier.is_empty());
                    }
                    (0 | 9, 0..=80) | (1..=9, 0 | 7 | 39 | 40 | 50 | 79) => {
                        assert_eq!(result_cell.fg, Color::White);
                        assert!(result_cell.modifier.is_empty());
                    }
                    _ => {
                        assert_eq!(result_cell.fg, Color::Reset);
                        assert!(result_cell.modifier.is_empty());
                    }
                }
            }
        }
    }

    #[test]
    /// When dead, text is red
    fn test_draw_blocks_charts_dead() {
        let mut setup = test_setup(80, 10, true, true);
        insert_all_chart_data(&setup);
        setup.app_data.lock().containers.items[0].state = State::Dead;
        let fd = FrameData::from((&setup.app_data, &setup.gui_state));

        setup
            .terminal
            .draw(|f| {
                super::draw(setup.area, setup.app_data.lock().config.app_colors, f, &fd);
            })
            .unwrap();
        assert_snapshot!(setup.terminal.backend());
        for (row_index, result_row) in get_result(&setup) {
            for (result_cell_index, result_cell) in result_row.iter().enumerate() {
                match (row_index, result_cell_index) {
                    (0, 14..=25 | 51..=67) | (1, 1..=6 | 41..=49) => {
                        assert_eq!(result_cell.fg, Color::Red);
                        assert_eq!(result_cell.modifier, Modifier::BOLD);
                    }
                    xy if CPU_XY.contains(&xy) => {
                        assert_eq!(result_cell.fg, Color::Magenta);
                        assert!(result_cell.modifier.is_empty());
                    }
                    xy if MEM_XY.contains(&xy) => {
                        assert_eq!(result_cell.fg, Color::Cyan);
                        assert!(result_cell.modifier.is_empty());
                    }
                    (0 | 9, 0..=80) | (1..=9, 0 | 7 | 39 | 40 | 50 | 79) => {
                        assert_eq!(result_cell.fg, Color::White);
                        assert!(result_cell.modifier.is_empty());
                    }
                    _ => {
                        assert_eq!(result_cell.fg, Color::Reset);
                        assert!(result_cell.modifier.is_empty());
                    }
                }
            }
        }
    }

    #[test]
    /// Custom colos correctly applied to each part of the charts
    fn test_draw_blocks_charts_custom_colors() {
        let mut colors = AppColors::new();

        colors.chart_cpu.background = Color::White;
        colors.chart_cpu.border = Color::Red;
        colors.chart_cpu.title = Color::Green;
        colors.chart_cpu.max = Color::Magenta;
        colors.chart_cpu.points = Color::Black;
        colors.chart_cpu.y_axis = Color::Blue;

        colors.chart_memory.background = Color::White;
        colors.chart_memory.border = Color::Red;
        colors.chart_memory.title = Color::Green;
        colors.chart_memory.max = Color::Magenta;
        colors.chart_memory.points = Color::Black;
        colors.chart_memory.y_axis = Color::Blue;

        let mut setup = test_setup(80, 10, true, true);

        insert_all_chart_data(&setup);
        let fd = FrameData::from((&setup.app_data, &setup.gui_state));

        setup
            .terminal
            .draw(|f| {
                super::draw(setup.area, colors, f, &fd);
            })
            .unwrap();

        assert_snapshot!(setup.terminal.backend());

        for (row_index, result_row) in get_result(&setup) {
            for (result_cell_index, result_cell) in result_row.iter().enumerate() {
                assert_eq!(result_cell.bg, Color::White);

                match (row_index, result_cell_index) {
                    // border
                    (0, 0..=13 | 26..=50 | 68..=79) | (9, _) | (1..=8, 0 | 39 | 40 | 79) => {
                        assert_eq!(result_cell.fg, Color::Red);
                    }
                    // title
                    (0, 14..=25 | 51..=67) => {
                        assert_eq!(result_cell.fg, Color::Green);
                    }
                    // max label
                    (1, 1..=6 | 41..=49) => {
                        assert_eq!(result_cell.fg, Color::Magenta);
                    }
                    // data points
                    xy if CPU_XY.contains(&xy) | MEM_XY.contains(&xy) => {
                        assert_eq!(result_cell.fg, Color::Black);
                    }
                    // y axis
                    (1..=8, 7 | 50) => {
                        assert_eq!(result_cell.fg, Color::Blue);
                    }
                    _ => {
                        assert_eq!(result_cell.fg, Color::Reset);
                    }
                }
            }
        }
    }
}
