use anyhow::{Context, Result};
use serde::Serialize;
use std::collections::BTreeMap;
use std::path::Path;

/// Embedded SKILL.md content - baked into binary at compile time
const SKILL_CONTENT: &str = include_str!("../../.claude/skills/wg/SKILL.md");

/// JSON output for skill listing
#[derive(Debug, Serialize)]
struct SkillSummary {
    skill: String,
    task_count: usize,
    tasks: Vec<String>,
}

/// JSON output for task skills
#[derive(Debug, Serialize)]
struct TaskSkills {
    id: String,
    title: String,
    skills: Vec<String>,
}

/// List all skills used across tasks
pub fn run_list(dir: &Path, json: bool) -> Result<()> {
    let (graph, _path) = super::load_workgraph(dir)?;

    // Build a map of skill -> tasks that require it
    let mut skill_map: BTreeMap<String, Vec<String>> = BTreeMap::new();

    for task in graph.tasks() {
        for skill in &task.skills {
            skill_map
                .entry(skill.clone())
                .or_default()
                .push(task.id.clone());
        }
    }

    if json {
        let output: Vec<SkillSummary> = skill_map
            .into_iter()
            .map(|(skill, tasks)| SkillSummary {
                skill,
                task_count: tasks.len(),
                tasks,
            })
            .collect();
        println!("{}", serde_json::to_string_pretty(&output)?);
    } else if skill_map.is_empty() {
        println!("No skills defined in any tasks");
    } else {
        println!("Skills used across tasks:\n");
        for (skill, tasks) in &skill_map {
            println!("  {} ({} tasks)", skill, tasks.len());
        }
    }

    Ok(())
}

/// Show skills for a specific task
pub fn run_task(dir: &Path, id: &str, json: bool) -> Result<()> {
    let (graph, _path) = super::load_workgraph(dir)?;

    let task = graph.get_task_or_err(id)?;

    if json {
        let output = TaskSkills {
            id: task.id.clone(),
            title: task.title.clone(),
            skills: task.skills.clone(),
        };
        println!("{}", serde_json::to_string_pretty(&output)?);
    } else {
        println!("Task: {} - {}", task.id, task.title);
        if task.skills.is_empty() {
            println!("Skills: (none)");
        } else {
            println!("Skills: {}", task.skills.join(", "));
        }
    }

    Ok(())
}

/// Find tasks requiring a specific skill
pub fn run_find(dir: &Path, skill: &str, json: bool) -> Result<()> {
    let (graph, _path) = super::load_workgraph(dir)?;

    let matching_tasks: Vec<_> = graph
        .tasks()
        .filter(|t| t.skills.iter().any(|s| s == skill))
        .collect();

    if json {
        let output: Vec<TaskSkills> = matching_tasks
            .iter()
            .map(|t| TaskSkills {
                id: t.id.clone(),
                title: t.title.clone(),
                skills: t.skills.clone(),
            })
            .collect();
        println!("{}", serde_json::to_string_pretty(&output)?);
    } else if matching_tasks.is_empty() {
        println!("No tasks require skill '{}'", skill);
    } else {
        println!("Tasks requiring skill '{}':\n", skill);
        for task in matching_tasks {
            println!("  {} - {}", task.id, task.title);
        }
    }

    Ok(())
}

/// Install the wg Claude Code skill to ~/.claude/skills/wg/
pub fn run_install() -> Result<()> {
    let home = std::env::var("HOME").context("HOME environment variable not set")?;
    let skill_dir = std::path::PathBuf::from(&home)
        .join(".claude")
        .join("skills")
        .join("wg");

    std::fs::create_dir_all(&skill_dir)
        .with_context(|| format!("Failed to create directory: {}", skill_dir.display()))?;

    let skill_path = skill_dir.join("SKILL.md");
    std::fs::write(&skill_path, SKILL_CONTENT)
        .with_context(|| format!("Failed to write skill file: {}", skill_path.display()))?;

    println!("Installed wg skill to: {}", skill_path.display());
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;
    use workgraph::graph::{Node, Task, WorkGraph};
    use workgraph::parser::save_graph;

    fn make_task(id: &str, title: &str) -> Task {
        Task {
            id: id.to_string(),
            title: title.to_string(),
            ..Task::default()
        }
    }

    #[test]
    fn test_skill_collection() {
        let mut graph = WorkGraph::new();

        let mut t1 = make_task("t1", "Task 1");
        t1.skills = vec!["rust".to_string(), "testing".to_string()];

        let mut t2 = make_task("t2", "Task 2");
        t2.skills = vec!["rust".to_string(), "documentation".to_string()];

        let t3 = make_task("t3", "Task 3");

        graph.add_node(Node::Task(t1));
        graph.add_node(Node::Task(t2));
        graph.add_node(Node::Task(t3));

        // Build skill map similar to run_list
        let mut skill_map: BTreeMap<String, Vec<String>> = BTreeMap::new();
        for task in graph.tasks() {
            for skill in &task.skills {
                skill_map
                    .entry(skill.clone())
                    .or_default()
                    .push(task.id.clone());
            }
        }

        assert_eq!(skill_map.len(), 3);
        assert_eq!(skill_map.get("rust").unwrap().len(), 2);
        assert_eq!(skill_map.get("testing").unwrap().len(), 1);
        assert_eq!(skill_map.get("documentation").unwrap().len(), 1);
    }

    #[test]
    fn test_find_tasks_by_skill() {
        let mut graph = WorkGraph::new();

        let mut t1 = make_task("t1", "Task 1");
        t1.skills = vec!["rust".to_string()];

        let mut t2 = make_task("t2", "Task 2");
        t2.skills = vec!["python".to_string()];

        graph.add_node(Node::Task(t1));
        graph.add_node(Node::Task(t2));

        let skill = "rust";
        let matching: Vec<_> = graph
            .tasks()
            .filter(|t| t.skills.iter().any(|s| s == skill))
            .collect();

        assert_eq!(matching.len(), 1);
        assert_eq!(matching[0].id, "t1");
    }

    fn setup_graph_with_skills(dir: &Path) {
        let path = dir.join("graph.jsonl");
        let mut graph = WorkGraph::new();

        let mut t1 = make_task("t1", "Rust task");
        t1.skills = vec!["rust".to_string(), "testing".to_string()];

        let mut t2 = make_task("t2", "Python task");
        t2.skills = vec!["python".to_string()];

        let t3 = make_task("t3", "No-skill task");

        graph.add_node(Node::Task(t1));
        graph.add_node(Node::Task(t2));
        graph.add_node(Node::Task(t3));
        save_graph(&graph, &path).unwrap();
    }

    #[test]
    fn test_run_list_shows_skills() {
        let temp_dir = TempDir::new().unwrap();
        setup_graph_with_skills(temp_dir.path());
        assert!(run_list(temp_dir.path(), false).is_ok());
    }

    #[test]
    fn test_run_list_json() {
        let temp_dir = TempDir::new().unwrap();
        setup_graph_with_skills(temp_dir.path());
        assert!(run_list(temp_dir.path(), true).is_ok());
    }

    #[test]
    fn test_run_list_empty_graph() {
        let temp_dir = TempDir::new().unwrap();
        let path = temp_dir.path().join("graph.jsonl");
        save_graph(&WorkGraph::new(), &path).unwrap();
        assert!(run_list(temp_dir.path(), false).is_ok());
    }

    #[test]
    fn test_run_task_existing() {
        let temp_dir = TempDir::new().unwrap();
        setup_graph_with_skills(temp_dir.path());
        assert!(run_task(temp_dir.path(), "t1", false).is_ok());
    }

    #[test]
    fn test_run_task_nonexistent() {
        let temp_dir = TempDir::new().unwrap();
        setup_graph_with_skills(temp_dir.path());
        assert!(run_task(temp_dir.path(), "no-such", false).is_err());
    }

    #[test]
    fn test_run_task_no_skills() {
        let temp_dir = TempDir::new().unwrap();
        setup_graph_with_skills(temp_dir.path());
        assert!(run_task(temp_dir.path(), "t3", false).is_ok());
    }

    #[test]
    fn test_run_find_matching() {
        let temp_dir = TempDir::new().unwrap();
        setup_graph_with_skills(temp_dir.path());
        assert!(run_find(temp_dir.path(), "rust", false).is_ok());
    }

    #[test]
    fn test_run_find_no_match() {
        let temp_dir = TempDir::new().unwrap();
        setup_graph_with_skills(temp_dir.path());
        assert!(run_find(temp_dir.path(), "haskell", false).is_ok());
    }

    #[test]
    fn test_run_find_json() {
        let temp_dir = TempDir::new().unwrap();
        setup_graph_with_skills(temp_dir.path());
        assert!(run_find(temp_dir.path(), "rust", true).is_ok());
    }

    #[test]
    fn test_run_list_no_init() {
        let temp_dir = TempDir::new().unwrap();
        assert!(run_list(temp_dir.path(), false).is_err());
    }
}
