use crate::util_helpers::MaybeSet;
use async_trait::async_trait;
use serde_json::{Value, json};
use std::fs;
use std::sync::Arc;
use zeroclaw_api::tool::{Tool, ToolOutput, ToolResult};
use zeroclaw_config::policy::SecurityPolicy;
use zeroclaw_config::schema::{
    Config, ProxyConfig, ProxyScope, runtime_proxy_config, set_runtime_proxy_config,
};

pub struct ProxyConfigTool {
    config: Arc<Config>,
    security: Arc<SecurityPolicy>,
}

impl ProxyConfigTool {
    pub fn new(config: Arc<Config>, security: Arc<SecurityPolicy>) -> Self {
        Self { config, security }
    }

    fn load_config_without_env(&self) -> anyhow::Result<Config> {
        let contents = fs::read_to_string(&self.config.config_path).map_err(|error| {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(::serde_json::json!({
                        "path": self.config.config_path.display().to_string(),
                        "error": format!("{}", error),
                    })),
                "proxy_config: failed to read config file"
            );
            anyhow::Error::msg(format!(
                "Failed to read config file {}: {error}",
                self.config.config_path.display()
            ))
        })?;

        let mut parsed: Config = toml::from_str(&contents).map_err(|error| {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                    .with_attrs(::serde_json::json!({
                        "path": self.config.config_path.display().to_string(),
                        "error": format!("{}", error),
                    })),
                "proxy_config: failed to parse config file"
            );
            anyhow::Error::msg(format!(
                "Failed to parse config file {}: {error}",
                self.config.config_path.display()
            ))
        })?;
        parsed.config_path = self.config.config_path.clone();
        parsed.data_dir = self.config.data_dir.clone();
        Ok(parsed)
    }

    fn require_write_access(&self) -> Option<ToolResult> {
        if !self.security.can_act() {
            return Some(ToolResult {
                success: false,
                output: ToolOutput::default(),
                error: Some("Action blocked: autonomy is read-only".into()),
            });
        }

        if !self.security.record_action() {
            return Some(ToolResult {
                success: false,
                output: ToolOutput::default(),
                error: Some("Action blocked: rate limit exceeded".into()),
            });
        }

        None
    }

    fn parse_scope(raw: &str) -> Option<ProxyScope> {
        match raw.trim().to_ascii_lowercase().as_str() {
            "environment" | "env" => Some(ProxyScope::Environment),
            "zeroclaw" | "internal" | "core" => Some(ProxyScope::Zeroclaw),
            "services" | "service" => Some(ProxyScope::Services),
            _ => None,
        }
    }

    fn parse_string_list(raw: &Value, field: &str) -> anyhow::Result<Vec<String>> {
        if let Some(raw_string) = raw.as_str() {
            return Ok(raw_string
                .split(',')
                .map(str::trim)
                .filter(|entry| !entry.is_empty())
                .map(ToOwned::to_owned)
                .collect());
        }

        if let Some(array) = raw.as_array() {
            let mut out = Vec::new();
            for item in array {
                let value = item.as_str().ok_or_else(|| {
                    ::zeroclaw_log::record!(
                        WARN,
                        ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                            .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                            .with_attrs(::serde_json::json!({"field": field})),
                        "proxy_config: array element must be a string"
                    );
                    anyhow::Error::msg(format!("'{field}' array must only contain strings"))
                })?;
                let trimmed = value.trim();
                if !trimmed.is_empty() {
                    out.push(trimmed.to_string());
                }
            }
            return Ok(out);
        }

        anyhow::bail!("'{field}' must be a string or string[]")
    }

    fn parse_optional_string_update(args: &Value, field: &str) -> anyhow::Result<MaybeSet<String>> {
        let Some(raw) = args.get(field) else {
            return Ok(MaybeSet::Unset);
        };

        if raw.is_null() {
            return Ok(MaybeSet::Null);
        }

        let value = raw
            .as_str()
            .ok_or_else(|| {
                ::zeroclaw_log::record!(
                    WARN,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                        .with_attrs(::serde_json::json!({"field": field})),
                    "proxy_config: field must be a string or null"
                );
                anyhow::Error::msg(format!("'{field}' must be a string or null"))
            })?
            .trim()
            .to_string();

        let output = if value.is_empty() {
            MaybeSet::Null
        } else {
            MaybeSet::Set(value)
        };
        Ok(output)
    }

    fn env_snapshot() -> Value {
        json!({
            "HTTP_PROXY": std::env::var("HTTP_PROXY").ok(),
            "HTTPS_PROXY": std::env::var("HTTPS_PROXY").ok(),
            "ALL_PROXY": std::env::var("ALL_PROXY").ok(),
            "NO_PROXY": std::env::var("NO_PROXY").ok(),
        })
    }

    fn proxy_json(proxy: &ProxyConfig) -> Value {
        json!({
            "enabled": proxy.enabled,
            "scope": proxy.scope,
            "http_proxy": proxy.http_proxy,
            "https_proxy": proxy.https_proxy,
            "all_proxy": proxy.all_proxy,
            "no_proxy": proxy.normalized_no_proxy(),
            "services": proxy.normalized_services(),
        })
    }

    fn handle_get(&self) -> anyhow::Result<ToolResult> {
        let file_proxy = self.load_config_without_env()?.proxy;
        let runtime_proxy = runtime_proxy_config();
        Ok(ToolResult {
            success: true,
            output: serde_json::to_string_pretty(&json!({
                "proxy": Self::proxy_json(&file_proxy),
                "runtime_proxy": Self::proxy_json(&runtime_proxy),
                "environment": Self::env_snapshot(),
            }))?
            .into(),
            error: None,
        })
    }

    fn handle_list_services(&self) -> anyhow::Result<ToolResult> {
        Ok(ToolResult {
            success: true,
            output: serde_json::to_string_pretty(&json!({
                "supported_service_keys": ProxyConfig::supported_service_keys(),
                "supported_selectors": ProxyConfig::supported_service_selectors(),
                "usage_example": {
                    "action": "set",
                    "scope": "services",
                    "services": ["model_provider.openai", "tool.http_request", "channel.telegram"]
                }
            }))?
            .into(),
            error: None,
        })
    }

    async fn handle_set(&self, args: &Value) -> anyhow::Result<ToolResult> {
        let mut cfg = self.load_config_without_env()?;
        let previous_scope = cfg.proxy.scope;
        let mut proxy = cfg.proxy.clone();
        let mut touched_proxy_url = false;

        if let Some(enabled) = args.get("enabled") {
            proxy.enabled = enabled.as_bool().ok_or_else(|| {
                ::zeroclaw_log::record!(
                    WARN,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                        .with_attrs(::serde_json::json!({"param": "enabled"})),
                    "proxy_config: enabled must be boolean"
                );
                anyhow::Error::msg("'enabled' must be a boolean")
            })?;
        }

        if let Some(scope_raw) = args.get("scope") {
            let scope = scope_raw.as_str().ok_or_else(|| {
                ::zeroclaw_log::record!(
                    WARN,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                        .with_attrs(::serde_json::json!({"param": "scope"})),
                    "proxy_config: scope must be string"
                );
                anyhow::Error::msg("'scope' must be a string")
            })?;
            proxy.scope = Self::parse_scope(scope).ok_or_else(|| {
                ::zeroclaw_log::record!(
                    WARN,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                        .with_attrs(::serde_json::json!({"scope": scope})),
                    "proxy_config: invalid scope"
                );
                anyhow::Error::msg(format!(
                    "Invalid scope '{scope}'. Use environment|zeroclaw|services"
                ))
            })?;
        }

        match Self::parse_optional_string_update(args, "http_proxy")? {
            MaybeSet::Set(update) => {
                proxy.http_proxy = Some(update);
                touched_proxy_url = true;
            }
            MaybeSet::Null => {
                proxy.http_proxy = None;
                touched_proxy_url = true;
            }
            MaybeSet::Unset => {}
        }

        match Self::parse_optional_string_update(args, "https_proxy")? {
            MaybeSet::Set(update) => {
                proxy.https_proxy = Some(update);
                touched_proxy_url = true;
            }
            MaybeSet::Null => {
                proxy.https_proxy = None;
                touched_proxy_url = true;
            }
            MaybeSet::Unset => {}
        }

        match Self::parse_optional_string_update(args, "all_proxy")? {
            MaybeSet::Set(update) => {
                proxy.all_proxy = Some(update);
                touched_proxy_url = true;
            }
            MaybeSet::Null => {
                proxy.all_proxy = None;
                touched_proxy_url = true;
            }
            MaybeSet::Unset => {}
        }

        if let Some(no_proxy_raw) = args.get("no_proxy") {
            proxy.no_proxy = Self::parse_string_list(no_proxy_raw, "no_proxy")?;
            touched_proxy_url = true;
        }

        if let Some(services_raw) = args.get("services") {
            proxy.services = Self::parse_string_list(services_raw, "services")?;
        }

        if args.get("enabled").is_none() && touched_proxy_url {
            // Keep auto-enable behavior when users provide a proxy URL, but
            // auto-disable when all proxy URLs are cleared in the same update.
            proxy.enabled = proxy.has_any_proxy_url();
        }

        proxy.no_proxy = proxy.normalized_no_proxy();
        proxy.services = proxy.normalized_services();
        proxy.validate()?;

        cfg.proxy = proxy.clone();
        cfg.save().await?;
        set_runtime_proxy_config(proxy.clone());

        if proxy.enabled && proxy.scope == ProxyScope::Environment {
            proxy.apply_to_process_env();
        } else if previous_scope == ProxyScope::Environment {
            ProxyConfig::clear_process_env();
        }

        Ok(ToolResult {
            success: true,
            output: serde_json::to_string_pretty(&json!({
                "message": "Proxy configuration updated",
                "proxy": Self::proxy_json(&proxy),
                "environment": Self::env_snapshot(),
            }))?
            .into(),
            error: None,
        })
    }

    async fn handle_disable(&self, args: &Value) -> anyhow::Result<ToolResult> {
        let mut cfg = self.load_config_without_env()?;
        let clear_env_default = cfg.proxy.scope == ProxyScope::Environment;
        cfg.proxy.enabled = false;
        cfg.save().await?;

        set_runtime_proxy_config(cfg.proxy.clone());

        let clear_env = args
            .get("clear_env")
            .and_then(Value::as_bool)
            .unwrap_or(clear_env_default);
        if clear_env {
            ProxyConfig::clear_process_env();
        }

        Ok(ToolResult {
            success: true,
            output: serde_json::to_string_pretty(&json!({
                "message": "Proxy disabled",
                "proxy": Self::proxy_json(&cfg.proxy),
                "environment": Self::env_snapshot(),
            }))?
            .into(),
            error: None,
        })
    }

    fn handle_apply_env(&self) -> anyhow::Result<ToolResult> {
        let cfg = self.load_config_without_env()?;
        let proxy = cfg.proxy;
        proxy.validate()?;

        if !proxy.enabled {
            anyhow::bail!("Proxy is disabled. Use action 'set' with enabled=true first");
        }

        if proxy.scope != ProxyScope::Environment {
            anyhow::bail!(
                "apply_env only works when proxy.scope is 'environment' (current: {:?})",
                proxy.scope
            );
        }

        proxy.apply_to_process_env();
        set_runtime_proxy_config(proxy.clone());

        Ok(ToolResult {
            success: true,
            output: serde_json::to_string_pretty(&json!({
                "message": "Proxy environment variables applied",
                "proxy": Self::proxy_json(&proxy),
                "environment": Self::env_snapshot(),
            }))?
            .into(),
            error: None,
        })
    }

    fn handle_clear_env(&self) -> anyhow::Result<ToolResult> {
        ProxyConfig::clear_process_env();
        Ok(ToolResult {
            success: true,
            output: serde_json::to_string_pretty(&json!({
                "message": "Proxy environment variables cleared",
                "environment": Self::env_snapshot(),
            }))?
            .into(),
            error: None,
        })
    }
}

#[async_trait]
impl Tool for ProxyConfigTool {
    fn name(&self) -> &str {
        "proxy_config"
    }

    fn description(&self) -> &str {
        "Manage ZeroClaw proxy settings (scope: environment | zeroclaw | services), including runtime and process env application"
    }

    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["get", "set", "disable", "list_services", "apply_env", "clear_env"],
                    "default": "get"
                },
                "enabled": {
                    "type": "boolean",
                    "description": "Enable or disable proxy"
                },
                "scope": {
                    "type": "string",
                    "description": "Proxy scope: environment | zeroclaw | services"
                },
                "http_proxy": {
                    "type": ["string", "null"],
                    "description": "HTTP proxy URL"
                },
                "https_proxy": {
                    "type": ["string", "null"],
                    "description": "HTTPS proxy URL"
                },
                "all_proxy": {
                    "type": ["string", "null"],
                    "description": "Fallback proxy URL for all protocols"
                },
                "no_proxy": {
                    "description": "Comma-separated string or array of NO_PROXY entries",
                    "oneOf": [
                        {"type": "string"},
                        {"type": "array", "items": {"type": "string"}}
                    ]
                },
                "services": {
                    "description": "Comma-separated string or array of service selectors used when scope=services",
                    "oneOf": [
                        {"type": "string"},
                        {"type": "array", "items": {"type": "string"}}
                    ]
                },
                "clear_env": {
                    "type": "boolean",
                    "description": "When action=disable, clear process proxy environment variables"
                }
            }
        })
    }

    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
        let action = args
            .get("action")
            .and_then(Value::as_str)
            .unwrap_or("get")
            .to_ascii_lowercase();

        let result = match action.as_str() {
            "get" => self.handle_get(),
            "list_services" => self.handle_list_services(),
            "set" | "disable" | "apply_env" | "clear_env" => {
                if let Some(blocked) = self.require_write_access() {
                    return Ok(blocked);
                }

                match action.as_str() {
                    "set" => Box::pin(self.handle_set(&args)).await,
                    "disable" => Box::pin(self.handle_disable(&args)).await,
                    "apply_env" => self.handle_apply_env(),
                    "clear_env" => self.handle_clear_env(),
                    _ => unreachable!("handled above"),
                }
            }
            _ => anyhow::bail!(
                "Unknown action '{action}'. Valid: get, set, disable, list_services, apply_env, clear_env"
            ),
        };

        match result {
            Ok(outcome) => Ok(outcome),
            Err(error) => Ok(ToolResult {
                success: false,
                output: ToolOutput::default(),
                error: Some(error.to_string()),
            }),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;
    use zeroclaw_config::autonomy::AutonomyLevel;
    use zeroclaw_config::policy::SecurityPolicy;

    fn test_security() -> Arc<SecurityPolicy> {
        Arc::new(SecurityPolicy {
            autonomy: AutonomyLevel::Supervised,
            workspace_dir: std::env::temp_dir(),
            ..SecurityPolicy::default()
        })
    }

    async fn test_config(tmp: &TempDir) -> Arc<Config> {
        let config = Config {
            data_dir: tmp.path().join("data"),
            config_path: tmp.path().join("config.toml"),
            ..Config::default()
        };
        config.save().await.unwrap();
        Arc::new(config)
    }

    #[tokio::test]
    async fn list_services_action_returns_known_keys() {
        let tmp = TempDir::new().unwrap();
        let tool = ProxyConfigTool::new(Box::pin(test_config(&tmp)).await, test_security());

        let result = tool
            .execute(json!({"action": "list_services"}))
            .await
            .unwrap();
        assert!(result.success);
        assert!(result.output.contains("model_provider.openai"));
        assert!(result.output.contains("tool.http_request"));
    }

    #[tokio::test]
    async fn set_scope_services_requires_services_entries() {
        let tmp = TempDir::new().unwrap();
        let tool = ProxyConfigTool::new(Box::pin(test_config(&tmp)).await, test_security());

        let result = tool
            .execute(json!({
                "action": "set",
                "enabled": true,
                "scope": "services",
                "http_proxy": "http://127.0.0.1:7890",
                "services": []
            }))
            .await
            .unwrap();

        assert!(!result.success);
        assert!(
            result
                .error
                .unwrap_or_default()
                .contains("proxy.scope='services'")
        );
    }

    #[tokio::test]
    async fn set_and_get_round_trip_proxy_scope() {
        let tmp = TempDir::new().unwrap();
        let tool = ProxyConfigTool::new(Box::pin(test_config(&tmp)).await, test_security());

        let set_result = tool
            .execute(json!({
                "action": "set",
                "scope": "services",
                "http_proxy": "http://127.0.0.1:7890",
                "services": ["model_provider.openai", "tool.http_request"]
            }))
            .await
            .unwrap();
        assert!(set_result.success, "{:?}", set_result.error);

        let get_result = tool.execute(json!({"action": "get"})).await.unwrap();
        assert!(get_result.success);
        assert!(get_result.output.contains("model_provider.openai"));
        assert!(get_result.output.contains("services"));
    }

    #[tokio::test]
    async fn set_null_proxy_url_clears_existing_value() {
        let tmp = TempDir::new().unwrap();
        let tool = ProxyConfigTool::new(Box::pin(test_config(&tmp)).await, test_security());

        let set_result = tool
            .execute(json!({
                "action": "set",
                "http_proxy": "http://127.0.0.1:7890"
            }))
            .await
            .unwrap();
        assert!(set_result.success, "{:?}", set_result.error);

        let clear_result = tool
            .execute(json!({
                "action": "set",
                "http_proxy": null
            }))
            .await
            .unwrap();
        assert!(clear_result.success, "{:?}", clear_result.error);

        let get_result = tool.execute(json!({"action": "get"})).await.unwrap();
        assert!(get_result.success);
        let parsed: Value = serde_json::from_str(&get_result.output).unwrap();
        assert!(parsed["proxy"]["http_proxy"].is_null());
        assert!(parsed["runtime_proxy"]["http_proxy"].is_null());
    }
}
