use std::collections::BTreeMap;
use std::path::Path;

use crate::body::{self, ManualItem};
use crate::commands::today;
use crate::error::Result;
use crate::Ctx;

pub fn run(ctx: &Ctx, out: Option<&str>, name: Option<&str>) -> Result<()> {
    let prj = ctx.open()?;
    let (feats, _) = prj.load();

    // Group manual items by Setup line; skip wontfix features.
    let mut groups: BTreeMap<String, Vec<(String, ManualItem)>> = BTreeMap::new();
    for f in &feats {
        if f.status() == Some("wontfix") {
            continue;
        }
        let id = f.id().unwrap_or("").to_string();
        for it in body::manual_items(&f.body) {
            let key = it.setup.clone().unwrap_or_else(|| "(no setup)".to_string());
            groups.entry(key).or_default().push((id.clone(), it));
        }
    }
    // Within each group, list items lacking automated coverage first.
    for items in groups.values_mut() {
        items.sort_by_key(|(_, it)| it.uncovered() as u8 ^ 1); // uncovered (true) first
    }

    let title = name.map(str::to_string).unwrap_or_else(today);
    let mut lines = vec![
        format!("# Manual test runbook — {title}"),
        String::new(),
        "Generated by opys. Check boxes as you execute; annotate failures".to_string(),
        "inline (e.g. 'fails at step 2: <note>'). Commit when done. Items marked".to_string(),
        "⚠ have no automated coverage — prioritize them.".to_string(),
        String::new(),
    ];
    for (setup, items) in &groups {
        lines.push(format!("## Setup: {setup}"));
        lines.push(String::new());
        for (id, it) in items {
            let marker = if it.uncovered() { "⚠ " } else { "" };
            lines.push(format!("- [ ] {marker}{id} — {}", it.desc));
            for (i, step) in it.steps.iter().enumerate() {
                lines.push(format!("  {}. {step}", i + 1));
            }
            lines.push(format!(
                "  - Expect: {}",
                it.expect.as_deref().unwrap_or("")
            ));
        }
        lines.push(String::new());
    }
    let text = lines.join("\n");

    match out {
        Some(path) => {
            let path = Path::new(path);
            if let Some(parent) = path.parent() {
                std::fs::create_dir_all(parent)?;
            }
            std::fs::write(path, &text)?;
            println!("{}", path.display());
        }
        None => println!("{text}"),
    }
    Ok(())
}
