//! Integration tests verifying that each feature-gated re-export module
//! resolves its public surface through the `klieo` umbrella crate.
//!
//! Compile with `--all-features` (or at least `all-foundation`) to run
//! the gated tests. The `core_only_compiles_and_resolves_traits` test
//! always runs regardless of features.

// ── always ───────────────────────────────────────────────────────────────────

#[test]
fn core_only_compiles_and_resolves_traits() {
    // Core trait surfaces must be reachable through the umbrella root
    // without enabling any feature flags.
    use klieo::{Error, RunOptions};

    let opts = RunOptions::default();
    assert_eq!(opts.max_steps, 16);
    assert_eq!(opts.max_history_tokens, 8_000);

    let err = Error::Cancelled;
    assert!(matches!(err, Error::Cancelled));
}

// ── bus-memory ────────────────────────────────────────────────────────────────

#[cfg(feature = "bus-memory")]
#[tokio::test]
async fn bus_memory_reexport_round_trips() {
    use klieo::bus_memory::MemoryBus;
    use klieo::{KvStore, Pubsub};
    use std::sync::Arc;

    let bus = MemoryBus::new();

    // Verify the fields implement the trait objects we expect.
    let _pubsub: Arc<dyn Pubsub> = bus.pubsub.clone();
    let _kv: Arc<dyn KvStore> = bus.kv.clone();

    // Basic round-trip through the KV store.
    bus.kv
        .put("bucket", "key", bytes::Bytes::from_static(b"val"))
        .await
        .unwrap();
    let entry = bus.kv.get("bucket", "key").await.unwrap().unwrap();
    assert_eq!(entry.value, bytes::Bytes::from_static(b"val"));
}

// ── memory-sqlite ─────────────────────────────────────────────────────────────

#[cfg(feature = "memory-sqlite")]
#[tokio::test]
async fn memory_sqlite_reexport_opens_in_memory_db() {
    use klieo::memory_sqlite::{DummyEmbedder, MemorySqlite};
    use std::sync::Arc;

    // Signature confirmed from klieo-memory-sqlite/src/factory.rs:
    //   MemorySqlite::new(path: impl AsRef<Path>, embedder: Arc<dyn Embedder>) -> Result<…>
    let mem = MemorySqlite::new(":memory:", Arc::new(DummyEmbedder))
        .await
        .unwrap();

    // All three handles must be non-null (just holding the Arc is enough).
    let _st = mem.short_term.clone();
    let _lt = mem.long_term.clone();
    let _ep = mem.episodic.clone();
}

// ── llm-ollama ────────────────────────────────────────────────────────────────

#[cfg(feature = "llm-ollama")]
#[test]
fn llm_ollama_reexport_constructs_default_client() {
    use klieo::llm::OllamaClient;
    use klieo::LlmClient;

    // Signature confirmed from klieo-llm-ollama/src/client.rs:
    //   OllamaClient::with_defaults() builds (localhost:11434, qwen2.5:14b)
    let client = OllamaClient::with_defaults();
    assert!(
        client.name().starts_with("ollama:"),
        "expected name to start with 'ollama:', got: {}",
        client.name()
    );
}

// ── mcp ───────────────────────────────────────────────────────────────────────

#[cfg(feature = "mcp")]
#[test]
fn mcp_reexport_namespaces_resolve() {
    use klieo::mcp::McpServerId;

    // McpServerId is a public type from klieo-tools-mcp.
    // McpServerId::new is a simple constructor; verify the module path resolves.
    let id = McpServerId::new("my-server");
    assert_eq!(id.0, "my-server");

    // McpToolset::connect_stdio is async so we can't take a fn pointer directly.
    // We just verify the type name is reachable at compile time.
    let _: fn() = || {
        // Type-check only — never called. Confirms McpToolset resolves.
        #[allow(unused_imports)]
        use klieo::mcp::McpToolset as _;
    };
}

// ── tools + macros ────────────────────────────────────────────────────────────

#[cfg(all(feature = "tools", feature = "macros"))]
#[test]
fn tools_and_macros_reexports_compile_together() {
    use klieo::tools::ChainedInvoker;
    // ToolInvoker must be in scope to call .catalogue() on ChainedInvoker.
    use klieo::ToolInvoker;

    // ChainedInvoker::new() constructs an empty invoker;
    // catalogue() returns the empty tool list.
    // Confirmed from klieo-tools/src/invoker.rs.
    let inv = ChainedInvoker::new();
    let cat = inv.catalogue();
    assert!(
        cat.is_empty(),
        "fresh invoker should have an empty catalogue"
    );

    // klieo::macros::tool is a proc-macro attribute, not a first-class fn value.
    // We use a compile-time-only type alias stub to verify the module path
    // resolves without attempting to use it as a value (which rustc rejects).
    // (The proc-macro is callable from user crates as #[klieo::macros::tool(...)];
    // the import below proves the path compiles.)
    #[allow(unused_imports)]
    use klieo::macros::tool as _klieo_tool_attr;
}
