speedtest-tui 0.1.1

A terminal-based network speed test tool with real-time gauges and graphs
use ratatui::{
    layout::{Constraint, Rect},
    prelude::*,
    widgets::{Block, Borders, Paragraph, Row, Table, TableState},
    Frame,
};

use super::Theme;
use crate::app::App;

/// Render the history view
pub fn render(frame: &mut Frame, area: Rect, app: &App, theme: &Theme) {
    let block = Block::default()
        .title(" Test History ")
        .title_style(Style::default().fg(theme.primary))
        .borders(Borders::ALL)
        .border_style(Style::default().fg(theme.border))
        .style(Style::default().bg(theme.background));

    let inner = block.inner(area);
    frame.render_widget(block, area);

    if app.history.is_empty() {
        let placeholder = Paragraph::new("  No test history found. Run a test to get started!")
            .style(Style::default().fg(theme.muted));
        frame.render_widget(placeholder, inner);
        return;
    }

    // Create header row
    let header = Row::new(vec![
        "Date", "Provider", "Download", "Upload", "Ping", "Location",
    ])
    .style(Style::default().fg(theme.primary).bold())
    .bottom_margin(1);

    // Create data rows
    let rows: Vec<Row> = app
        .history
        .iter()
        .rev()
        .take(50) // Show last 50 entries
        .map(|entry| {
            let date = entry.timestamp.format("%m/%d %H:%M").to_string();

            let download = entry
                .download
                .as_ref()
                .map(|d| format!("{:.1} Mbps", d.speed_mbps))
                .unwrap_or_else(|| "-".to_string());

            let upload = entry
                .upload
                .as_ref()
                .map(|u| format!("{:.1} Mbps", u.speed_mbps))
                .unwrap_or_else(|| "-".to_string());

            let ping = entry
                .ping
                .as_ref()
                .map(|p| format!("{:.0} ms", p.latency_ms))
                .unwrap_or_else(|| "-".to_string());

            let location = entry
                .connection_info
                .as_ref()
                .and_then(|c| c.location.clone())
                .unwrap_or_else(|| "-".to_string());

            Row::new(vec![
                date,
                entry.provider.clone(),
                download,
                upload,
                ping,
                location,
            ])
            .style(Style::default().fg(theme.foreground))
        })
        .collect();

    let table = Table::new(
        rows,
        [
            Constraint::Length(12), // Date
            Constraint::Length(12), // Provider
            Constraint::Length(12), // Download
            Constraint::Length(12), // Upload
            Constraint::Length(8),  // Ping
            Constraint::Min(15),    // Location
        ],
    )
    .header(header)
    .row_highlight_style(
        Style::default()
            .bg(theme.primary)
            .fg(theme.background)
            .bold(),
    )
    .highlight_symbol("â–¶ ");

    // Render with state for scrolling
    let mut state = TableState::default();
    state.select(Some(app.history_selected_index));

    frame.render_stateful_widget(table, inner, &mut state);

    // Render footer with instructions
    let instructions =
        Paragraph::new("[↑/↓] Navigate  [Enter] View Details  [e] Export  [Esc] Back")
            .style(Style::default().fg(theme.muted))
            .alignment(Alignment::Center);

    let footer_area = Rect {
        x: area.x,
        y: area.y + area.height - 2,
        width: area.width,
        height: 1,
    };
    frame.render_widget(instructions, footer_area);
}

#[allow(dead_code)]
pub fn render_detail(frame: &mut Frame, area: Rect, app: &App, theme: &Theme) {
    let entry = match app.history.get(app.history_selected_index) {
        Some(e) => e,
        None => return,
    };

    let block = Block::default()
        .title(" Test Details ")
        .title_style(Style::default().fg(theme.primary))
        .borders(Borders::ALL)
        .border_style(Style::default().fg(theme.border))
        .style(Style::default().bg(theme.background));

    let inner = block.inner(area);
    frame.render_widget(block, area);

    let mut lines = vec![
        Line::from(vec![
            Span::styled("  Date:     ", Style::default().fg(theme.muted)),
            Span::styled(
                entry.timestamp.format("%Y-%m-%d %H:%M:%S").to_string(),
                Style::default().fg(theme.foreground),
            ),
        ]),
        Line::from(vec![
            Span::styled("  Provider: ", Style::default().fg(theme.muted)),
            Span::styled(&entry.provider, Style::default().fg(theme.foreground)),
        ]),
        Line::from(""),
    ];

    // Ping details
    if let Some(ref ping) = entry.ping {
        lines.push(Line::from(vec![Span::styled(
            "  Ping:",
            Style::default().fg(theme.secondary).bold(),
        )]));
        lines.push(Line::from(vec![
            Span::styled("    Latency:     ", Style::default().fg(theme.muted)),
            Span::styled(
                format!("{:.2} ms", ping.latency_ms),
                Style::default().fg(theme.foreground),
            ),
        ]));
        lines.push(Line::from(vec![
            Span::styled("    Jitter:      ", Style::default().fg(theme.muted)),
            Span::styled(
                format!("{:.2} ms", ping.jitter_ms),
                Style::default().fg(theme.foreground),
            ),
        ]));
        lines.push(Line::from(vec![
            Span::styled("    Packet Loss: ", Style::default().fg(theme.muted)),
            Span::styled(
                format!("{:.1}%", ping.packet_loss_percent),
                Style::default().fg(theme.foreground),
            ),
        ]));
        lines.push(Line::from(""));
    }

    // Download details
    if let Some(ref download) = entry.download {
        lines.push(Line::from(vec![Span::styled(
            "  Download:",
            Style::default().fg(theme.success).bold(),
        )]));
        lines.push(Line::from(vec![
            Span::styled("    Speed:    ", Style::default().fg(theme.muted)),
            Span::styled(
                format!("{:.2} Mbps", download.speed_mbps),
                Style::default().fg(theme.foreground),
            ),
        ]));
        lines.push(Line::from(vec![
            Span::styled("    Peak:     ", Style::default().fg(theme.muted)),
            Span::styled(
                format!("{:.2} Mbps", download.peak_speed_mbps),
                Style::default().fg(theme.foreground),
            ),
        ]));
        lines.push(Line::from(vec![
            Span::styled("    Data:     ", Style::default().fg(theme.muted)),
            Span::styled(
                crate::utils::format_bytes(download.bytes_transferred),
                Style::default().fg(theme.foreground),
            ),
        ]));
        lines.push(Line::from(""));
    }

    // Upload details
    if let Some(ref upload) = entry.upload {
        lines.push(Line::from(vec![Span::styled(
            "  Upload:",
            Style::default().fg(theme.secondary).bold(),
        )]));
        lines.push(Line::from(vec![
            Span::styled("    Speed:    ", Style::default().fg(theme.muted)),
            Span::styled(
                format!("{:.2} Mbps", upload.speed_mbps),
                Style::default().fg(theme.foreground),
            ),
        ]));
        lines.push(Line::from(vec![
            Span::styled("    Peak:     ", Style::default().fg(theme.muted)),
            Span::styled(
                format!("{:.2} Mbps", upload.peak_speed_mbps),
                Style::default().fg(theme.foreground),
            ),
        ]));
        lines.push(Line::from(vec![
            Span::styled("    Data:     ", Style::default().fg(theme.muted)),
            Span::styled(
                crate::utils::format_bytes(upload.bytes_transferred),
                Style::default().fg(theme.foreground),
            ),
        ]));
    }

    let paragraph = Paragraph::new(lines);
    frame.render_widget(paragraph, inner);
}

#[cfg(test)]
mod tests {
    // UI rendering tests would need a test backend
    #[test]
    fn test_module_compiles() {
        assert!(true);
    }
}