//! Agent management tools — list, status, dispatch.
//!
//! Agents in provisioning are orchestrator tasks whose name begins with "agent_".
//! There is no separate agent API; these tools are thin views over the task queue.

use crate::protocol::{ToolCategory, ToolError};
use crate::sources::OrchestratorClient;
use crate::tool::{Context, Tool};
use async_trait::async_trait;
use serde_json::{Value, json};
use std::sync::Arc;

// ── agent_list ────────────────────────────────────────────────────────────────

pub struct AgentList {
    client: Arc<OrchestratorClient>,
}
impl AgentList {
    pub fn new(client: Arc<OrchestratorClient>) -> Self { Self { client } }
}

#[async_trait]
impl Tool for AgentList {
    fn name(&self) -> &'static str { "agent_list" }
    fn description(&self) -> &'static str {
        "List orchestrator tasks whose name begins with 'agent_'"
    }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "status": {
                    "type": "string",
                    "enum": ["Pending", "Running", "Completed", "Failed", "Cancelled"],
                    "description": "Filter by task state"
                }
            }
        })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Read }

    async fn invoke(&self, params: Value, _ctx: &Context) -> Result<Value, ToolError> {
        let status_filter = params["status"].as_str();
        let tasks = self.client.list_tasks().await?;

        let items: Vec<Value> = tasks
            .into_iter()
            .filter(|t| {
                t.name.starts_with("agent_")
                    && status_filter
                        .map(|f| t.status.to_string() == f)
                        .unwrap_or(true)
            })
            .map(|t| serde_json::to_value(t).unwrap_or(Value::Null))
            .collect();

        Ok(json!({ "items": items, "total": items.len() }))
    }
}

// ── agent_status ──────────────────────────────────────────────────────────────

pub struct AgentStatus {
    client: Arc<OrchestratorClient>,
}
impl AgentStatus {
    pub fn new(client: Arc<OrchestratorClient>) -> Self { Self { client } }
}

#[async_trait]
impl Tool for AgentStatus {
    fn name(&self) -> &'static str { "agent_status" }
    fn description(&self) -> &'static str { "Get status of a specific agent task by ID" }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "required": ["id"],
            "properties": {
                "id": { "type": "string", "description": "Agent task UUID" }
            }
        })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Read }

    async fn invoke(&self, params: Value, _ctx: &Context) -> Result<Value, ToolError> {
        let id = params["id"]
            .as_str()
            .ok_or_else(|| ToolError::invalid_param("id", "required string"))?;
        let task = self.client.get_task(id).await?;

        if !task.name.starts_with("agent_") {
            return Err(ToolError::not_found(format!("agent task '{id}'")));
        }

        serde_json::to_value(task)
            .map_err(|e| ToolError::invocation(format!("serialize task: {e}")))
    }
}

// ── agent_run ─────────────────────────────────────────────────────────────────

pub struct AgentRun;
impl AgentRun {
    pub fn new() -> Self { Self }
}
impl Default for AgentRun {
    fn default() -> Self { Self::new() }
}

#[async_trait]
impl Tool for AgentRun {
    fn name(&self) -> &'static str { "agent_run" }
    fn description(&self) -> &'static str {
        "Dispatch an agent taskserv via the orchestrator (creates an 'agent_<name>' job)"
    }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "required": ["infra", "settings", "agent"],
            "properties": {
                "infra":     { "type": "string", "description": "Infrastructure name" },
                "settings":  { "type": "string", "description": "Settings NCL path" },
                "agent":     { "type": "string", "description": "Agent taskserv name" },
                "operation": { "type": "string", "enum": ["create", "delete"], "default": "create" },
                "wait":      { "type": "boolean", "default": false }
            }
        })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Mutation }

    async fn invoke(&self, params: Value, ctx: &Context) -> Result<Value, ToolError> {
        let infra = params["infra"]
            .as_str()
            .ok_or_else(|| ToolError::invalid_param("infra", "required string"))?;
        let settings = params["settings"]
            .as_str()
            .ok_or_else(|| ToolError::invalid_param("settings", "required string"))?;
        let agent = params["agent"]
            .as_str()
            .ok_or_else(|| ToolError::invalid_param("agent", "required string"))?;
        let operation = params["operation"].as_str().unwrap_or("create");
        let wait = params["wait"].as_bool().unwrap_or(false);

        let body = json!({
            "infra":     infra,
            "settings":  settings,
            "taskserv":  format!("agent_{agent}"),
            "operation": operation,
            "check_mode": false,
            "wait":      wait,
        });

        let client = reqwest::Client::builder()
            .timeout(std::time::Duration::from_secs(10))
            .build()
            .map_err(|e| ToolError::invocation(format!("http client: {e}")))?;

        let url = format!("{}/workflows/taskserv/create", ctx.env.orchestrator_url);
        let resp = client
            .post(&url)
            .json(&body)
            .send()
            .await
            .map_err(|e| ToolError::invocation(format!("orchestrator unreachable: {e}")))?;

        let status = resp.status();
        if !status.is_success() {
            return Err(ToolError::invocation(format!(
                "orchestrator returned {status} for agent run"
            )));
        }

        let result: Value = resp
            .json()
            .await
            .map_err(|e| ToolError::invocation(format!("parse response: {e}")))?;

        Ok(json!({
            "agent":     agent,
            "operation": operation,
            "task_id":   result["data"],
            "status":    "queued",
        }))
    }
}
