//! MCP worktree tools — create, list, remove, get editor command, and read worktree commits.

use serde_json::{Value, json};

use crate::config::ProjectConfig;
use crate::http::context::AppContext;
use crate::service::settings::get_editor_command;
use crate::worktree;

pub(crate) async fn tool_create_worktree(args: &Value, ctx: &AppContext) -> Result<String, String> {
    let story_id = args
        .get("story_id")
        .and_then(|v| v.as_str())
        .ok_or("Missing required argument: story_id")?;

    let project_root = ctx.services.agents.get_project_root(&ctx.state)?;
    let info = ctx
        .services
        .agents
        .create_worktree(&project_root, story_id)
        .await?;

    serde_json::to_string_pretty(&json!({
        "story_id": story_id,
        "worktree_path": info.path.to_string_lossy(),
        "branch": info.branch,
        "base_branch": info.base_branch,
    }))
    .map_err(|e| format!("Serialization error: {e}"))
}

pub(crate) fn tool_list_worktrees(ctx: &AppContext) -> Result<String, String> {
    let project_root = ctx.services.agents.get_project_root(&ctx.state)?;
    let entries = worktree::list_worktrees(&project_root)?;

    serde_json::to_string_pretty(&json!(
        entries
            .iter()
            .map(|e| json!({
                "story_id": e.story_id,
                "path": e.path.to_string_lossy(),
            }))
            .collect::<Vec<_>>()
    ))
    .map_err(|e| format!("Serialization error: {e}"))
}

pub(crate) async fn tool_remove_worktree(args: &Value, ctx: &AppContext) -> Result<String, String> {
    let story_id = args
        .get("story_id")
        .and_then(|v| v.as_str())
        .ok_or("Missing required argument: story_id")?;

    let project_root = ctx.services.agents.get_project_root(&ctx.state)?;
    let config = ProjectConfig::load(&project_root)?;
    worktree::remove_worktree_by_story_id(&project_root, story_id, &config).await?;

    Ok(format!("Worktree for story '{story_id}' removed."))
}

/// MCP tool handler for `cleanup_worktrees` — removes stale worktrees whose stories are done or archived.
pub(crate) async fn tool_cleanup_worktrees(
    args: &Value,
    ctx: &AppContext,
) -> Result<String, String> {
    let confirm = args
        .get("confirm")
        .and_then(|v| v.as_bool())
        .unwrap_or(false);

    let project_root = ctx.services.agents.get_project_root(&ctx.state)?;
    let config = crate::config::ProjectConfig::load(&project_root)?;
    let report = worktree::run_cleanup(&project_root, &config, confirm).await;
    Ok(worktree::format_report(&report, confirm))
}

pub(crate) fn tool_get_editor_command(args: &Value, ctx: &AppContext) -> Result<String, String> {
    let worktree_path = args
        .get("worktree_path")
        .and_then(|v| v.as_str())
        .ok_or("Missing required argument: worktree_path")?;

    let editor = get_editor_command(&*ctx.store)
        .ok_or_else(|| "No editor configured. Set one via PUT /api/settings/editor.".to_string())?;

    Ok(format!("{editor} {worktree_path}"))
}

/// Run `git log <base>..HEAD --oneline` in the worktree and return the commit
pub(crate) async fn get_worktree_commits(
    worktree_path: &str,
    base_branch: &str,
) -> Option<Vec<String>> {
    let wt = worktree_path.to_string();
    let base = base_branch.to_string();
    tokio::task::spawn_blocking(move || {
        let output = std::process::Command::new("git")
            .args(["log", &format!("{base}..HEAD"), "--oneline"])
            .current_dir(&wt)
            .output()
            .ok()?;

        if output.status.success() {
            let lines: Vec<String> = String::from_utf8(output.stdout)
                .ok()?
                .lines()
                .filter(|l| !l.is_empty())
                .map(|l| l.to_string())
                .collect();
            Some(lines)
        } else {
            None
        }
    })
    .await
    .ok()
    .flatten()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::http::test_helpers::test_ctx;
    use crate::store::StoreOps;

    #[tokio::test]
    async fn tool_create_worktree_missing_story_id() {
        let tmp = tempfile::tempdir().unwrap();
        let ctx = test_ctx(tmp.path());
        let result = tool_create_worktree(&json!({}), &ctx).await;
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("story_id"));
    }

    #[tokio::test]
    async fn tool_remove_worktree_missing_story_id() {
        let tmp = tempfile::tempdir().unwrap();
        let ctx = test_ctx(tmp.path());
        let result = tool_remove_worktree(&json!({}), &ctx).await;
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("story_id"));
    }

    #[test]
    fn tool_list_worktrees_empty_dir() {
        let tmp = tempfile::tempdir().unwrap();
        let ctx = test_ctx(tmp.path());
        let result = tool_list_worktrees(&ctx).unwrap();
        let parsed: Vec<Value> = serde_json::from_str(&result).unwrap();
        assert!(parsed.is_empty());
    }

    // ── Editor command tool tests ─────────────────────────────────

    #[test]
    fn tool_get_editor_command_missing_worktree_path() {
        let tmp = tempfile::tempdir().unwrap();
        let ctx = test_ctx(tmp.path());
        let result = tool_get_editor_command(&json!({}), &ctx);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("worktree_path"));
    }

    #[test]
    fn tool_get_editor_command_no_editor_configured() {
        let tmp = tempfile::tempdir().unwrap();
        let ctx = test_ctx(tmp.path());
        let result = tool_get_editor_command(&json!({"worktree_path": "/some/path"}), &ctx);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("No editor configured"));
    }

    #[test]
    fn tool_get_editor_command_formats_correctly() {
        let tmp = tempfile::tempdir().unwrap();
        let ctx = test_ctx(tmp.path());
        ctx.store.set("editor_command", json!("zed"));

        let result = tool_get_editor_command(
            &json!({"worktree_path": "/home/user/worktrees/37_my_story"}),
            &ctx,
        )
        .unwrap();
        assert_eq!(result, "zed /home/user/worktrees/37_my_story");
    }

    #[test]
    fn tool_get_editor_command_works_with_vscode() {
        let tmp = tempfile::tempdir().unwrap();
        let ctx = test_ctx(tmp.path());
        ctx.store.set("editor_command", json!("code"));

        let result =
            tool_get_editor_command(&json!({"worktree_path": "/path/to/worktree"}), &ctx).unwrap();
        assert_eq!(result, "code /path/to/worktree");
    }

    #[test]
    fn get_editor_command_in_tools_list() {
        use super::super::super::tools_list::handle_tools_list;
        let resp = handle_tools_list(Some(json!(1)));
        let tools = resp.result.unwrap()["tools"].as_array().unwrap().clone();
        let tool = tools.iter().find(|t| t["name"] == "get_editor_command");
        assert!(tool.is_some(), "get_editor_command missing from tools list");
        let t = tool.unwrap();
        assert!(t["description"].is_string());
        let required = t["inputSchema"]["required"].as_array().unwrap();
        let req_names: Vec<&str> = required.iter().map(|v| v.as_str().unwrap()).collect();
        assert!(req_names.contains(&"worktree_path"));
    }
}
