use super::*;
use crate::openhuman::security::{AutonomyLevel, SecurityPolicy};
use tempfile::TempDir;

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 {
        workspace_dir: tmp.path().join("workspace"),
        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(test_config(&tmp).await, test_security());

    let result = tool
        .execute(json!({"action": "list_services"}))
        .await
        .unwrap();
    assert!(!result.is_error);
    assert!(result.output().contains("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(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.is_error);
    assert!(result.output().contains("proxy.scope='services'"));
}

#[tokio::test]
async fn set_and_get_round_trip_proxy_scope() {
    let tmp = TempDir::new().unwrap();
    let tool = ProxyConfigTool::new(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": ["provider.openai", "tool.http_request"]
        }))
        .await
        .unwrap();
    assert!(!set_result.is_error, "{:?}", set_result.output());

    let get_result = tool.execute(json!({"action": "get"})).await.unwrap();
    assert!(!get_result.is_error);
    assert!(get_result.output().contains("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(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.is_error, "{:?}", set_result.output());

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

    let get_result = tool.execute(json!({"action": "get"})).await.unwrap();
    assert!(!get_result.is_error);
    let parsed: Value = serde_json::from_str(&get_result.output()).unwrap();
    // Only assert the *configured* proxy is cleared. `runtime_proxy.http_proxy`
    // resolves through the process env (HTTP_PROXY / http_proxy) when the
    // configured value is null, so on runners with those vars set the resolved
    // field is non-null and unrelated to whether `set` cleared the config.
    assert!(parsed["proxy"]["http_proxy"].is_null());
}

// ── parse_scope ──────────────────────────────────────────────────

#[test]
fn parse_scope_known_values() {
    assert_eq!(
        ProxyConfigTool::parse_scope("environment"),
        Some(ProxyScope::Environment)
    );
    assert_eq!(
        ProxyConfigTool::parse_scope("env"),
        Some(ProxyScope::Environment)
    );
    assert_eq!(
        ProxyConfigTool::parse_scope("openhuman"),
        Some(ProxyScope::OpenHuman)
    );
    assert_eq!(
        ProxyConfigTool::parse_scope("internal"),
        Some(ProxyScope::OpenHuman)
    );
    assert_eq!(
        ProxyConfigTool::parse_scope("core"),
        Some(ProxyScope::OpenHuman)
    );
    assert_eq!(
        ProxyConfigTool::parse_scope("services"),
        Some(ProxyScope::Services)
    );
    assert_eq!(
        ProxyConfigTool::parse_scope("service"),
        Some(ProxyScope::Services)
    );
}

#[test]
fn parse_scope_case_insensitive() {
    assert_eq!(
        ProxyConfigTool::parse_scope("SERVICES"),
        Some(ProxyScope::Services)
    );
    assert_eq!(
        ProxyConfigTool::parse_scope("  ENV  "),
        Some(ProxyScope::Environment)
    );
}

#[test]
fn parse_scope_unknown_returns_none() {
    assert!(ProxyConfigTool::parse_scope("unknown").is_none());
    assert!(ProxyConfigTool::parse_scope("").is_none());
}

// ── parse_string_list ────────────────────────────────────────────

#[test]
fn parse_string_list_from_csv() {
    let result =
        ProxyConfigTool::parse_string_list(&json!("provider.openai,tool.browser"), "services")
            .unwrap();
    assert_eq!(result, vec!["provider.openai", "tool.browser"]);
}

#[test]
fn parse_string_list_from_array() {
    let result =
        ProxyConfigTool::parse_string_list(&json!(["provider.openai", "tool.browser"]), "services")
            .unwrap();
    assert_eq!(result, vec!["provider.openai", "tool.browser"]);
}

#[test]
fn parse_string_list_trims_and_filters_empty() {
    let result = ProxyConfigTool::parse_string_list(&json!("  a , , b  "), "services").unwrap();
    assert_eq!(result, vec!["a", "b"]);
}

#[test]
fn parse_string_list_rejects_non_string_array_elements() {
    let result = ProxyConfigTool::parse_string_list(&json!([1, 2, 3]), "services");
    assert!(result.is_err());
}

#[test]
fn parse_string_list_rejects_object() {
    let result = ProxyConfigTool::parse_string_list(&json!({}), "services");
    assert!(result.is_err());
}

// ── parse_optional_string_update ─────────────────────────────────

#[test]
fn parse_optional_string_update_unset() {
    let result = ProxyConfigTool::parse_optional_string_update(&json!({}), "http_proxy").unwrap();
    assert!(matches!(result, MaybeSet::Unset));
}

#[test]
fn parse_optional_string_update_null() {
    let result =
        ProxyConfigTool::parse_optional_string_update(&json!({"http_proxy": null}), "http_proxy")
            .unwrap();
    assert!(matches!(result, MaybeSet::Null));
}

#[test]
fn parse_optional_string_update_empty_string_is_null() {
    let result =
        ProxyConfigTool::parse_optional_string_update(&json!({"http_proxy": ""}), "http_proxy")
            .unwrap();
    assert!(matches!(result, MaybeSet::Null));
}

#[test]
fn parse_optional_string_update_set() {
    let result = ProxyConfigTool::parse_optional_string_update(
        &json!({"http_proxy": "http://proxy:8080"}),
        "http_proxy",
    )
    .unwrap();
    assert!(matches!(result, MaybeSet::Set(ref v) if v == "http://proxy:8080"));
}

#[test]
fn parse_optional_string_update_rejects_non_string() {
    let result =
        ProxyConfigTool::parse_optional_string_update(&json!({"http_proxy": 42}), "http_proxy");
    assert!(result.is_err());
}

// ── env_snapshot ─────────────────────────────────────────────────

#[test]
fn env_snapshot_returns_object() {
    let snap = ProxyConfigTool::env_snapshot();
    assert!(snap.is_object());
    assert!(snap.get("HTTP_PROXY").is_some());
    assert!(snap.get("HTTPS_PROXY").is_some());
}

// ── proxy_json ───────────────────────────────────────────────────

#[test]
fn proxy_json_returns_object_with_expected_fields() {
    let config = ProxyConfig::default();
    let json = ProxyConfigTool::proxy_json(&config);
    assert!(json.get("enabled").is_some());
    assert!(json.get("scope").is_some());
    assert!(json.get("http_proxy").is_some());
}

// ── tool metadata ────────────────────────────────────────────────

#[test]
fn tool_name_and_description() {
    let tmp = TempDir::new().unwrap();
    let tool = ProxyConfigTool::new(
        Arc::new(Config {
            workspace_dir: tmp.path().to_path_buf(),
            config_path: tmp.path().join("config.toml"),
            ..Config::default()
        }),
        test_security(),
    );
    assert_eq!(tool.name(), "proxy_config");
    assert!(!tool.description().is_empty());
}

#[tokio::test]
async fn parameters_schema_is_valid() {
    let tmp = TempDir::new().unwrap();
    let tool = ProxyConfigTool::new(test_config(&tmp).await, test_security());
    let schema = tool.parameters_schema();
    assert!(schema.is_object());
    assert!(schema.get("properties").is_some() || schema.get("type").is_some());
}

// ── require_write_access ─────────────────────────────────────────

#[tokio::test]
async fn blocks_set_in_readonly_mode() {
    let tmp = TempDir::new().unwrap();
    let readonly = Arc::new(SecurityPolicy {
        autonomy: AutonomyLevel::ReadOnly,
        ..SecurityPolicy::default()
    });
    let tool = ProxyConfigTool::new(test_config(&tmp).await, readonly);
    let result = tool
        .execute(json!({"action": "set", "enabled": true}))
        .await
        .unwrap();
    assert!(result.is_error);
    assert!(result.output().contains("read-only"));
}

#[tokio::test]
async fn missing_action_returns_error() {
    let tmp = TempDir::new().unwrap();
    let tool = ProxyConfigTool::new(test_config(&tmp).await, test_security());
    let result = tool.execute(json!({})).await;
    // Missing action may return Err or ToolResult::error
    match result {
        Err(_) => {}
        Ok(r) => {
            // Some implementations return success with help text; just verify it ran
            let _ = r;
        }
    }
}

#[tokio::test]
async fn unknown_action_returns_error() {
    let tmp = TempDir::new().unwrap();
    let tool = ProxyConfigTool::new(test_config(&tmp).await, test_security());
    let result = tool.execute(json!({"action": "delete"})).await;
    match result {
        Err(e) => assert!(e.to_string().contains("Unknown action")),
        Ok(r) => assert!(r.is_error, "expected error for unknown action"),
    }
}
