//! Server domain tools — list, show, create, delete.
//!
//! Read operations use the injected `HcloudClient` (hcloud CLI over JSON output).
//! Create is enqueued via the orchestrator workflow API (`POST /workflows/servers/create`).
//! Delete is a synchronous Destructive call through the hcloud CLI — the orchestrator
//! has no server-delete workflow endpoint.

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

// ── server_list ───────────────────────────────────────────────────────────────

pub struct ServerList {
    hcloud: Arc<HcloudClient>,
}
impl ServerList {
    pub fn new(hcloud: Arc<HcloudClient>) -> Self { Self { hcloud } }
}

#[async_trait]
impl Tool for ServerList {
    fn name(&self) -> &'static str { "server_list" }
    fn description(&self) -> &'static str { "List all Hetzner Cloud servers in the active context" }
    fn schema(&self) -> Value {
        json!({ "type": "object", "properties": {} })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Read }

    async fn invoke(&self, _params: Value, _ctx: &Context) -> Result<Value, ToolError> {
        let servers = self.hcloud.server_list().await?;
        let items = servers.as_array().cloned().unwrap_or_default();
        Ok(json!({ "items": items, "total": items.len() }))
    }
}

// ── server_show ───────────────────────────────────────────────────────────────

pub struct ServerShow {
    hcloud: Arc<HcloudClient>,
}
impl ServerShow {
    pub fn new(hcloud: Arc<HcloudClient>) -> Self { Self { hcloud } }
}

#[async_trait]
impl Tool for ServerShow {
    fn name(&self) -> &'static str { "server_show" }
    fn description(&self) -> &'static str { "Show details of a specific Hetzner Cloud server" }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "required": ["name"],
            "properties": {
                "name": { "type": "string", "description": "Server name or ID" }
            }
        })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Read }

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

// ── server_create ─────────────────────────────────────────────────────────────

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

#[async_trait]
impl Tool for ServerCreate {
    fn name(&self) -> &'static str { "server_create" }
    fn description(&self) -> &'static str {
        "Enqueue a server creation workflow via the orchestrator"
    }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "required": ["infra", "settings"],
            "properties": {
                "infra":     { "type": "string", "description": "Infrastructure name" },
                "settings":  { "type": "string", "description": "Settings NCL path" },
                "servers":   {
                    "type": "array",
                    "items": { "type": "string" },
                    "default": [],
                    "description": "Specific server names; empty means all in infra"
                },
                "check_mode": { "type": "boolean", "default": false },
                "wait":       { "type": "boolean", "default": true }
            }
        })
    }
    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 servers: Vec<String> = params["servers"]
            .as_array()
            .map(|a| a.iter().filter_map(|v| v.as_str().map(str::to_owned)).collect())
            .unwrap_or_default();
        let check_mode = params["check_mode"].as_bool().unwrap_or(false);
        let wait = params["wait"].as_bool().unwrap_or(true);

        let body = json!({
            "infra":      infra,
            "settings":   settings,
            "servers":    servers,
            "check_mode": check_mode,
            "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/servers/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 server create"
            )));
        }

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

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

// ── server_delete ─────────────────────────────────────────────────────────────

pub struct ServerDelete {
    hcloud: Arc<HcloudClient>,
}
impl ServerDelete {
    pub fn new(hcloud: Arc<HcloudClient>) -> Self { Self { hcloud } }
}

#[async_trait]
impl Tool for ServerDelete {
    fn name(&self) -> &'static str { "server_delete" }
    fn description(&self) -> &'static str {
        "Delete a Hetzner Cloud server (irreversible — disables protection first)"
    }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "required": ["name"],
            "properties": {
                "name": { "type": "string", "description": "Server name or ID" }
            }
        })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Destructive }

    async fn invoke(&self, params: Value, _ctx: &Context) -> Result<Value, ToolError> {
        let name = params["name"]
            .as_str()
            .ok_or_else(|| ToolError::invalid_param("name", "required string"))?;
        self.hcloud.server_delete(name).await?;
        Ok(json!({ "name": name, "deleted": true }))
    }
}
