//! `batty metrics` — consolidated telemetry dashboard.
//!
//! Queries the SQLite telemetry database and prints a single-screen summary
//! covering session totals, per-agent performance, cycle-time statistics,
//! and review pipeline health. Handles missing/empty databases gracefully.

use std::path::Path;

use anyhow::{Context, Result};
use rusqlite::Connection;

use super::telemetry_db;

/// Aggregated dashboard metrics produced by [`query_dashboard`].
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DashboardMetrics {
    // Session totals
    pub total_tasks_completed: i64,
    pub total_merges: i64,
    pub total_events: i64,
    pub sessions_count: i64,

    // Cycle time
    pub avg_cycle_time_secs: Option<f64>,
    pub min_cycle_time_secs: Option<i64>,
    pub max_cycle_time_secs: Option<i64>,

    // Rates
    pub completion_rate: Option<f64>,
    pub failure_rate: Option<f64>,
    pub merge_success_rate: Option<f64>,

    // Review pipeline
    pub auto_merge_count: i64,
    pub manual_merge_count: i64,
    pub auto_merge_rate: Option<f64>,
    pub rework_count: i64,
    pub rework_rate: Option<f64>,
    pub avg_review_latency_secs: Option<f64>,

    // Per-agent breakdown
    pub agent_rows: Vec<AgentRow>,
}

/// Per-agent row in the dashboard.
#[derive(Debug, Clone, PartialEq)]
pub struct AgentRow {
    pub role: String,
    pub completions: i64,
    pub failures: i64,
    pub restarts: i64,
    pub total_cycle_secs: i64,
    pub idle_pct: Option<f64>,
}

/// Query the telemetry database and build the aggregated dashboard.
pub fn query_dashboard(conn: &Connection) -> Result<DashboardMetrics> {
    let mut m = DashboardMetrics::default();

    // Session totals
    let sessions = telemetry_db::query_session_summaries(conn)?;
    m.sessions_count = sessions.len() as i64;
    for s in &sessions {
        m.total_tasks_completed += s.tasks_completed;
        m.total_merges += s.total_merges;
        m.total_events += s.total_events;
    }

    // Per-agent metrics
    let agents = telemetry_db::query_agent_metrics(conn)?;
    let mut total_completions: i64 = 0;
    let mut total_failures: i64 = 0;
    for a in &agents {
        total_completions += a.completions;
        total_failures += a.failures;
        let total_polls = a.idle_polls + a.working_polls;
        let idle_pct = if total_polls > 0 {
            Some(a.idle_polls as f64 / total_polls as f64 * 100.0)
        } else {
            None
        };
        m.agent_rows.push(AgentRow {
            role: a.role.clone(),
            completions: a.completions,
            failures: a.failures,
            restarts: a.restarts,
            total_cycle_secs: a.total_cycle_secs,
            idle_pct,
        });
    }

    // Rates
    let total_outcomes = total_completions + total_failures;
    if total_outcomes > 0 {
        m.completion_rate = Some(total_completions as f64 / total_outcomes as f64 * 100.0);
        m.failure_rate = Some(total_failures as f64 / total_outcomes as f64 * 100.0);
    }
    if m.total_tasks_completed > 0 {
        m.merge_success_rate = Some(m.total_merges as f64 / m.total_tasks_completed as f64 * 100.0);
    }

    // Cycle time from task_metrics
    let tasks = telemetry_db::query_task_metrics(conn)?;
    let cycle_times: Vec<i64> = tasks
        .iter()
        .filter_map(|t| match (t.started_at, t.completed_at) {
            (Some(s), Some(c)) if c > s => Some(c - s),
            _ => None,
        })
        .collect();
    if !cycle_times.is_empty() {
        let sum: i64 = cycle_times.iter().sum();
        m.avg_cycle_time_secs = Some(sum as f64 / cycle_times.len() as f64);
        m.min_cycle_time_secs = cycle_times.iter().copied().min();
        m.max_cycle_time_secs = cycle_times.iter().copied().max();
    }

    // Review pipeline
    let review = telemetry_db::query_review_metrics(conn)?;
    m.auto_merge_count = review.auto_merge_count;
    m.manual_merge_count = review.manual_merge_count;
    m.rework_count = review.rework_count;
    m.avg_review_latency_secs = review.avg_review_latency_secs;

    let total_merge = m.auto_merge_count + m.manual_merge_count;
    if total_merge > 0 {
        m.auto_merge_rate = Some(m.auto_merge_count as f64 / total_merge as f64 * 100.0);
    }
    let total_reviewed = total_merge + m.rework_count;
    if total_reviewed > 0 {
        m.rework_rate = Some(m.rework_count as f64 / total_reviewed as f64 * 100.0);
    }

    Ok(m)
}

/// Format a duration in seconds as a human-readable string.
fn format_duration(secs: f64) -> String {
    if secs < 60.0 {
        format!("{:.0}s", secs)
    } else if secs < 3600.0 {
        let m = (secs / 60.0).floor();
        let s = secs - m * 60.0;
        format!("{:.0}m {:.0}s", m, s)
    } else {
        let h = (secs / 3600.0).floor();
        let rem = secs - h * 3600.0;
        let m = (rem / 60.0).floor();
        format!("{:.0}h {:.0}m", h, m)
    }
}

/// Format the dashboard for terminal display.
pub fn format_dashboard(m: &DashboardMetrics) -> String {
    let mut out = String::new();
    let na = "n/a".to_string();

    // Header
    out.push_str("Telemetry Dashboard\n");
    out.push_str(&"=".repeat(60));
    out.push('\n');

    // Session totals
    out.push_str("\nSession Totals\n");
    out.push_str(&"-".repeat(40));
    out.push('\n');
    out.push_str(&format!("  Sessions:        {}\n", m.sessions_count));
    out.push_str(&format!("  Tasks Completed: {}\n", m.total_tasks_completed));
    out.push_str(&format!("  Total Merges:    {}\n", m.total_merges));
    out.push_str(&format!("  Total Events:    {}\n", m.total_events));

    // Cycle time
    out.push_str("\nCycle Time\n");
    out.push_str(&"-".repeat(40));
    out.push('\n');
    let avg = m
        .avg_cycle_time_secs
        .map(format_duration)
        .unwrap_or_else(|| na.clone());
    let min = m
        .min_cycle_time_secs
        .map(|s| format_duration(s as f64))
        .unwrap_or_else(|| na.clone());
    let max = m
        .max_cycle_time_secs
        .map(|s| format_duration(s as f64))
        .unwrap_or_else(|| na.clone());
    out.push_str(&format!("  Average: {}\n", avg));
    out.push_str(&format!("  Min:     {}\n", min));
    out.push_str(&format!("  Max:     {}\n", max));

    // Rates
    out.push_str("\nRates\n");
    out.push_str(&"-".repeat(40));
    out.push('\n');
    let cr = m
        .completion_rate
        .map(|r| format!("{:.0}%", r))
        .unwrap_or_else(|| na.clone());
    let fr = m
        .failure_rate
        .map(|r| format!("{:.0}%", r))
        .unwrap_or_else(|| na.clone());
    let mr = m
        .merge_success_rate
        .map(|r| format!("{:.0}%", r))
        .unwrap_or_else(|| na.clone());
    out.push_str(&format!("  Completion Rate:    {}\n", cr));
    out.push_str(&format!("  Failure Rate:       {}\n", fr));
    out.push_str(&format!("  Merge Success Rate: {}\n", mr));

    // Review pipeline
    out.push_str("\nReview Pipeline\n");
    out.push_str(&"-".repeat(40));
    out.push('\n');
    let amr = m
        .auto_merge_rate
        .map(|r| format!("{:.0}%", r))
        .unwrap_or_else(|| na.clone());
    let rr = m
        .rework_rate
        .map(|r| format!("{:.0}%", r))
        .unwrap_or_else(|| na.clone());
    let latency = m
        .avg_review_latency_secs
        .map(format_duration)
        .unwrap_or_else(|| na.clone());
    out.push_str(&format!("  Auto-merge Rate: {}\n", amr));
    out.push_str(&format!(
        "  Auto: {}  Manual: {}  Rework: {}\n",
        m.auto_merge_count, m.manual_merge_count, m.rework_count
    ));
    out.push_str(&format!("  Rework Rate:     {}\n", rr));
    out.push_str(&format!("  Avg Review Latency: {}\n", latency));

    // Per-agent table
    if !m.agent_rows.is_empty() {
        out.push_str("\nPer-Agent Breakdown\n");
        out.push_str(&"-".repeat(60));
        out.push('\n');
        out.push_str(&format!(
            "  {:<16} {:>6} {:>6} {:>6} {:>10} {:>8}\n",
            "ROLE", "DONE", "FAIL", "RESTART", "CYCLE_S", "IDLE%"
        ));
        for a in &m.agent_rows {
            let idle = a
                .idle_pct
                .map(|p| format!("{:.0}%", p))
                .unwrap_or_else(|| "-".to_string());
            out.push_str(&format!(
                "  {:<16} {:>6} {:>6} {:>6} {:>10} {:>8}\n",
                a.role, a.completions, a.failures, a.restarts, a.total_cycle_secs, idle
            ));
        }
    }

    out
}

/// Run the `batty metrics` command against the project root.
///
/// Opens the telemetry DB, queries the dashboard, and prints it.
/// Returns gracefully when the DB is missing or empty.
pub fn run(project_root: &Path) -> Result<()> {
    let db_path = project_root.join(".batty").join("telemetry.db");
    if !db_path.exists() {
        println!("Telemetry Dashboard\n{}", "=".repeat(60));
        println!("\nNo telemetry database found. Run `batty start` to begin collecting data.");
        return Ok(());
    }

    let conn = telemetry_db::open(project_root).context("failed to open telemetry database")?;

    let metrics = query_dashboard(&conn)?;
    print!("{}", format_dashboard(&metrics));
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::team::events::TeamEvent;
    use crate::team::telemetry_db;

    fn setup_db_with_data() -> Connection {
        let conn = telemetry_db::open_in_memory().unwrap();

        // Create a session
        let mut started = TeamEvent::daemon_started();
        started.ts = 1000;
        telemetry_db::insert_event(&conn, &started).unwrap();

        // Assign and complete tasks
        let mut a1 = TeamEvent::task_assigned("eng-1", "10");
        a1.ts = 1100;
        telemetry_db::insert_event(&conn, &a1).unwrap();

        let mut c1 = TeamEvent::task_completed("eng-1", Some("10"));
        c1.ts = 1400; // 300s cycle
        telemetry_db::insert_event(&conn, &c1).unwrap();

        let mut a2 = TeamEvent::task_assigned("eng-2", "20");
        a2.ts = 1200;
        telemetry_db::insert_event(&conn, &a2).unwrap();

        let mut c2 = TeamEvent::task_completed("eng-2", Some("20"));
        c2.ts = 1700; // 500s cycle
        telemetry_db::insert_event(&conn, &c2).unwrap();

        // Merge events
        let mut m1 = TeamEvent::task_auto_merged("eng-1", "10", 0.9, 2, 30);
        m1.ts = 1500;
        telemetry_db::insert_event(&conn, &m1).unwrap();

        let mut m2 = TeamEvent::task_manual_merged("20");
        m2.ts = 1800;
        telemetry_db::insert_event(&conn, &m2).unwrap();

        // A failure
        telemetry_db::insert_event(&conn, &TeamEvent::pane_death("eng-1")).unwrap();

        conn
    }

    #[test]
    fn metrics_with_data() {
        let conn = setup_db_with_data();
        let m = query_dashboard(&conn).unwrap();

        assert_eq!(m.sessions_count, 1);
        assert_eq!(m.total_tasks_completed, 2);
        assert_eq!(m.total_merges, 2);
        assert!(m.total_events > 0);

        // Cycle times: 300s and 500s → avg 400
        let avg = m.avg_cycle_time_secs.unwrap();
        assert!((avg - 400.0).abs() < 0.01);
        assert_eq!(m.min_cycle_time_secs, Some(300));
        assert_eq!(m.max_cycle_time_secs, Some(500));

        // Completion rate: 2 completions, 1 failure → 2/3 ≈ 66.7%
        let cr = m.completion_rate.unwrap();
        assert!((cr - 66.67).abs() < 1.0);

        // Failure rate: 1/3 ≈ 33.3%
        let fr = m.failure_rate.unwrap();
        assert!((fr - 33.33).abs() < 1.0);

        // Merge success rate: 2 merges / 2 completed → 100%
        let mr = m.merge_success_rate.unwrap();
        assert!((mr - 100.0).abs() < 0.01);

        // Review pipeline
        assert_eq!(m.auto_merge_count, 1);
        assert_eq!(m.manual_merge_count, 1);
        let amr = m.auto_merge_rate.unwrap();
        assert!((amr - 50.0).abs() < 0.01);

        // Agents present
        assert_eq!(m.agent_rows.len(), 2);
    }

    #[test]
    fn empty_db() {
        let conn = telemetry_db::open_in_memory().unwrap();
        let m = query_dashboard(&conn).unwrap();

        assert_eq!(m, DashboardMetrics::default());
        assert_eq!(m.sessions_count, 0);
        assert_eq!(m.total_tasks_completed, 0);
        assert!(m.avg_cycle_time_secs.is_none());
        assert!(m.completion_rate.is_none());
        assert!(m.failure_rate.is_none());
        assert!(m.merge_success_rate.is_none());
    }

    #[test]
    fn missing_db_shows_message() {
        let tmp = tempfile::tempdir().unwrap();
        // No .batty/ directory → no DB file
        let result = run(tmp.path());
        assert!(result.is_ok());
    }

    #[test]
    fn rate_calculations() {
        let conn = telemetry_db::open_in_memory().unwrap();

        // 3 completions, 1 failure
        let mut started = TeamEvent::daemon_started();
        started.ts = 100;
        telemetry_db::insert_event(&conn, &started).unwrap();

        for i in 1..=3 {
            let mut a = TeamEvent::task_assigned("eng-1", &i.to_string());
            a.ts = 200 + i as u64 * 100;
            telemetry_db::insert_event(&conn, &a).unwrap();

            let mut c = TeamEvent::task_completed("eng-1", Some(&i.to_string()));
            c.ts = 200 + i as u64 * 100 + 60;
            telemetry_db::insert_event(&conn, &c).unwrap();

            let mut m = TeamEvent::task_auto_merged("eng-1", &i.to_string(), 0.9, 2, 30);
            m.ts = 200 + i as u64 * 100 + 120;
            telemetry_db::insert_event(&conn, &m).unwrap();
        }
        telemetry_db::insert_event(&conn, &TeamEvent::pane_death("eng-1")).unwrap();

        let m = query_dashboard(&conn).unwrap();

        // completion_rate: 3 / (3+1) = 75%
        let cr = m.completion_rate.unwrap();
        assert!((cr - 75.0).abs() < 0.01);

        // failure_rate: 1 / (3+1) = 25%
        let fr = m.failure_rate.unwrap();
        assert!((fr - 25.0).abs() < 0.01);

        // merge_success_rate: 3 merges / 3 completed = 100%
        let mr = m.merge_success_rate.unwrap();
        assert!((mr - 100.0).abs() < 0.01);

        // auto_merge_rate: 3 auto / (3+0) = 100%
        let amr = m.auto_merge_rate.unwrap();
        assert!((amr - 100.0).abs() < 0.01);

        // cycle times: each task has 60s cycle → avg 60
        let avg = m.avg_cycle_time_secs.unwrap();
        assert!((avg - 60.0).abs() < 0.01);
    }

    #[test]
    fn format_dashboard_renders_sections() {
        let m = DashboardMetrics {
            sessions_count: 2,
            total_tasks_completed: 10,
            total_merges: 8,
            total_events: 50,
            avg_cycle_time_secs: Some(300.0),
            min_cycle_time_secs: Some(60),
            max_cycle_time_secs: Some(900),
            completion_rate: Some(90.0),
            failure_rate: Some(10.0),
            merge_success_rate: Some(80.0),
            auto_merge_count: 6,
            manual_merge_count: 2,
            auto_merge_rate: Some(75.0),
            rework_count: 1,
            rework_rate: Some(11.0),
            avg_review_latency_secs: Some(120.0),
            agent_rows: vec![AgentRow {
                role: "eng-1".to_string(),
                completions: 5,
                failures: 1,
                restarts: 0,
                total_cycle_secs: 1500,
                idle_pct: Some(20.0),
            }],
        };

        let text = format_dashboard(&m);

        assert!(text.contains("Telemetry Dashboard"));
        assert!(text.contains("Session Totals"));
        assert!(text.contains("Sessions:        2"));
        assert!(text.contains("Tasks Completed: 10"));
        assert!(text.contains("Total Merges:    8"));

        assert!(text.contains("Cycle Time"));
        assert!(text.contains("Average: 5m 0s"));
        assert!(text.contains("Min:     1m 0s"));
        assert!(text.contains("Max:     15m 0s"));

        assert!(text.contains("Rates"));
        assert!(text.contains("Completion Rate:    90%"));
        assert!(text.contains("Failure Rate:       10%"));
        assert!(text.contains("Merge Success Rate: 80%"));

        assert!(text.contains("Review Pipeline"));
        assert!(text.contains("Auto-merge Rate: 75%"));
        assert!(text.contains("Rework Rate:     11%"));

        assert!(text.contains("Per-Agent Breakdown"));
        assert!(text.contains("eng-1"));
    }

    #[test]
    fn format_dashboard_empty_shows_na() {
        let m = DashboardMetrics::default();
        let text = format_dashboard(&m);

        assert!(text.contains("n/a"));
        assert!(text.contains("Sessions:        0"));
        // No agent table when empty
        assert!(!text.contains("Per-Agent Breakdown"));
    }

    #[test]
    fn format_duration_works() {
        assert_eq!(format_duration(30.0), "30s");
        assert_eq!(format_duration(90.0), "1m 30s");
        assert_eq!(format_duration(3661.0), "1h 1m");
    }
}
