use rmcp::handler::server::router::tool::ToolRouter;
use rmcp::handler::server::wrapper::Parameters;
use rmcp::model::*;
use rmcp::{ServerHandler, tool, tool_handler, tool_router};

use crate::error::Error;
use crate::service;
use crate::store;
use crate::task::{self, Priority, Status};

use super::params::*;
use super::{BeaMcp, ok_json, parse_priority, parse_status, tool_ok};

#[tool_router]
impl BeaMcp {
    #[tool(description = "List tasks that are ready to work on (open with all dependencies done)")]
    async fn list_ready(
        &self,
        Parameters(params): Parameters<ListReadyParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let limit = params.limit.map(|v| v as usize);
                let ready = service::list_ready(
                    &tasks,
                    params.tag.as_deref(),
                    limit,
                    params.epic.as_deref(),
                );
                let eff = service::effective_priorities(&tasks);
                let summaries: Vec<_> = ready.iter().map(|t| t.summary(eff.get(&t.id))).collect();
                ok_json(serde_json::json!(summaries))
            }
            .await,
        )
    }

    #[tool(description = "List tasks with optional filters")]
    async fn list_all_tasks(
        &self,
        Parameters(params): Parameters<ListTasksFilterParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let status = params.status.as_deref().map(parse_status).transpose()?;
                let priority = params.priority.as_deref().map(parse_priority).transpose()?;
                let filtered = service::list_tasks(
                    &tasks,
                    status,
                    priority,
                    params.tag.as_deref(),
                    true, // MCP always shows all statuses unless filtered
                    params.epic.as_deref(),
                );
                let eff = service::effective_priorities(&tasks);
                let summaries: Vec<_> =
                    filtered.iter().map(|t| t.summary(eff.get(&t.id))).collect();
                ok_json(serde_json::json!(summaries))
            }
            .await,
        )
    }

    #[tool(description = "Get full details of a single task")]
    async fn get_task(
        &self,
        Parameters(params): Parameters<TaskIdParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let t = service::get_task(&tasks, &params.id)?;
                let eff = service::effective_priorities(&tasks);
                let ep = eff.get(&t.id);
                ok_json(serde_json::to_value(t.detail(ep))?)
            }
            .await,
        )
    }

    #[tool(description = "Create a new task")]
    async fn create_task(
        &self,
        Parameters(params): Parameters<CreateTaskParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let priority = params
                    .priority
                    .as_deref()
                    .map(parse_priority)
                    .transpose()?
                    .unwrap_or(Priority::P2);

                let task_type = params
                    .task_type
                    .as_deref()
                    .map(|s| s.parse::<task::TaskType>())
                    .transpose()
                    .map_err(|e| Error::InvalidFilter {
                        field: "type".into(),
                        value: e.clone(),
                        expected: "task, epic".into(),
                    })?
                    .unwrap_or_default();

                let t = service::create_task(
                    &self.base,
                    &tasks,
                    params.title,
                    priority,
                    params.tags.unwrap_or_default(),
                    params.depends_on.unwrap_or_default(),
                    params.parent,
                    params.body.unwrap_or_default(),
                    task_type,
                )?;
                ok_json(serde_json::to_value(t.summary(None))?)
            }
            .await,
        )
    }

    #[tool(description = "Update task fields")]
    async fn update_task(
        &self,
        Parameters(params): Parameters<UpdateTaskParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let status = params.status.as_deref().map(parse_status).transpose()?;
                let priority = params.priority.as_deref().map(parse_priority).transpose()?;
                let t = service::update_task(
                    &self.base,
                    &tasks,
                    &params.id,
                    status,
                    priority,
                    params.tags,
                    params.assignee,
                    params.body,
                    None, // MCP doesn't support title update currently
                )?;
                ok_json(serde_json::to_value(t.summary(None))?)
            }
            .await,
        )
    }

    #[tool(description = "Start a task (set status to in_progress)")]
    async fn start_task(
        &self,
        Parameters(params): Parameters<TaskIdParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let t = service::set_status(&self.base, &tasks, &params.id, Status::InProgress)?;
                ok_json(serde_json::to_value(t.summary(None))?)
            }
            .await,
        )
    }

    #[tool(description = "Complete a task (set status to done)")]
    async fn complete_task(
        &self,
        Parameters(params): Parameters<TaskIdParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let t = service::set_status(&self.base, &tasks, &params.id, Status::Done)?;
                ok_json(serde_json::to_value(t.summary(None))?)
            }
            .await,
        )
    }

    #[tool(description = "Add a dependency between tasks")]
    async fn add_dependency(
        &self,
        Parameters(params): Parameters<DepParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let t =
                    service::add_dependency(&self.base, &tasks, &params.id, &params.depends_on)?;
                ok_json(serde_json::to_value(t.summary(None))?)
            }
            .await,
        )
    }

    #[tool(description = "Remove a dependency between tasks")]
    async fn remove_dependency(
        &self,
        Parameters(params): Parameters<DepParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let t =
                    service::remove_dependency(&self.base, &tasks, &params.id, &params.depends_on)?;
                ok_json(serde_json::to_value(t.summary(None))?)
            }
            .await,
        )
    }

    #[tool(description = "Search tasks by text query")]
    async fn search_tasks(
        &self,
        Parameters(params): Parameters<SearchParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let results = service::search_tasks(&tasks, &params.query, true);
                let summaries: Vec<_> = results.iter().map(|t| t.summary(None)).collect();
                ok_json(serde_json::json!(summaries))
            }
            .await,
        )
    }

    #[tool(description = "Cancel a task (set status to cancelled)")]
    async fn cancel_task(
        &self,
        Parameters(params): Parameters<TaskIdParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let t = service::set_status(&self.base, &tasks, &params.id, Status::Cancelled)?;
                ok_json(serde_json::to_value(t.summary(None))?)
            }
            .await,
        )
    }

    #[tool(
        description = "Delete cancelled tasks. Set include_done=true to also delete done tasks."
    )]
    async fn prune_tasks(
        &self,
        Parameters(params): Parameters<PruneParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let include_done = params.include_done.unwrap_or(false);
                let deleted = service::prune_tasks(&self.base, &tasks, include_done)?;
                let summaries: Vec<_> = deleted.iter().map(|t| t.summary(None)).collect();
                ok_json(serde_json::json!(summaries))
            }
            .await,
        )
    }

    #[tool(description = "Permanently delete a task by ID")]
    async fn delete_task(
        &self,
        Parameters(params): Parameters<TaskIdParams>,
    ) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let t = service::delete_task(&self.base, &tasks, &params.id)?;
                ok_json(serde_json::to_value(t.summary(None))?)
            }
            .await,
        )
    }

    #[tool(description = "Get the full dependency graph as an adjacency list")]
    async fn get_graph(&self) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let graph = service::build_graph(&tasks);
                let adj = graph.adjacency_list();
                ok_json(serde_json::json!(adj))
            }
            .await,
        )
    }

    #[tool(description = "List all epics with progress summary")]
    async fn list_epics(&self) -> Result<CallToolResult, rmcp::ErrorData> {
        tool_ok(
            async {
                let tasks = store::load_all(&self.base).await?;
                let mut epics: Vec<&task::Task> =
                    tasks.values().filter(|t| t.task_type.is_epic()).collect();
                epics.sort_by(|a, b| a.priority.cmp(&b.priority).then(a.created.cmp(&b.created)));
                let summaries: Vec<_> = epics
                    .iter()
                    .map(|t| t.epic_summary(service::epic_progress(&tasks, &t.id)))
                    .collect();
                ok_json(serde_json::json!(summaries))
            }
            .await,
        )
    }
}

#[tool_handler]
impl ServerHandler for BeaMcp {
    fn get_info(&self) -> ServerInfo {
        ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
            .with_server_info(Implementation::new("bears", env!("CARGO_PKG_VERSION")))
            .with_instructions(
                "bears is a file-based task tracker. Use tools to manage tasks and dependencies."
                    .to_string(),
            )
    }
}

impl BeaMcp {
    pub(super) fn build_tool_router() -> ToolRouter<Self> {
        Self::tool_router()
    }
}

#[cfg(test)]
mod tests {
    use rmcp::handler::server::wrapper::Parameters;
    use rmcp::model::*;

    use crate::store;

    use super::super::BeaMcp;
    use super::super::params::*;

    use tempfile::TempDir;

    fn setup() -> (TempDir, BeaMcp) {
        let tmp = TempDir::new().unwrap();
        store::init(tmp.path()).unwrap();
        let mcp = BeaMcp::new(tmp.path().to_path_buf());
        (tmp, mcp)
    }

    fn extract_json(result: &CallToolResult) -> serde_json::Value {
        let text = match &result.content[0].raw {
            RawContent::Text(t) => &t.text,
            _ => panic!("expected text content"),
        };
        serde_json::from_str(text).unwrap()
    }

    fn extract_text(result: &CallToolResult) -> &str {
        match &result.content[0].raw {
            RawContent::Text(t) => &t.text,
            _ => panic!("expected text content"),
        }
    }

    #[tokio::test]
    async fn test_tool_create_and_list() {
        let (_tmp, mcp) = setup();
        let result = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "Test task".into(),
                priority: Some("P1".into()),
                tags: Some(vec!["backend".into()]),
                depends_on: None,
                parent: None,
                body: None,
                task_type: None,
            }))
            .await
            .unwrap();
        let json = extract_json(&result);
        assert_eq!(json["title"], "Test task");
        let id = json["id"].as_str().unwrap();

        let list = mcp
            .list_all_tasks(Parameters(ListTasksFilterParams {
                status: None,
                priority: None,
                tag: None,
                epic: None,
            }))
            .await
            .unwrap();
        let arr = extract_json(&list);
        let arr = arr.as_array().unwrap();
        assert_eq!(arr.len(), 1);
        assert_eq!(arr[0]["id"], id);
    }

    #[tokio::test]
    async fn test_tool_get_task() {
        let (_tmp, mcp) = setup();
        let result = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "Detail task".into(),
                priority: None,
                tags: None,
                depends_on: None,
                parent: None,
                body: Some("Some body".into()),
                task_type: None,
            }))
            .await
            .unwrap();
        let id = extract_json(&result)["id"].as_str().unwrap().to_string();

        let detail = mcp.get_task(Parameters(TaskIdParams { id })).await.unwrap();
        let json = extract_json(&detail);
        assert_eq!(json["title"], "Detail task");
        assert_eq!(json["body"], "Some body");
    }

    #[tokio::test]
    async fn test_tool_start_complete() {
        let (_tmp, mcp) = setup();
        let result = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "Flow task".into(),
                priority: None,
                tags: None,
                depends_on: None,
                parent: None,
                body: None,
                task_type: None,
            }))
            .await
            .unwrap();
        let id = extract_json(&result)["id"].as_str().unwrap().to_string();

        let started = mcp
            .start_task(Parameters(TaskIdParams { id: id.clone() }))
            .await
            .unwrap();
        assert_eq!(extract_json(&started)["status"], "in_progress");

        let completed = mcp
            .complete_task(Parameters(TaskIdParams { id }))
            .await
            .unwrap();
        assert_eq!(extract_json(&completed)["status"], "done");
    }

    #[tokio::test]
    async fn test_tool_ready() {
        let (_tmp, mcp) = setup();
        let t1 = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "First".into(),
                priority: None,
                tags: None,
                depends_on: None,
                parent: None,
                body: None,
                task_type: None,
            }))
            .await
            .unwrap();
        let id1 = extract_json(&t1)["id"].as_str().unwrap().to_string();

        mcp.create_task(Parameters(CreateTaskParams {
            title: "Second".into(),
            priority: None,
            tags: None,
            depends_on: Some(vec![id1.clone()]),
            parent: None,
            body: None,
            task_type: None,
        }))
        .await
        .unwrap();

        // Only first should be ready
        let ready = mcp
            .list_ready(Parameters(ListReadyParams {
                limit: None,
                tag: None,
                epic: None,
            }))
            .await
            .unwrap();
        let arr = extract_json(&ready);
        let arr = arr.as_array().unwrap();
        assert_eq!(arr.len(), 1);
        assert_eq!(arr[0]["title"], "First");

        // Complete first
        mcp.complete_task(Parameters(TaskIdParams { id: id1 }))
            .await
            .unwrap();

        // Now second should be ready
        let ready = mcp
            .list_ready(Parameters(ListReadyParams {
                limit: None,
                tag: None,
                epic: None,
            }))
            .await
            .unwrap();
        let arr = extract_json(&ready);
        let arr = arr.as_array().unwrap();
        assert_eq!(arr.len(), 1);
        assert_eq!(arr[0]["title"], "Second");
    }

    #[tokio::test]
    async fn test_tool_dependency_cycle() {
        let (_tmp, mcp) = setup();
        let t1 = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "A".into(),
                priority: None,
                tags: None,
                depends_on: None,
                parent: None,
                body: None,
                task_type: None,
            }))
            .await
            .unwrap();
        let id_a = extract_json(&t1)["id"].as_str().unwrap().to_string();

        let t2 = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "B".into(),
                priority: None,
                tags: None,
                depends_on: Some(vec![id_a.clone()]),
                parent: None,
                body: None,
                task_type: None,
            }))
            .await
            .unwrap();
        let id_b = extract_json(&t2)["id"].as_str().unwrap().to_string();

        let result = mcp
            .add_dependency(Parameters(DepParams {
                id: id_a,
                depends_on: id_b,
            }))
            .await
            .unwrap();
        assert_eq!(result.is_error, Some(true));
    }

    #[tokio::test]
    async fn test_tool_search() {
        let (_tmp, mcp) = setup();
        mcp.create_task(Parameters(CreateTaskParams {
            title: "Implement OAuth".into(),
            priority: None,
            tags: Some(vec!["auth".into()]),
            depends_on: None,
            parent: None,
            body: None,
            task_type: None,
        }))
        .await
        .unwrap();
        mcp.create_task(Parameters(CreateTaskParams {
            title: "Fix database".into(),
            priority: None,
            tags: None,
            depends_on: None,
            parent: None,
            body: None,
            task_type: None,
        }))
        .await
        .unwrap();

        let results = mcp
            .search_tasks(Parameters(SearchParams {
                query: "OAuth".into(),
            }))
            .await
            .unwrap();
        let arr = extract_json(&results);
        let arr = arr.as_array().unwrap();
        assert_eq!(arr.len(), 1);
        assert_eq!(arr[0]["title"], "Implement OAuth");
    }

    #[tokio::test]
    async fn test_tool_delete_task() {
        let (_tmp, mcp) = setup();
        let result = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "To be deleted".into(),
                priority: None,
                tags: None,
                depends_on: None,
                parent: None,
                body: None,
                task_type: None,
            }))
            .await
            .unwrap();
        let id = extract_json(&result)["id"].as_str().unwrap().to_string();

        let deleted = mcp
            .delete_task(Parameters(TaskIdParams { id: id.clone() }))
            .await
            .unwrap();
        assert_eq!(extract_json(&deleted)["id"], id);

        // Should no longer be findable
        let not_found = mcp.get_task(Parameters(TaskIdParams { id })).await.unwrap();
        assert_eq!(not_found.is_error, Some(true));
    }

    #[tokio::test]
    async fn test_tool_graph() {
        let (_tmp, mcp) = setup();
        let t1 = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "A".into(),
                priority: None,
                tags: None,
                depends_on: None,
                parent: None,
                body: None,
                task_type: None,
            }))
            .await
            .unwrap();
        let id_a = extract_json(&t1)["id"].as_str().unwrap().to_string();

        mcp.create_task(Parameters(CreateTaskParams {
            title: "B".into(),
            priority: None,
            tags: None,
            depends_on: Some(vec![id_a]),
            parent: None,
            body: None,
            task_type: None,
        }))
        .await
        .unwrap();

        let graph = mcp.get_graph().await.unwrap();
        let json = extract_json(&graph);
        assert!(json.is_object());
    }

    #[tokio::test]
    async fn test_tool_list_invalid_status_filter() {
        let (_tmp, mcp) = setup();
        let result = mcp
            .list_all_tasks(Parameters(ListTasksFilterParams {
                status: Some("bogus".into()),
                priority: None,
                tag: None,
                epic: None,
            }))
            .await
            .unwrap();
        assert_eq!(result.is_error, Some(true));
        assert!(extract_text(&result).contains("invalid status"));
    }

    #[tokio::test]
    async fn test_tool_list_invalid_priority_filter() {
        let (_tmp, mcp) = setup();
        let result = mcp
            .list_all_tasks(Parameters(ListTasksFilterParams {
                status: None,
                priority: Some("P9".into()),
                tag: None,
                epic: None,
            }))
            .await
            .unwrap();
        assert_eq!(result.is_error, Some(true));
        assert!(extract_text(&result).contains("invalid priority"));
    }

    #[tokio::test]
    async fn test_tool_create_invalid_priority() {
        let (_tmp, mcp) = setup();
        let result = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "Bad priority".into(),
                priority: Some("high".into()),
                tags: None,
                depends_on: None,
                parent: None,
                body: None,
                task_type: None,
            }))
            .await
            .unwrap();
        assert_eq!(result.is_error, Some(true));
        assert!(extract_text(&result).contains("invalid priority"));
    }

    #[tokio::test]
    async fn test_tool_update_invalid_status() {
        let (_tmp, mcp) = setup();
        let t = mcp
            .create_task(Parameters(CreateTaskParams {
                title: "Task".into(),
                priority: None,
                tags: None,
                depends_on: None,
                parent: None,
                body: None,
                task_type: None,
            }))
            .await
            .unwrap();
        let id = extract_json(&t)["id"].as_str().unwrap().to_string();

        let result = mcp
            .update_task(Parameters(UpdateTaskParams {
                id,
                status: Some("invalid".into()),
                priority: None,
                tags: None,
                assignee: None,
                body: None,
            }))
            .await
            .unwrap();
        assert_eq!(result.is_error, Some(true));
        assert!(extract_text(&result).contains("invalid status"));
    }
}
