/*!
 * Copyright 2019-2026, Benjamin Vaisvil and the zenith contributors
 */

use super::style::{max_style, ok_style, MAX_COLOR, OK_COLOR};
use crate::float_to_byte_string;
use crate::metrics::histogram::{HistogramKind, View};
use crate::metrics::CPUTimeApp;
use crate::renderer::{percent_of, split_left_right_pane, Render};
use byte_unit::{Byte, Unit};
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{BarChart, Block, Borders, Paragraph, Sparkline, Wrap};
use ratatui::Frame;

fn cpu_title<'a>(app: &'a CPUTimeApp, histogram: &'a [u64]) -> Line<'a> {
    let top_process_name = match &app.cum_cpu_process {
        Some(p) => p.name.as_str(),
        None => "",
    };
    let top_process_amt = match &app.cum_cpu_process {
        Some(p) => p.user_name.as_str(),
        None => "",
    };
    let top_pid = match &app.cum_cpu_process {
        Some(p) => p.pid,
        None => 0,
    };
    let mean: f64 = match histogram.len() {
        0 => 0.0,
        _ => histogram.iter().sum::<u64>() as f64 / histogram.len() as f64,
    };

    let peak: u64 = match histogram.len() {
        0 => 0,
        _ => histogram.iter().max().unwrap_or(&0).to_owned(),
    };
    // Build TEMP spans conditionally - if no sensors, returns empty vec
    // so "TEMP []" doesn't appear in the title bar
    let temp_spans: Vec<Span> = if !app.sensors.is_empty() {
        let t = app
            .sensors
            .iter()
            .map(|s| format!("{: >3.0}", s.current_temp))
            .collect::<Vec<String>>()
            .join(",");

        let hot_threshold = 70_f64;
        let cold_threshold = 40_f64;
        let numbers_txt = format!("{t:}°C");
        let max_temp = app
            .sensors
            .iter()
            .map(|s| s.current_temp as f64)
            .fold(f64::MIN, f64::max);

        // Style the temperature number based on how hot it is
        let colored = if max_temp > hot_threshold {
            Span::styled(numbers_txt, max_style())
        } else if max_temp < cold_threshold {
            Span::styled(numbers_txt, Style::default().fg(Color::Cyan))
        } else {
            Span::raw(numbers_txt)
        };

        // Return all three spans so brackets only appear when there are sensors
        vec![Span::raw(" TEMP ["), colored, Span::raw("]")]
    } else {
        vec![]
    };

    // Build the title spans incrementally so TEMP section can be
    // conditionally included via extend
    let mut spans = vec![
        Span::raw("CPU ["),
        Span::styled(
            format!("{: >3}%", app.cpu_utilization),
            if app.cpu_utilization > 90 {
                max_style()
            } else {
                ok_style()
            },
        ),
        Span::raw("]"),
    ];
    if !temp_spans.is_empty() {
        spans.extend(temp_spans);
    }
    spans.extend([
        Span::raw(" MEAN ["),
        Span::styled(
            format!("{mean: >3.2}%"),
            if mean > 90.0 { max_style() } else { ok_style() },
        ),
        Span::raw("] PEAK ["),
        Span::styled(
            format!("{peak: >3.2}%"),
            if peak > 90 { max_style() } else { ok_style() },
        ),
        Span::raw(format!(
            "] TOP [{top_pid} - {top_process_name} - {top_process_amt}]"
        )),
    ]);
    Line::from(spans)
}

fn mem_title(app: &'_ CPUTimeApp) -> Line<'_> {
    let mem = percent_of(app.mem_utilization, app.mem_total) as u64;
    let swp = percent_of(app.swap_utilization, app.swap_total) as u64;

    let top_mem_proc = match app.top_pids.mem.pid {
        Some(pid) => match app.process_map.get(&pid) {
            Some(p) => format!("TOP [{:} - {:} - {:}]", p.pid, p.name, p.user_name),
            None => String::from(""),
        },
        None => String::from(""),
    };

    Line::from(vec![
        Span::raw("MEM ["),
        Span::styled(
            format!(
                "{} / {} - {:}%",
                float_to_byte_string!(app.mem_utilization as f64, Unit::B),
                float_to_byte_string!(app.mem_total as f64, Unit::B),
                mem
            ),
            if mem > 95 { max_style() } else { ok_style() },
        ),
        Span::raw("] SWP ["),
        Span::styled(
            format!(
                "{} / {} - {:}%",
                float_to_byte_string!(app.swap_utilization as f64, Unit::B),
                float_to_byte_string!(app.swap_total as f64, Unit::B),
                swp,
            ),
            if swp > 20 { max_style() } else { ok_style() },
        ),
        Span::raw("] "),
        Span::raw(top_mem_proc),
    ])
}

fn render_cpu_histogram(app: &CPUTimeApp, area: Rect, f: &mut Frame<'_>, view: &View) {
    let h = match app.histogram_map.get_zoomed(&HistogramKind::Cpu, view) {
        Some(h) => h,
        None => return,
    };
    let title = cpu_title(app, h.data());
    Sparkline::default()
        .block(Block::default().title(title))
        .data(h.data())
        .style(Style::default().fg(Color::Blue))
        .max(100)
        .render(f, area);
}

fn render_memory_histogram(app: &CPUTimeApp, area: Rect, f: &mut Frame<'_>, view: &View) {
    let h = match app.histogram_map.get_zoomed(&HistogramKind::Mem, view) {
        Some(h) => h,
        None => return,
    };
    let title2 = mem_title(app);
    Sparkline::default()
        .block(Block::default().title(title2))
        .data(h.data())
        .style(Style::default().fg(Color::Cyan))
        .max(100)
        .render(f, area);
}

fn render_cpu_bars(app: &CPUTimeApp, area: Rect, f: &mut Frame<'_>, style: &Style) {
    let cpus = app.cpus.to_owned();
    if cpus.is_empty() {
        return;
    }

    let core_count = cpus.len() as u16;
    let widest_label = cpus.iter().map(|(s, _)| s.len()).max().unwrap_or(0) as u16;
    let full_width = widest_label * core_count + (core_count - 1);

    Block::default()
        .title(Span::styled(
            format!(
                "CPU{} {}@{} MHz",
                if core_count > 1 { "S" } else { "" },
                core_count,
                app.frequency
            ),
            *style,
        ))
        .borders(Borders::ALL)
        .border_style(*style)
        .render(f, area);

    let layout = Layout::default().margin(1).direction(Direction::Vertical);

    if full_width > 2 * area.width {
        // won't fit in 2 rows of bars, using grid layout

        let layout = layout
            .constraints(vec![Constraint::Percentage(100)])
            .split(area);

        let cols = 4;

        let nrows = (cpus.len() as u16).div_ceil(cols) as usize;

        let mut items = vec![];
        for i in 0..nrows {
            cpus.iter()
                .skip(i)
                .step_by(nrows)
                .take(cols.into())
                .for_each(|(label, load)| {
                    items.push(Span::raw(format!("{label:<2} ")));
                    let color = if *load < 90 { OK_COLOR } else { MAX_COLOR };
                    items.push(Span::styled(
                        format!("{load:3}"),
                        Style::default().fg(color),
                    ));
                    items.push(Span::raw("% "));
                });
        }

        Paragraph::new(Line::from(items))
            .wrap(Wrap { trim: false })
            .render(f, layout[0]);

        return;
    }

    // displaying as bars
    let bars: Vec<(&str, u64)> = cpus.iter().map(|(p, u)| (p.as_str(), *u)).collect();

    fn clamp_up(val: u16, upper: u16) -> u16 {
        if val > upper {
            upper
        } else {
            val
        }
    }
    let max_bar_width = 3;

    fn styled_bar_chart() -> BarChart<'static> {
        BarChart::default()
            .bar_gap(1)
            .max(100)
            .bar_style(Style::default().fg(Color::Green))
            .value_style(
                Style::default()
                    .fg(Color::White)
                    .bg(Color::Green)
                    .add_modifier(Modifier::BOLD),
            )
    }

    if full_width <= area.width {
        // fits in one row

        let cpu_bar_layout = layout
            .constraints(vec![Constraint::Percentage(100)])
            .split(area);

        let bar_width = clamp_up((area.width - (core_count - 1)) / core_count, max_bar_width);

        styled_bar_chart()
            .data(bars.as_slice())
            .bar_width(bar_width)
            .render(f, cpu_bar_layout[0]);
    } else {
        // fits on two rows

        let half = bars.len() / 2;

        let cpu_bar_layout = layout
            .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
            .split(area);

        let bar_width = clamp_up(
            (area.width * 2 - (core_count - 1)) / core_count,
            max_bar_width,
        );

        styled_bar_chart()
            .data(&bars[half..])
            .bar_width(bar_width)
            .render(f, cpu_bar_layout[1]);

        styled_bar_chart()
            .data(&bars[0..half])
            .bar_width(bar_width)
            .render(f, cpu_bar_layout[0]);
    }
}

pub fn render_cpu(
    app: &CPUTimeApp,
    area: Rect,
    f: &mut Frame<'_>,
    view: View,
    border_style: Style,
) {
    let (cpu_layout, view) = split_left_right_pane("", area, f, view, border_style);

    let cpu_mem = Layout::default()
        .margin(1)
        .direction(Direction::Vertical)
        .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
        .split(cpu_layout[1]);
    render_cpu_histogram(app, cpu_mem[0], f, &view);
    render_memory_histogram(app, cpu_mem[1], f, &view);
    render_cpu_bars(app, cpu_layout[0], f, &border_style);
}
