//! Stats screen view — database and index statistics.
//!
//! Renders entity counts, FTS/embedding coverage, and queue health
//! as a simple table layout.

use ftui::core::geometry::Rect;
use ftui::render::cell::{Cell, PackedRgba};
use ftui::render::drawing::Draw;
use ftui::render::frame::Frame;

use crate::state::stats::StatsState;

use super::{ACCENT, TEXT, TEXT_MUTED};

/// Success green (for good coverage).
const GOOD_FG: PackedRgba = PackedRgba::rgb(0x87, 0x9A, 0x39);
/// Warning yellow (for partial coverage).
const WARN_FG: PackedRgba = PackedRgba::rgb(0xD0, 0xA2, 0x15);

// ---------------------------------------------------------------------------
// Public entry point
// ---------------------------------------------------------------------------

/// Render the stats screen.
pub fn render_stats(frame: &mut Frame<'_>, state: &StatsState, area: Rect) {
    if area.width < 10 || area.height < 3 {
        return;
    }

    let max_x = area.right();

    if !state.loaded {
        let msg = "Loading statistics...";
        let x = area.x + area.width.saturating_sub(msg.len() as u16) / 2;
        let y = area.y + area.height / 2;
        frame.print_text_clipped(
            x,
            y,
            msg,
            Cell {
                fg: TEXT_MUTED,
                ..Cell::default()
            },
            max_x,
        );
        return;
    }

    let data = match &state.data {
        Some(d) => d,
        None => return,
    };

    // Title.
    frame.print_text_clipped(
        area.x + 2,
        area.y + 1,
        "Database Statistics",
        Cell {
            fg: ACCENT,
            ..Cell::default()
        },
        max_x,
    );

    let mut y = area.y + 3;
    let label_width = 22u16;
    let value_x = area.x + 2 + label_width;

    // --- Entity Counts section ---
    if y < area.bottom().saturating_sub(2) {
        frame.print_text_clipped(
            area.x + 2,
            y,
            "Entities",
            Cell {
                fg: TEXT,
                ..Cell::default()
            },
            max_x,
        );
        y += 1;
    }

    let entity_rows: [(&str, i64); 4] = [
        ("  Issues", data.issues),
        ("  Merge Requests", data.merge_requests),
        ("  Discussions", data.discussions),
        ("  Notes", data.notes),
    ];

    for (label, count) in &entity_rows {
        if y >= area.bottom().saturating_sub(2) {
            break;
        }
        render_stat_row(
            frame,
            area.x + 2,
            y,
            label,
            &format_count(*count),
            label_width,
            max_x,
        );
        y += 1;
    }

    // Total.
    if y < area.bottom().saturating_sub(2) {
        let total = data.issues + data.merge_requests + data.discussions + data.notes;
        render_stat_row(
            frame,
            area.x + 2,
            y,
            "  Total",
            &format_count(total),
            label_width,
            max_x,
        );
        y += 1;
    }

    y += 1; // Blank line.

    // --- Index Coverage section ---
    if y < area.bottom().saturating_sub(2) {
        frame.print_text_clipped(
            area.x + 2,
            y,
            "Index Coverage",
            Cell {
                fg: TEXT,
                ..Cell::default()
            },
            max_x,
        );
        y += 1;
    }

    // FTS.
    if y < area.bottom().saturating_sub(2) {
        let fts_pct = data.fts_coverage_pct();
        let fts_text = format!("{} ({:.0}%)", format_count(data.fts_indexed), fts_pct);
        let fg = coverage_color(fts_pct);
        frame.print_text_clipped(
            area.x + 2,
            y,
            &format!("{:<width$}", "  FTS Indexed", width = label_width as usize),
            Cell {
                fg: TEXT_MUTED,
                ..Cell::default()
            },
            value_x,
        );
        frame.print_text_clipped(
            value_x,
            y,
            &fts_text,
            Cell {
                fg,
                ..Cell::default()
            },
            max_x,
        );
        y += 1;
    }

    // Embeddings.
    if y < area.bottom().saturating_sub(2) {
        let embed_text = format!(
            "{} ({:.0}%)",
            format_count(data.embedded_documents),
            data.coverage_pct
        );
        let fg = coverage_color(data.coverage_pct);
        frame.print_text_clipped(
            area.x + 2,
            y,
            &format!("{:<width$}", "  Embeddings", width = label_width as usize),
            Cell {
                fg: TEXT_MUTED,
                ..Cell::default()
            },
            value_x,
        );
        frame.print_text_clipped(
            value_x,
            y,
            &embed_text,
            Cell {
                fg,
                ..Cell::default()
            },
            max_x,
        );
        y += 1;
    }

    // Chunks.
    if y < area.bottom().saturating_sub(2) {
        render_stat_row(
            frame,
            area.x + 2,
            y,
            "  Chunks",
            &format_count(data.total_chunks),
            label_width,
            max_x,
        );
        y += 1;
    }

    y += 1; // Blank line.

    // --- Queue section ---
    if data.has_queue_work() && y < area.bottom().saturating_sub(2) {
        frame.print_text_clipped(
            area.x + 2,
            y,
            "Queue",
            Cell {
                fg: TEXT,
                ..Cell::default()
            },
            max_x,
        );
        y += 1;

        if y < area.bottom().saturating_sub(2) {
            render_stat_row(
                frame,
                area.x + 2,
                y,
                "  Pending",
                &format_count(data.queue_pending),
                label_width,
                max_x,
            );
            y += 1;
        }

        if data.queue_failed > 0 && y < area.bottom().saturating_sub(2) {
            let failed_cell = Cell {
                fg: WARN_FG,
                ..Cell::default()
            };
            frame.print_text_clipped(
                area.x + 2,
                y,
                &format!("{:<width$}", "  Failed", width = label_width as usize),
                Cell {
                    fg: TEXT_MUTED,
                    ..Cell::default()
                },
                value_x,
            );
            frame.print_text_clipped(
                value_x,
                y,
                &format_count(data.queue_failed),
                failed_cell,
                max_x,
            );
        }
    }

    // Hint at bottom.
    let hint_y = area.bottom().saturating_sub(1);
    frame.print_text_clipped(
        area.x + 2,
        hint_y,
        "Esc: back  |  lore stats (full report)",
        Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        },
        max_x,
    );
}

/// Render a label + value row.
fn render_stat_row(
    frame: &mut Frame<'_>,
    x: u16,
    y: u16,
    label: &str,
    value: &str,
    label_width: u16,
    max_x: u16,
) {
    let value_x = x + label_width;
    frame.print_text_clipped(
        x,
        y,
        &format!("{label:<width$}", width = label_width as usize),
        Cell {
            fg: TEXT_MUTED,
            ..Cell::default()
        },
        value_x,
    );
    frame.print_text_clipped(
        value_x,
        y,
        value,
        Cell {
            fg: TEXT,
            ..Cell::default()
        },
        max_x,
    );
}

/// Color based on coverage percentage.
fn coverage_color(pct: f64) -> PackedRgba {
    if pct >= 90.0 {
        GOOD_FG
    } else if pct >= 50.0 {
        WARN_FG
    } else {
        TEXT
    }
}

/// Format a count with comma separators for readability.
fn format_count(n: i64) -> String {
    if n < 1_000 {
        return n.to_string();
    }
    let s = n.to_string();
    let mut result = String::with_capacity(s.len() + s.len() / 3);
    for (i, c) in s.chars().enumerate() {
        if i > 0 && (s.len() - i).is_multiple_of(3) {
            result.push(',');
        }
        result.push(c);
    }
    result
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::state::stats::StatsData;
    use ftui::render::grapheme_pool::GraphemePool;

    macro_rules! with_frame {
        ($width:expr, $height:expr, |$frame:ident| $body:block) => {{
            let mut pool = GraphemePool::new();
            let mut $frame = Frame::new($width, $height, &mut pool);
            $body
        }};
    }

    fn sample_data() -> StatsData {
        StatsData {
            total_documents: 500,
            issues: 200,
            merge_requests: 150,
            discussions: 100,
            notes: 50,
            fts_indexed: 450,
            embedded_documents: 300,
            total_chunks: 1200,
            coverage_pct: 60.0,
            queue_pending: 5,
            queue_failed: 1,
        }
    }

    #[test]
    fn test_render_not_loaded() {
        with_frame!(80, 24, |frame| {
            let state = StatsState::default();
            let area = frame.bounds();
            render_stats(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_with_data() {
        with_frame!(80, 24, |frame| {
            let mut state = StatsState::default();
            state.apply_data(sample_data());
            let area = frame.bounds();
            render_stats(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_no_queue_work() {
        with_frame!(80, 24, |frame| {
            let mut state = StatsState::default();
            state.apply_data(StatsData {
                queue_pending: 0,
                queue_failed: 0,
                ..sample_data()
            });
            let area = frame.bounds();
            render_stats(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_tiny_terminal() {
        with_frame!(8, 2, |frame| {
            let mut state = StatsState::default();
            state.apply_data(sample_data());
            let area = frame.bounds();
            render_stats(&mut frame, &state, area);
        });
    }

    #[test]
    fn test_render_short_terminal() {
        with_frame!(80, 8, |frame| {
            let mut state = StatsState::default();
            state.apply_data(sample_data());
            let area = frame.bounds();
            render_stats(&mut frame, &state, area);
            // Should clip without panicking.
        });
    }

    #[test]
    fn test_format_count_small() {
        assert_eq!(format_count(0), "0");
        assert_eq!(format_count(42), "42");
        assert_eq!(format_count(999), "999");
    }

    #[test]
    fn test_format_count_thousands() {
        assert_eq!(format_count(1_000), "1,000");
        assert_eq!(format_count(12_345), "12,345");
        assert_eq!(format_count(1_234_567), "1,234,567");
    }

    #[test]
    fn test_coverage_color_thresholds() {
        assert_eq!(coverage_color(100.0), GOOD_FG);
        assert_eq!(coverage_color(90.0), GOOD_FG);
        assert_eq!(coverage_color(89.9), WARN_FG);
        assert_eq!(coverage_color(50.0), WARN_FG);
        assert_eq!(coverage_color(49.9), TEXT);
    }
}
