use crate::serial_core::frame::TestFrame;
use std::collections::HashMap;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

const MAX_POINTS: usize = 28;
const MAX_SUMMARY_POINTS: usize = 42;
const PANEL_STALE_AFTER: Duration = Duration::from_millis(2400);

#[derive(serde::Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct HudPacket {
    pub ts: u64,
    pub panels: Vec<HudSignalPanel>,
    pub summary: HudSummary,
    pub pressure_matrix: Option<Vec<f32>>,
}

#[derive(serde::Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct HudSummary {
    pub label: String,
    pub points: Vec<f32>,
    pub latest: Option<f32>,
    pub min: Option<f32>,
    pub max: Option<f32>,
}

#[derive(serde::Serialize, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum HudPanelSide {
    Left,
    Right,
}

#[derive(serde::Serialize, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum HudTone {
    Cyan,
    Lime,
    Orange,
    Violet,
    Gold,
    Rose,
}

#[derive(serde::Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct HudSignalPanel {
    pub id: String,
    pub code: String,
    pub title: String,
    pub side: HudPanelSide,
    pub active: bool,
    pub series: Vec<HudSignalSeries>,
    pub icons: Vec<HudSignalIcon>,
    pub latest: Option<f32>,
    pub min: Option<f32>,
    pub max: Option<f32>,
}

#[derive(serde::Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct HudSignalSeries {
    pub id: String,
    pub tone: HudTone,
    pub points: Vec<f32>,
}

#[derive(serde::Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct HudSignalIcon {
    pub id: String,
    pub label: String,
    pub tone: HudTone,
}

struct HudPanelUpdate {
    source_id: String,
    values: Vec<f32>,
}

struct PanelEntry {
    panel: HudSignalPanel,
    last_seen: Instant,
}

pub struct HudChartState {
    panels: HashMap<String, PanelEntry>,
    order: Vec<String>,
    summary_points: Vec<f32>,
    pressure_matrix: Option<Vec<f32>>,
    last_frame_seen: Option<Instant>,
}

impl HudChartState {
    pub fn new() -> Self {
        Self {
            panels: HashMap::new(),
            order: Vec::new(),
            summary_points: Vec::new(),
            pressure_matrix: None,
            last_frame_seen: None,
        }
    }

    pub fn record_summary(&mut self, value: f32) {
        push_summary_point(&mut self.summary_points, value);
    }

    pub fn record_pressure_matrix(&mut self, values: &[i32]) {
        if values.is_empty() {
            return;
        }

        self.pressure_matrix = Some(values.iter().map(|value| *value as f32).collect());
    }

    pub fn apply_frame(&mut self, frame: &TestFrame, decoded_values: Option<&[i32]>) -> HudPacket {
        let now = Instant::now();
        self.last_frame_seen = Some(now);

        for update in expand_frame_updates(frame, decoded_values) {
            self.apply_update(update, now);
        }

        self.prune_stale_at(now);
        self.snapshot()
    }

    pub fn prune_stale(&mut self) -> Option<HudPacket> {
        let before = self.panels.len();
        let summary_points_before = self.summary_points.len();
        self.prune_stale_at(Instant::now());

        if before == self.panels.len() && summary_points_before == self.summary_points.len() {
            return None;
        }

        Some(self.snapshot())
    }

    fn apply_update(&mut self, update: HudPanelUpdate, now: Instant) {
        if update.values.is_empty() {
            return;
        }

        if !self.panels.contains_key(&update.source_id) {
            let next_side = side_for_index(self.order.len());
            self.order.push(update.source_id.clone());
            self.panels.insert(
                update.source_id.clone(),
                PanelEntry {
                    panel: build_panel(&update.source_id, next_side, update.values.len()),
                    last_seen: now,
                },
            );
        }

        let entry = self
            .panels
            .get_mut(&update.source_id)
            .expect("panel entry should exist after insertion");

        entry.last_seen = now;
        entry.panel.active = true;
        ensure_panel_channels(&mut entry.panel, update.values.len());

        for (index, value) in update.values.into_iter().enumerate() {
            if let Some(series) = entry.panel.series.get_mut(index) {
                push_point(&mut series.points, value);
            }
        }

        refresh_panel_stats(&mut entry.panel);
    }

    fn prune_stale_at(&mut self, now: Instant) {
        self.panels
            .retain(|_, entry| now.duration_since(entry.last_seen) <= PANEL_STALE_AFTER);
        self.order.retain(|id| self.panels.contains_key(id));

        let summary_stale = self
            .last_frame_seen
            .map(|last_seen| now.duration_since(last_seen) > PANEL_STALE_AFTER)
            .unwrap_or(false);

        if summary_stale {
            self.summary_points.clear();
            self.pressure_matrix = None;
            self.last_frame_seen = None;
        }
    }

    fn snapshot(&mut self) -> HudPacket {
        self.rebalance_sides();

        let panels = self
            .order
            .iter()
            .filter_map(|id| self.panels.get(id).map(|entry| entry.panel.clone()))
            .collect();

        HudPacket {
            ts: now_millis(),
            panels,
            summary: build_summary(&self.summary_points),
            pressure_matrix: self.pressure_matrix.clone(),
        }
    }

    fn rebalance_sides(&mut self) {
        for (index, id) in self.order.iter().enumerate() {
            if let Some(entry) = self.panels.get_mut(id) {
                entry.panel.side = side_for_index(index);
            }
        }
    }
}

impl Default for HudChartState {
    fn default() -> Self {
        Self::new()
    }
}

fn build_panel(source_id: &str, side: HudPanelSide, channel_count: usize) -> HudSignalPanel {
    HudSignalPanel {
        id: format!("panel-{source_id}"),
        code: source_id.to_string(),
        title: format!("Source {source_id}"),
        side,
        active: true,
        series: build_panel_series(source_id, channel_count, &[]),
        icons: build_panel_icons(source_id, channel_count),
        latest: None,
        min: None,
        max: None,
    }
}

fn expand_frame_updates(frame: &TestFrame, decoded_values: Option<&[i32]>) -> Vec<HudPanelUpdate> {
    if let Some(values) = decoded_values {
        if values.is_empty() {
            return Vec::new();
        }

        return vec![HudPanelUpdate {
            source_id: format_source_id(frame.cmd),
            values: values.iter().map(|value| *value as f32).collect(),
        }];
    }

    let chunks = frame.payload.chunks_exact(4);

    if !frame.payload.is_empty() && chunks.remainder().is_empty() {
        return chunks.map(build_update_from_chunk).collect();
    }

    vec![HudPanelUpdate {
        source_id: format_source_id(frame.cmd),
        values: fallback_values(frame),
    }]
}

fn build_update_from_chunk(chunk: &[u8]) -> HudPanelUpdate {
    HudPanelUpdate {
        source_id: format_source_id(chunk[0]),
        values: chunk[1..]
            .iter()
            .enumerate()
            .map(|(index, byte)| normalize_value(*byte, tone_for_index(index)))
            .collect(),
    }
}

fn fallback_values(frame: &TestFrame) -> Vec<f32> {
    let mut bytes = frame.payload.clone();

    if bytes.is_empty() {
        bytes.extend([
            frame.cmd,
            frame.length as u8,
            frame.checksum,
            frame.cmd.wrapping_add(frame.checksum),
        ]);
    }

    while bytes.len() < 3 {
        let previous = *bytes.last().unwrap_or(&frame.cmd);
        bytes.push(
            previous
                .wrapping_add(frame.cmd)
                .wrapping_add(bytes.len() as u8),
        );
    }

    bytes
        .into_iter()
        .enumerate()
        .map(|(index, byte)| normalize_value(byte, tone_for_index(index)))
        .collect()
}

fn normalize_value(byte: u8, tone: HudTone) -> f32 {
    let base = (byte as f32 / 255.0) * 100.0;
    let offset = match tone {
        HudTone::Cyan => 6.0,
        HudTone::Lime => 0.0,
        HudTone::Orange => -6.0,
        HudTone::Violet => 10.0,
        HudTone::Gold => -10.0,
        HudTone::Rose => 3.0,
    };

    (base + offset).clamp(0.0, 100.0)
}

fn format_source_id(byte: u8) -> String {
    if byte.is_ascii_alphanumeric() {
        (byte as char).to_ascii_uppercase().to_string()
    } else {
        format!("CH{:02X}", byte)
    }
}

fn side_for_index(index: usize) -> HudPanelSide {
    if index % 2 == 0 {
        HudPanelSide::Left
    } else {
        HudPanelSide::Right
    }
}

fn push_point(points: &mut Vec<f32>, value: f32) {
    if points.len() >= MAX_POINTS {
        points.remove(0);
    }

    points.push((value * 10.0).round() / 10.0);
}

fn build_panel_series(
    source_id: &str,
    channel_count: usize,
    previous: &[HudSignalSeries],
) -> Vec<HudSignalSeries> {
    (0..channel_count)
        .map(|index| HudSignalSeries {
            id: format!("{source_id}-series-{}", index + 1),
            tone: tone_for_index(index),
            points: previous
                .get(index)
                .map(|series| series.points.clone())
                .unwrap_or_default(),
        })
        .collect()
}

fn build_panel_icons(source_id: &str, channel_count: usize) -> Vec<HudSignalIcon> {
    (0..channel_count)
        .map(|index| HudSignalIcon {
            id: format!("{source_id}-icon-{}", index + 1),
            label: if channel_count == 1 {
                "TOTAL".to_string()
            } else {
                format!("{source_id}-{}", index + 1)
            },
            tone: tone_for_index(index),
        })
        .collect()
}

fn ensure_panel_channels(panel: &mut HudSignalPanel, channel_count: usize) {
    if panel.series.len() == channel_count && panel.icons.len() == channel_count {
        return;
    }

    panel.series = build_panel_series(&panel.code, channel_count, &panel.series);
    panel.icons = build_panel_icons(&panel.code, channel_count);
}

fn refresh_panel_stats(panel: &mut HudSignalPanel) {
    let latest_values: Vec<f32> = panel
        .series
        .iter()
        .filter_map(|series| series.points.last().copied())
        .collect();

    panel.latest = if latest_values.is_empty() {
        None
    } else {
        Some(latest_values.iter().sum::<f32>() / latest_values.len() as f32)
    };

    panel.min = panel
        .series
        .iter()
        .flat_map(|series| series.points.iter().copied())
        .reduce(f32::min);

    panel.max = panel
        .series
        .iter()
        .flat_map(|series| series.points.iter().copied())
        .reduce(f32::max);
}

fn tone_for_index(index: usize) -> HudTone {
    match index % 6 {
        0 => HudTone::Cyan,
        1 => HudTone::Lime,
        2 => HudTone::Orange,
        3 => HudTone::Violet,
        4 => HudTone::Gold,
        _ => HudTone::Rose,
    }
}

fn push_summary_point(points: &mut Vec<f32>, value: f32) {
    if points.len() >= MAX_SUMMARY_POINTS {
        points.remove(0);
    }

    points.push((value * 10.0).round() / 10.0);
}

fn build_summary(points: &[f32]) -> HudSummary {
    HudSummary {
        label: "TOTAL".to_string(),
        points: points.to_vec(),
        latest: points.last().copied(),
        min: points.iter().copied().reduce(f32::min),
        max: points.iter().copied().reduce(f32::max),
    }
}

fn now_millis() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_millis() as u64)
        .unwrap_or_default()
}

// #[cfg(test)]
// mod tests {
//     use super::*;
//
//     fn sample_frame() -> TestFrame {
//         TestFrame {
//             header: [0xAA, 0x55],
//             cmd: 0x01,
//             length: 4,
//             payload: vec![0x00, 0x0A, 0x00, 0x14],
//             checksum: 0,
//
//         }
//     }
//
//     #[test]
//     fn prune_stale_clears_panels_and_summary_after_timeout() {
//         let mut state = HudChartState::new();
//         let frame = sample_frame();
//
//         state.record_summary(30.0);
//         let _ = state.apply_frame(&frame, Some(&[10, 20]));
//
//         let stale_now = Instant::now();
//         let stale_seen = stale_now - PANEL_STALE_AFTER - Duration::from_millis(1);
//
//         state.last_frame_seen = Some(stale_seen);
//
//         for entry in state.panels.values_mut() {
//             entry.last_seen = stale_seen;
//         }
//
//         let packet = state
//             .prune_stale()
//             .expect("stale data should emit an update");
//
//         assert!(packet.panels.is_empty());
//         assert!(packet.summary.points.is_empty());
//         assert!(state.panels.is_empty());
//         assert!(state.summary_points.is_empty());
//     }
//
//     #[test]
//     fn prune_stale_keeps_recent_summary_points() {
//         let mut state = HudChartState::new();
//         let frame = sample_frame();
//
//         state.record_summary(30.0);
//         let _ = state.apply_frame(&frame, Some(&[10, 20]));
//
//         state.last_frame_seen = Some(Instant::now());
//
//         assert!(state.prune_stale().is_none());
//         assert_eq!(state.summary_points, vec![30.0]);
//         assert_eq!(state.panels.len(), 1);
//     }
// }
