//! End-to-end proof for manifest-declared tools (EPIC 12.11): a plugin that
//! ships a `[[tools]]` entry is discovered, registered as a native MCP tool
//! **without forking** `build_registry()`, and invocable as a sandboxed
//! subprocess. Own integration-test binary so the global plugin registry
//! (`OnceLock`) is fresh. Unix-only: the tool is the `cat` echo binary.

#![cfg(unix)]

use std::fs;

use lean_ctx::core::plugins::PluginManager;
use lean_ctx::core::plugins::tools::invoke;
use lean_ctx::server::registry::build_registry;

#[test]
fn manifest_tool_is_discovered_registered_and_invocable() {
    let root = tempfile::tempdir().expect("tempdir");
    let plugin_dir = root.path().join("weather-plugin");
    fs::create_dir_all(&plugin_dir).expect("plugin dir");

    // `cat` echoes the JSON args we pass on stdin straight back as the result —
    // a real subprocess round-trip with no mock layer.
    fs::write(
        plugin_dir.join("plugin.toml"),
        "[plugin]\nname = \"weather-plugin\"\nversion = \"1.0.0\"\n\n\
         [[tools]]\nname = \"weather_lookup\"\n\
         description = \"Look up the weather\"\ncommand = \"cat\"\ntimeout_ms = 5000\n\
         input_schema = { type = \"object\", properties = { city = { type = \"string\" } } }\n",
    )
    .expect("manifest");

    // TODO: Audit that the environment access only happens in single-threaded code.
    unsafe { std::env::set_var("LEAN_CTX_PLUGINS_DIR", root.path()) };
    PluginManager::init();

    // 1) Discovered from the manifest (no fork).
    let specs = PluginManager::tool_specs();
    let spec = specs
        .iter()
        .find(|s| s.name == "weather_lookup")
        .expect("manifest tool should be discovered");
    assert_eq!(spec.plugin_name, "weather-plugin");

    // 2) Appears in the native tool surface (gateway / list_tools).
    let registry = build_registry();
    assert!(
        registry.contains("weather_lookup"),
        "manifest tool must register as a native MCP tool without a code edit"
    );

    // 3) Runs sandboxed: the subprocess echoes our arguments back.
    let out = invoke(spec, "{\"city\":\"Bern\"}").expect("tool invocation");
    assert!(out.contains("Bern"), "tool output should echo args: {out}");
}
