use std::sync::OnceLock;

use ab_glyph::{FontArc, PxScale};
use chrono::Local;
use image::{Rgb, RgbImage};
use imageproc::{
    drawing::{draw_filled_rect_mut, draw_hollow_rect_mut, draw_text_mut},
    rect::Rect,
};
use sysinfo::{Components, Disks, System};

use crate::types::SystemView;

pub(crate) const DISPLAY_WIDTH: u32 = 960;
pub(crate) const DISPLAY_HEIGHT: u32 = 376;

pub(crate) fn normalize_specs_mode(mode: &str) -> &'static str {
    match mode {
        "meters" => "meters",
        _ => "cards",
    }
}

pub(crate) fn collect_system_view() -> SystemView {
    fn mount_usage(disks: &Disks, mount_point: &str) -> (String, String, String) {
        disks
            .iter()
            .find(|disk| disk.mount_point() == std::path::Path::new(mount_point))
            .map(|disk| {
                let total = disk.total_space();
                let used = total.saturating_sub(disk.available_space());
                let usage_percent = if total == 0 {
                    0.0
                } else {
                    used as f64 / total as f64 * 100.0
                };
                (
                    format!("{usage_percent:.0}"),
                    format_bytes(used),
                    format_bytes(total),
                )
            })
            .unwrap_or_else(|| ("n/a".into(), "n/a".into(), "n/a".into()))
    }

    let mut sys = System::new_all();
    sys.refresh_all();

    let load_avg = System::load_average();
    let total_memory = sys.total_memory();
    let used_memory = sys.used_memory();
    let total_swap = sys.total_swap();
    let used_swap = sys.used_swap();

    let mut disks = Disks::new();
    disks.refresh(false);
    let disk_total: u64 = disks.iter().map(|disk| disk.total_space()).sum();
    let disk_used: u64 = disks
        .iter()
        .map(|disk| disk.total_space().saturating_sub(disk.available_space()))
        .sum();

    let mut components = Components::new();
    components.refresh(false);

    let (cache_usage_percent, cache_used, cache_total) = mount_usage(&disks, "/mnt/cache");
    let (user_usage_percent, user_used, user_total) = mount_usage(&disks, "/mnt/user");

    SystemView {
        cache_usage_percent,
        cache_used,
        cache_total,
        user_usage_percent,
        user_used,
        user_total,
        cpu_usage_percent: format!("{:.1}", sys.global_cpu_usage()),
        load_avg_one: format!("{:.2}", load_avg.one),
        load_avg_five: format!("{:.2}", load_avg.five),
        load_avg_fifteen: format!("{:.2}", load_avg.fifteen),
        mem_usage_percent: format!("{:.1}", percentage(used_memory, total_memory)),
        mem_used: format_bytes(used_memory),
        mem_total: format_bytes(total_memory),
        swap_usage_percent: format!("{:.1}", percentage(used_swap, total_swap)),
        swap_used: format_bytes(used_swap),
        swap_total: format_bytes(total_swap),
        disk_usage_percent: format!("{:.1}", percentage(disk_used, disk_total)),
        disk_used: format_bytes(disk_used),
        disk_total: format_bytes(disk_total),
        cpu_count: sys.cpus().len(),
        process_count: sys.processes().len(),
        uptime: format_uptime(System::uptime()),
        temperature_cpu: component_temperature(&components, "Tctl"),
        temperature_gpu: component_temperature(&components, "amdgpu"),
    }
}

fn component_temperature(components: &Components, needle: &str) -> Option<String> {
    components
        .iter()
        .find(|component| component.label().contains(needle))
        .and_then(|component| component.temperature())
        .map(|value| format!("{value:.1} °C"))
}

fn percentage(used: u64, total: u64) -> f64 {
    if total == 0 {
        0.0
    } else {
        used as f64 * 100.0 / total as f64
    }
}

fn format_uptime(seconds: u64) -> String {
    let days = seconds / 86_400;
    let hours = (seconds % 86_400) / 3_600;
    let minutes = (seconds % 3_600) / 60;

    if days == 0 {
        format!("{hours:02}:{minutes:02}")
    } else if days == 1 {
        format!("1 day, {hours:02}:{minutes:02}")
    } else {
        format!("{days} days, {hours:02}:{minutes:02}")
    }
}

fn format_bytes(bytes: u64) -> String {
    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB"];
    const THRESHOLD: f64 = 1024.0;

    if bytes == 0 {
        return "0 B".to_string();
    }

    let mut size = bytes as f64;
    let mut unit_index = 0;

    while size >= THRESHOLD && unit_index < UNITS.len() - 1 {
        size /= THRESHOLD;
        unit_index += 1;
    }

    if unit_index > 0 {
        format!("{size:.2} {}", UNITS[unit_index])
    } else {
        format!("{} {}", size, UNITS[unit_index])
    }
}

pub(crate) fn render_system_panel(mode: &str) -> RgbImage {
    match normalize_specs_mode(mode) {
        "meters" => render_system_panel_meters(),
        _ => render_system_panel_cards(),
    }
}

fn render_system_panel_cards() -> RgbImage {
    let system = collect_system_view();
    let mut canvas = RgbImage::from_pixel(DISPLAY_WIDTH, DISPLAY_HEIGHT, Rgb([10, 14, 18]));
    let host_name = System::host_name().unwrap_or_else(|| "Unraid".into());
    let timestamp = Local::now().format("%d.%m.%Y %H:%M:%S").to_string();

    draw_filled_rect_mut(
        &mut canvas,
        Rect::at(0, 0).of_size(DISPLAY_WIDTH, 84),
        Rgb([18, 27, 36]),
    );
    draw_filled_rect_mut(
        &mut canvas,
        Rect::at(0, 84).of_size(DISPLAY_WIDTH, DISPLAY_HEIGHT - 84),
        Rgb([8, 11, 15]),
    );

    draw_text_line(
        &mut canvas,
        Rgb([216, 253, 114]),
        28,
        20,
        36.0,
        "AOOSTAR Native Specs",
    );
    draw_text_line(
        &mut canvas,
        Rgb([146, 163, 181]),
        30,
        58,
        20.0,
        &format!("{host_name}  |  {timestamp}"),
    );

    let cards = [
        (
            24,
            106,
            "CPU",
            format!("{} %", system.cpu_usage_percent),
            format!(
                "Load {} / {} / {}",
                system.load_avg_one, system.load_avg_five, system.load_avg_fifteen
            ),
            Rgb([124, 231, 191]),
        ),
        (
            332,
            106,
            "Memory",
            format!("{} %", system.mem_usage_percent),
            format!("{} / {}", system.mem_used, system.mem_total),
            Rgb([125, 196, 255]),
        ),
        (
            640,
            106,
            "Storage",
            format!(
                "C {}%  U {}%",
                system.cache_usage_percent, system.user_usage_percent
            ),
            format!(
                "/mnt/cache {} / {}  |  /mnt/user {} / {}",
                system.cache_used, system.cache_total, system.user_used, system.user_total
            ),
            Rgb([255, 199, 115]),
        ),
        (
            24,
            236,
            "Swap",
            format!("{} %", system.swap_usage_percent),
            format!("{} / {}", system.swap_used, system.swap_total),
            Rgb([194, 167, 255]),
        ),
        (
            332,
            236,
            "Temperatures",
            format!("CPU {}", system.temperature_cpu.as_deref().unwrap_or("-")),
            format!("GPU {}", system.temperature_gpu.as_deref().unwrap_or("-")),
            Rgb([255, 127, 115]),
        ),
        (
            640,
            236,
            "System",
            system.uptime.clone(),
            format!(
                "{} cores  |  {} procs",
                system.cpu_count, system.process_count
            ),
            Rgb([216, 253, 114]),
        ),
    ];

    for (x, y, title, value, meta, accent) in cards {
        draw_metric_card(&mut canvas, x, y, title, &value, &meta, accent);
    }

    canvas
}

fn render_system_panel_meters() -> RgbImage {
    let system = collect_system_view();
    let mut canvas = RgbImage::from_pixel(DISPLAY_WIDTH, DISPLAY_HEIGHT, Rgb([9, 13, 17]));
    let host_name = System::host_name().unwrap_or_else(|| "Unraid".into());
    let timestamp = Local::now().format("%d.%m.%Y %H:%M:%S").to_string();

    draw_filled_rect_mut(
        &mut canvas,
        Rect::at(0, 0).of_size(DISPLAY_WIDTH, 72),
        Rgb([15, 23, 30]),
    );
    draw_text_line(
        &mut canvas,
        Rgb([216, 253, 114]),
        26,
        18,
        32.0,
        "AOOSTAR System Monitor",
    );
    draw_text_line(
        &mut canvas,
        Rgb([146, 163, 181]),
        28,
        48,
        18.0,
        &format!("{host_name}  |  {timestamp}"),
    );

    draw_meter_row(
        &mut canvas,
        26,
        102,
        "CPU",
        &format!("{}%", system.cpu_usage_percent),
        parse_metric_percent(&system.cpu_usage_percent),
        Rgb([110, 231, 183]),
        &format!(
            "Load {} / {} / {}   {} cores   {} procs",
            system.load_avg_one,
            system.load_avg_five,
            system.load_avg_fifteen,
            system.cpu_count,
            system.process_count
        ),
    );
    draw_meter_row(
        &mut canvas,
        26,
        170,
        "RAM",
        &format!("{}%", system.mem_usage_percent),
        parse_metric_percent(&system.mem_usage_percent),
        Rgb([120, 189, 247]),
        &format!("{} / {}", system.mem_used, system.mem_total),
    );
    draw_meter_row(
        &mut canvas,
        26,
        238,
        "GPU",
        system.temperature_gpu.as_deref().unwrap_or("-"),
        normalize_temperature_bar(system.temperature_gpu.as_deref()),
        Rgb([241, 132, 121]),
        &format!(
            "Temp meter   CPU {}   Cache {}%   User {}%",
            system.temperature_cpu.as_deref().unwrap_or("-"),
            system.cache_usage_percent,
            system.user_usage_percent
        ),
    );

    draw_filled_rect_mut(
        &mut canvas,
        Rect::at(26, 308).of_size(908, 42),
        Rgb([12, 18, 24]),
    );
    draw_hollow_rect_mut(
        &mut canvas,
        Rect::at(26, 308).of_size(908, 42),
        Rgb([31, 44, 58]),
    );
    draw_text_line(
        &mut canvas,
        Rgb([146, 163, 181]),
        42,
        321,
        18.0,
        &format!(
            "Swap {}%  {} / {}   Cache {} / {}   User {} / {}   Uptime {}",
            system.swap_usage_percent,
            system.swap_used,
            system.swap_total,
            system.cache_used,
            system.cache_total,
            system.user_used,
            system.user_total,
            system.uptime
        ),
    );

    canvas
}

pub(crate) fn overlay_system_specs(canvas: &mut RgbImage) {
    let system = collect_system_view();
    let host_name = System::host_name().unwrap_or_else(|| "Unraid".into());
    let timestamp = Local::now().format("%H:%M:%S").to_string();

    draw_filled_rect_mut(canvas, Rect::at(18, 16).of_size(924, 54), Rgb([12, 18, 24]));
    draw_hollow_rect_mut(canvas, Rect::at(18, 16).of_size(924, 54), Rgb([36, 50, 67]));
    draw_text_line(
        canvas,
        Rgb([216, 253, 114]),
        34,
        28,
        22.0,
        &format!(
            "{host_name}  CPU {}%  RAM {}%  C {}%  U {}%",
            system.cpu_usage_percent,
            system.mem_usage_percent,
            system.cache_usage_percent,
            system.user_usage_percent
        ),
    );
    draw_text_line(
        canvas,
        Rgb([146, 163, 181]),
        34,
        52,
        16.0,
        &format!(
            "Load {} / {} / {}  Temp {} / {}  Uptime {}  {}",
            system.load_avg_one,
            system.load_avg_five,
            system.load_avg_fifteen,
            system.temperature_cpu.as_deref().unwrap_or("-"),
            system.temperature_gpu.as_deref().unwrap_or("-"),
            system.uptime,
            timestamp
        ),
    );

    draw_filled_rect_mut(
        canvas,
        Rect::at(18, 306).of_size(924, 52),
        Rgb([12, 18, 24]),
    );
    draw_hollow_rect_mut(
        canvas,
        Rect::at(18, 306).of_size(924, 52),
        Rgb([36, 50, 67]),
    );
    draw_text_line(
        canvas,
        Rgb([237, 242, 247]),
        34,
        320,
        18.0,
        &format!(
            "Cache {} / {}  User {} / {}  Proc {}  CPU {}  GPU {}",
            system.cache_used,
            system.cache_total,
            system.user_used,
            system.user_total,
            system.process_count,
            system.temperature_cpu.as_deref().unwrap_or("-"),
            system.temperature_gpu.as_deref().unwrap_or("-"),
        ),
    );
}

fn draw_meter_row(
    canvas: &mut RgbImage,
    x: i32,
    y: i32,
    label: &str,
    value: &str,
    percent: f32,
    accent: Rgb<u8>,
    meta: &str,
) {
    draw_text_line(canvas, accent, x, y, 19.0, label);
    draw_text_line(canvas, Rgb([237, 242, 247]), x + 112, y, 19.0, value);
    draw_meter_bar(canvas, x, y + 28, 840, 20, percent, accent);
    draw_text_line(canvas, Rgb([146, 163, 181]), x, y + 56, 17.0, meta);
}

fn draw_meter_bar(
    canvas: &mut RgbImage,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
    percent: f32,
    accent: Rgb<u8>,
) {
    draw_filled_rect_mut(
        canvas,
        Rect::at(x, y).of_size(width, height),
        Rgb([19, 27, 35]),
    );
    draw_hollow_rect_mut(
        canvas,
        Rect::at(x, y).of_size(width, height),
        Rgb([36, 50, 67]),
    );
    let fill_width =
        ((width.saturating_sub(2)) as f32 * (percent.clamp(0.0, 100.0) / 100.0)).round() as u32;
    if fill_width > 0 {
        draw_filled_rect_mut(
            canvas,
            Rect::at(x + 1, y + 1).of_size(fill_width, height.saturating_sub(2)),
            accent,
        );
    }
}

fn parse_metric_percent(value: &str) -> f32 {
    value.parse::<f32>().unwrap_or(0.0)
}

fn normalize_temperature_bar(value: Option<&str>) -> f32 {
    let Some(raw) = value else {
        return 0.0;
    };
    let numeric = raw
        .replace(" °C", "")
        .replace('C', "")
        .trim()
        .parse::<f32>()
        .unwrap_or(0.0);
    (numeric / 100.0 * 100.0).clamp(0.0, 100.0)
}

fn draw_metric_card(
    canvas: &mut RgbImage,
    x: i32,
    y: i32,
    title: &str,
    value: &str,
    meta: &str,
    accent: Rgb<u8>,
) {
    draw_filled_rect_mut(canvas, Rect::at(x, y).of_size(284, 112), Rgb([16, 22, 30]));
    draw_hollow_rect_mut(canvas, Rect::at(x, y).of_size(284, 112), Rgb([36, 50, 67]));
    draw_filled_rect_mut(canvas, Rect::at(x + 1, y + 1).of_size(6, 110), accent);

    draw_text_line(canvas, accent, x + 20, y + 16, 19.0, title);
    draw_text_line(canvas, Rgb([237, 242, 247]), x + 20, y + 44, 28.0, value);
    draw_text_line(canvas, Rgb([146, 163, 181]), x + 20, y + 82, 18.0, meta);
}

fn draw_text_line(canvas: &mut RgbImage, color: Rgb<u8>, x: i32, y: i32, size: f32, text: &str) {
    draw_text_mut(
        canvas,
        color,
        x,
        y,
        PxScale { x: size, y: size },
        display_font(),
        text,
    );
}

fn display_font() -> &'static FontArc {
    static FONT: OnceLock<FontArc> = OnceLock::new();
    FONT.get_or_init(|| {
        FontArc::try_from_slice(include_bytes!("../../../fonts/DejaVuSans.ttf"))
            .expect("embedded display font must be valid")
    })
}
