//! Render the bakeoff matrix into a dated scorecard (Markdown or JSON).
//!
//! The scorecard is deterministic: it stamps the matrix `as_of` date, not the
//! wall clock, so the committed artifact and a fresh render match byte for byte.

use super::model::{BakeoffMatrix, Support};

/// Render the matrix as a dated Markdown scorecard.
pub fn render_markdown(matrix: &BakeoffMatrix) -> String {
    let mut out = String::new();
    out.push_str("# MCP tooling competitive bakeoff\n\n");
    out.push_str(&format!(
        "Pinned snapshot as of {}. Generated by `mcptest bakeoff`. Support values: \
         yes, partial, no, hosted (account-only), n/a (not applicable to that tool's role). \
         This is evidence, not marketing; see registry.yml for the pinned versions.\n\n",
        matrix.as_of
    ));

    push_matrix_table(&mut out, matrix);
    push_legend(&mut out, matrix);
    push_gaps(&mut out, matrix);
    out
}

/// The competitor x criterion table, with mcptest as the first data column.
fn push_matrix_table(out: &mut String, matrix: &BakeoffMatrix) {
    out.push_str("## Capability matrix\n\n");
    out.push_str("| Capability | mcptest |");
    for comp in &matrix.competitors {
        out.push_str(&format!(" {} |", comp.name));
    }
    out.push('\n');
    out.push_str("| --- | --- |");
    for _ in &matrix.competitors {
        out.push_str(" --- |");
    }
    out.push('\n');

    for criterion in &matrix.criteria {
        out.push_str(&format!("| {} |", criterion.name));
        let mine = matrix
            .mcptest
            .capabilities
            .get(&criterion.id)
            .map(|c| c.support)
            .unwrap_or(Support::Na);
        out.push_str(&format!(" {} |", mine.glyph()));
        for comp in &matrix.competitors {
            let v = comp
                .capabilities
                .get(&criterion.id)
                .copied()
                .unwrap_or(Support::Na);
            out.push_str(&format!(" {} |", v.glyph()));
        }
        out.push('\n');
    }
    out.push('\n');
}

/// The pinned-version / hosting legend for each tool.
fn push_legend(out: &mut String, matrix: &BakeoffMatrix) {
    out.push_str("## Pinned versions and hosting\n\n");
    out.push_str(&format!(
        "- mcptest {} (local, OSS, Apache-2.0)\n",
        matrix.mcptest.version
    ));
    for comp in &matrix.competitors {
        out.push_str(&format!(
            "- {} {} ({}, {}) {}\n",
            comp.name, comp.version, comp.hosting, comp.license, comp.url
        ));
    }
    out.push('\n');
}

/// The honest mcptest gap list, each linking its Linear ticket.
fn push_gaps(out: &mut String, matrix: &BakeoffMatrix) {
    out.push_str("## mcptest gaps\n\n");
    let gaps = matrix.gaps();
    if gaps.is_empty() {
        out.push_str("No open capability gaps in this snapshot.\n");
        return;
    }
    out.push_str(
        "Capabilities where mcptest is partial or absent, each tracked by a Linear ticket:\n\n",
    );
    for (criterion, support, issue) in gaps {
        out.push_str(&format!(
            "- **{}** ({}): tracked by {}\n",
            criterion.name,
            support.glyph(),
            issue
        ));
    }
    out.push('\n');
}

#[cfg(test)]
mod tests {
    use super::super::model::BakeoffMatrix;
    use super::render_markdown;

    #[test]
    fn embedded_matrix_loads_and_validates() {
        let matrix = BakeoffMatrix::embedded().expect("matrix loads");
        assert!(
            matrix.competitors.len() >= 9,
            "compares at least nine tools"
        );
        assert!(!matrix.criteria.is_empty());
    }

    #[test]
    fn every_mcptest_gap_links_a_ticket() {
        let matrix = BakeoffMatrix::embedded().unwrap();
        for (criterion, _, issue) in matrix.gaps() {
            assert!(
                issue.starts_with("WOR-"),
                "gap on {} must link a ticket",
                criterion.id
            );
        }
    }

    #[test]
    fn markdown_is_dated_and_lists_competitors() {
        let matrix = BakeoffMatrix::embedded().unwrap();
        let md = render_markdown(&matrix);
        assert!(md.contains(&matrix.as_of), "stamps the as_of date");
        assert!(md.contains("Official MCP Inspector"));
        assert!(md.contains("ToolHive"));
        assert!(md.contains("## mcptest gaps"));
    }

    #[test]
    fn render_is_reproducible() {
        let matrix = BakeoffMatrix::embedded().unwrap();
        assert_eq!(render_markdown(&matrix), render_markdown(&matrix));
    }
}
