//! Installer / settings tools — query, validate, defaults for platform config.
//!
//! Reads the platform configuration via `platform-config` crate.
//! Settings live at `PROVISIONING_CONFIG_DIR` / `config.toml` or defaults.

use crate::protocol::{ToolCategory, ToolError};
use crate::tool::{Context, Tool};
use async_trait::async_trait;
use serde_json::{Value, json};

const DEPLOYMENT_MODES: &[&str] = &["solo", "multiuser", "cicd", "enterprise"];

const REQUIRED_FIELDS: &[&str] = &[
    "orchestrator.port",
    "storage.backend",
    "vault.backend",
];

// ── installer_settings_get ────────────────────────────────────────────────────

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

#[async_trait]
impl Tool for InstallerSettingsGet {
    fn name(&self) -> &'static str { "installer_settings_get" }
    fn description(&self) -> &'static str {
        "Read current platform installer settings from PROVISIONING_CONFIG_DIR"
    }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "key": {
                    "type": "string",
                    "description": "Dot-notation key to read (e.g. 'orchestrator.port'). Omit for all."
                }
            }
        })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Read }

    async fn invoke(&self, params: Value, _ctx: &Context) -> Result<Value, ToolError> {
        let settings = load_platform_settings()?;
        if let Some(key) = params["key"].as_str() {
            let val = dot_get(&settings, key);
            Ok(json!({ "key": key, "value": val }))
        } else {
            Ok(settings)
        }
    }
}

// ── installer_settings_validate ───────────────────────────────────────────────

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

#[async_trait]
impl Tool for InstallerSettingsValidate {
    fn name(&self) -> &'static str { "installer_settings_validate" }
    fn description(&self) -> &'static str {
        "Validate that required platform settings are present and well-formed"
    }
    fn schema(&self) -> Value {
        json!({ "type": "object", "properties": {}, "additionalProperties": false })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Read }

    async fn invoke(&self, _params: Value, _ctx: &Context) -> Result<Value, ToolError> {
        let settings = match load_platform_settings() {
            Ok(s) => s,
            Err(e) => {
                return Ok(json!({
                    "valid": false,
                    "issues": [e.to_string()],
                }));
            }
        };

        let mut issues: Vec<String> = Vec::new();
        for field in REQUIRED_FIELDS {
            if dot_get(&settings, field).is_null() {
                issues.push(format!("missing required field: {field}"));
            }
        }

        Ok(json!({
            "valid":  issues.is_empty(),
            "issues": issues,
        }))
    }
}

// ── installer_settings_defaults ───────────────────────────────────────────────

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

#[async_trait]
impl Tool for InstallerSettingsDefaults {
    fn name(&self) -> &'static str { "installer_settings_defaults" }
    fn description(&self) -> &'static str {
        "Return mode-specific default settings (solo / multiuser / cicd / enterprise)"
    }
    fn schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "mode": {
                    "type": "string",
                    "enum": ["solo", "multiuser", "cicd", "enterprise"],
                    "default": "solo"
                }
            }
        })
    }
    fn category(&self) -> ToolCategory { ToolCategory::Read }

    async fn invoke(&self, params: Value, _ctx: &Context) -> Result<Value, ToolError> {
        let mode = params["mode"].as_str().unwrap_or("solo");
        if !DEPLOYMENT_MODES.contains(&mode) {
            return Err(ToolError::invalid_param(
                "mode",
                format!("unknown mode '{mode}'; expected one of: {}", DEPLOYMENT_MODES.join(", ")),
            ));
        }
        Ok(defaults_for(mode))
    }
}

// ── Helpers ───────────────────────────────────────────────────────────────────

fn load_platform_settings() -> Result<Value, ToolError> {
    let config_dir = std::env::var("PROVISIONING_CONFIG_DIR")
        .map(std::path::PathBuf::from)
        .unwrap_or_else(|_| {
            dirs::config_dir()
                .unwrap_or_else(|| std::path::PathBuf::from("."))
                .join("provisioning")
                .join("platform")
        });

    // Try TOML, then JSON
    for name in &["config.toml", "orchestrator.toml", "orchestrator.json"] {
        let path = config_dir.join(name);
        if path.exists() {
            let content = std::fs::read_to_string(&path)
                .map_err(|e| ToolError::invocation(format!("read {}: {e}", path.display())))?;
            if path.extension().and_then(|e| e.to_str()) == Some("toml") {
                let t: toml::Value = toml::from_str(&content)
                    .map_err(|e| ToolError::invocation(format!("parse TOML: {e}")))?;
                return toml_to_json(t);
            }
            return serde_json::from_str(&content)
                .map_err(|e| ToolError::invocation(format!("parse JSON: {e}")));
        }
    }

    Ok(defaults_for("solo"))
}

fn dot_get(obj: &Value, key: &str) -> Value {
    let mut cur = obj;
    for part in key.split('.') {
        match cur.get(part) {
            Some(v) => cur = v,
            None => return Value::Null,
        }
    }
    cur.clone()
}

fn toml_to_json(val: toml::Value) -> Result<Value, ToolError> {
    serde_json::to_value(val)
        .map_err(|e| ToolError::invocation(format!("convert TOML→JSON: {e}")))
}

fn defaults_for(mode: &str) -> Value {
    match mode {
        "solo" => json!({
            "mode": "solo",
            "orchestrator": { "port": 9011, "workers": 2 },
            "storage": { "backend": "filesystem", "path": "./data" },
            "vault": { "backend": "age" },
            "auth": { "solo_mode": true },
            "nats": { "enabled": false },
        }),
        "multiuser" => json!({
            "mode": "multiuser",
            "orchestrator": { "port": 9011, "workers": 4 },
            "storage": { "backend": "surrealdb" },
            "vault": { "backend": "rustyvault" },
            "auth": { "solo_mode": false, "jwt_required": true },
            "nats": { "enabled": true, "url": "nats://localhost:4222" },
        }),
        "cicd" => json!({
            "mode": "cicd",
            "orchestrator": { "port": 9011, "workers": 8 },
            "storage": { "backend": "surrealdb" },
            "vault": { "backend": "aws_kms" },
            "auth": { "solo_mode": false, "jwt_required": true },
            "nats": { "enabled": true },
        }),
        "enterprise" => json!({
            "mode": "enterprise",
            "orchestrator": { "port": 9011, "workers": 16 },
            "storage": { "backend": "surrealdb", "ha": true },
            "vault": { "backend": "cosmian" },
            "auth": { "solo_mode": false, "jwt_required": true, "mfa": true },
            "nats": { "enabled": true, "cluster": true },
        }),
        other => json!({ "mode": other, "error": "unknown mode" }),
    }
}
