//! Integration tests for the composition data-flow layer.
//!
//! Exercises the public `composition::substitute` surface against
//! the kinds of args nodes carry in real YAML suites. The unit tests
//! in the module already drive each branch; this suite locks the
//! wire shape so a refactor that moves substitution behind a
//! different signature cannot ship without flipping these tests.

use std::collections::HashMap;

use mcptest_core::composition::{substitute, SubstitutionError};
use serde_json::{json, Value};

fn parents() -> HashMap<String, Value> {
    HashMap::from([
        (
            "search".to_string(),
            json!({
                "top_hit": {"name": "mcptest", "score": 0.95},
                "count": 3
            }),
        ),
        (
            "auth".to_string(),
            json!({
                "token": "Bearer-token-abc",
                "expires_in": 3600
            }),
        ),
    ])
}

#[test]
fn single_field_reference_resolves_with_preserved_type() {
    let args = json!({"score": "${search.top_hit.score}", "count": "${search.count}"});
    let out = substitute(&args, &parents()).unwrap();
    // Note: numbers stay numbers; the consumer would not have to
    // re-parse a string the way a plain template-stringify pass
    // would force.
    assert_eq!(out, json!({"score": 0.95, "count": 3}));
}

#[test]
fn mixed_string_resolves_to_a_concatenated_string() {
    let args = json!({"header": "Auth: ${auth.token}"});
    let out = substitute(&args, &parents()).unwrap();
    assert_eq!(out, json!({"header": "Auth: Bearer-token-abc"}));
}

#[test]
fn whole_parent_substitution_returns_the_parent_object() {
    let args = json!({"hit": "${search.top_hit}"});
    let out = substitute(&args, &parents()).unwrap();
    assert_eq!(out, json!({"hit": {"name": "mcptest", "score": 0.95}}));
}

#[test]
fn unknown_parent_surfaces_as_substitution_error() {
    let args = json!({"x": "${ghost.field}"});
    let err = substitute(&args, &parents()).unwrap_err();
    assert!(matches!(err, SubstitutionError::UnknownParent { .. }));
}

#[test]
fn missing_field_surfaces_as_substitution_error() {
    let args = json!({"x": "${search.does_not_exist}"});
    let err = substitute(&args, &parents()).unwrap_err();
    assert!(matches!(err, SubstitutionError::MissingField { .. }));
}

#[test]
fn reserved_namespaces_round_trip_unchanged() {
    let args = json!({
        "v": "${var.search_term}",
        "i": "${item}",
        "e": "${env.HOME}"
    });
    let out = substitute(&args, &parents()).unwrap();
    assert_eq!(out, args);
}
