//! Parse-time validation of the shipped `nexo-plugin.toml`.
//!
//! Phase voice-plugin-extract Fase B / Fase N — guards that every
//! manifest section the plan declares parses cleanly via the same
//! `PluginManifest::from_str` the daemon's discovery walker uses.
//! Without this test a typo in the manifest only surfaces when the
//! daemon tries to spawn us at boot.

use std::str::FromStr;

use nexo_plugin_manifest::PluginManifest;
use nexo_plugin_voice::MANIFEST;

#[test]
fn embedded_manifest_parses() {
    let m = PluginManifest::from_str(MANIFEST).expect("manifest TOML parse");
    assert_eq!(m.plugin.id, "voice");
    assert_eq!(m.plugin.version.to_string(), "0.3.1");
}

#[test]
fn exposed_tool_names_all_start_with_voice_prefix() {
    let m = PluginManifest::from_str(MANIFEST).unwrap();
    for tool in &m.plugin.tools.expose {
        assert!(
            tool.starts_with("voice_"),
            "tool `{tool}` must start with `voice_` per validator"
        );
    }
}

#[test]
fn outbound_tools_match_expose_list() {
    let m = PluginManifest::from_str(MANIFEST).unwrap();
    let exposed: std::collections::HashSet<_> = m.plugin.tools.expose.iter().collect();
    for outbound in &m.plugin.tools.outbound {
        assert!(
            exposed.contains(&outbound.name),
            "outbound spec `{}` not in expose list — would orphan",
            outbound.name
        );
        // Schema is a non-empty JSON object — defer the deeper
        // JSON-Schema validation to the daemon's registration path.
        assert!(!outbound.input_schema.is_empty());
        assert!(outbound.input_schema.contains("\"type\""));
    }
}

#[test]
fn manifest_declares_pairing_adapter_for_contract_completeness() {
    // Phase 81.20.x followup — voice is transform-only but
    // declares the Stage 1 pairing.adapter section so the
    // daemon's `GenericBrokerPairingAdapter` constructs
    // uniformly across every canonical plugin. Three broker
    // topics MUST appear in the subscribe allowlist; handlers
    // stub `not_supported` (see `src/auto_discovery.rs`).
    let raw = MANIFEST;
    assert!(
        raw.contains("[plugin.pairing.adapter]"),
        "manifest must declare `[plugin.pairing.adapter]` for auto-discovery Stage 1"
    );
    assert!(raw.contains("plugin.voice.pairing.normalize_sender"));
    assert!(raw.contains("plugin.voice.pairing.send_reply"));
    assert!(raw.contains("plugin.voice.pairing.send_qr_image"));
}

#[test]
fn transform_suffixes_present_for_auto_discovery() {
    // `nexo-core::agent::llm_behavior` discovers transform tools by
    // suffix. The plugin MUST expose both an `_inbound_transform`
    // and a `_reply_transform` tool to wire into the chain.
    let m = PluginManifest::from_str(MANIFEST).unwrap();
    let names: Vec<&String> = m.plugin.tools.expose.iter().collect();
    assert!(
        names.iter().any(|n| n.ends_with("_inbound_transform")),
        "no `_inbound_transform` tool — voice-mode addendum can't fire on inbound"
    );
    assert!(
        names.iter().any(|n| n.ends_with("_reply_transform")),
        "no `_reply_transform` tool — outbound text never gets synthesised"
    );
}
