//! Gateway aggregation — pure functions for cross-project pipeline status.
//!
//! Formats aggregated pipeline data into compact text suitable for chat
//! transports (Matrix, Slack). Uses `service::pipeline::aggregate_pipeline_counts`
//! for per-project parsing.

use serde_json::Value;
use std::collections::BTreeMap;

/// Format an aggregated status map as a compact, one-line-per-project string
/// suitable for Matrix/Slack messages.
///
/// Healthy projects: `🟢 **name** — B:5 C:2 Q:1 M:0 D:12`
/// Blocked items appended on the same line: `| blocked: 42 [story]`
/// Unreachable projects: `🔴 **name** — UNREACHABLE`
pub fn format_aggregate_status_compact(statuses: &BTreeMap<String, Value>) -> String {
    let mut lines: Vec<String> = Vec::new();
    for (name, status) in statuses {
        if let Some(err) = status.get("error").and_then(|e| e.as_str()) {
            lines.push(format!("\u{1F534} **{name}** — UNREACHABLE: {err}"));
        } else {
            let counts = status.get("counts");
            let b = counts
                .and_then(|c| c.get("backlog"))
                .and_then(|n| n.as_u64())
                .unwrap_or(0);
            let c = counts
                .and_then(|c| c.get("current"))
                .and_then(|n| n.as_u64())
                .unwrap_or(0);
            let q = counts
                .and_then(|c| c.get("qa"))
                .and_then(|n| n.as_u64())
                .unwrap_or(0);
            let m = counts
                .and_then(|c| c.get("merge"))
                .and_then(|n| n.as_u64())
                .unwrap_or(0);
            let d = counts
                .and_then(|c| c.get("done"))
                .and_then(|n| n.as_u64())
                .unwrap_or(0);

            let blocked_arr = status
                .get("blocked")
                .and_then(|a| a.as_array())
                .cloned()
                .unwrap_or_default();

            let indicator = if blocked_arr.is_empty() {
                "\u{1F7E2}" // 🟢
            } else {
                "\u{1F7E0}" // 🟠
            };

            let mut line = format!("{indicator} **{name}** — B:{b} C:{c} Q:{q} M:{m} D:{d}");

            if !blocked_arr.is_empty() {
                let ids: Vec<String> = blocked_arr
                    .iter()
                    .filter_map(|item| item.get("story_id").and_then(|s| s.as_str()))
                    .map(|s| s.to_string())
                    .collect();
                line.push_str(&format!(" | blocked: {}", ids.join(", ")));
            }

            lines.push(line);
        }
    }
    if lines.is_empty() {
        return "No projects registered.".to_string();
    }
    format!("**All Projects**\n\n{}", lines.join("\n\n"))
}

// ── Tests ────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn format_healthy_project() {
        let mut statuses = BTreeMap::new();
        statuses.insert(
            "huskies".to_string(),
            json!({
                "counts": { "backlog": 5, "current": 2, "qa": 1, "merge": 0, "done": 12 },
                "blocked": []
            }),
        );
        let output = format_aggregate_status_compact(&statuses);
        assert!(output.contains("huskies"));
        assert!(output.contains("B:5"));
        assert!(output.contains("C:2"));
        assert!(output.contains("Q:1"));
        assert!(output.contains("D:12"));
        assert!(!output.contains("blocked:"));
    }

    #[test]
    fn format_unreachable_project() {
        let mut statuses = BTreeMap::new();
        statuses.insert(
            "broken".to_string(),
            json!({ "error": "connection refused" }),
        );
        let output = format_aggregate_status_compact(&statuses);
        assert!(output.contains("broken"));
        assert!(output.contains("UNREACHABLE"));
        assert!(output.contains("connection refused"));
    }

    #[test]
    fn format_blocked_items_shown() {
        let mut statuses = BTreeMap::new();
        statuses.insert(
            "myproj".to_string(),
            json!({
                "counts": { "backlog": 0, "current": 1, "qa": 0, "merge": 0, "done": 0 },
                "blocked": [{ "story_id": "42_story_x", "name": "X", "stage": "current", "reason": "blocked" }]
            }),
        );
        let output = format_aggregate_status_compact(&statuses);
        assert!(output.contains("blocked:"));
        assert!(output.contains("42_story_x"));
    }

    #[test]
    fn format_empty_projects() {
        let statuses = BTreeMap::new();
        let output = format_aggregate_status_compact(&statuses);
        assert_eq!(output, "No projects registered.");
    }
}
