use std::collections::HashMap;

use bt_core::StrategyMetadata;
use bt_expr::{Expr, ScalarValue, ValueType};
use bt_strategy::{
    compile_strategy, compile_strategy_with_columns, Constraint, ParamSpec, StrategyDef,
};

#[test]
fn compile_strategy_collects_required_columns_and_defaults() {
    let def = StrategyDef {
        name: "momentum".to_string(),
        signals: HashMap::from([(
            "signal".to_string(),
            Expr::Sub(
                Box::new(Expr::Column("close".to_string())),
                Box::new(Expr::Column("open".to_string())),
            ),
        )]),
        position_sizing: Expr::Mul(
            Box::new(Expr::Column("signal".to_string())),
            Box::new(Expr::Parameter("size".to_string())),
        ),
        parameters: HashMap::from([(
            "size".to_string(),
            ParamSpec {
                name: "size".to_string(),
                default: ScalarValue::Float64(0.5),
                range: Some((ScalarValue::Float64(0.0), ScalarValue::Float64(1.0))),
                description: "position size".to_string(),
            },
        )]),
        constraints: vec![Constraint::RequireColumn("vwap".to_string())],
        metadata: StrategyMetadata::default(),
    };

    let plan = compile_strategy(&def).expect("compile strategy");
    assert_eq!(
        plan.parameters.get("size"),
        Some(&ScalarValue::Float64(0.5))
    );
    assert!(plan.required_columns.contains(&"close".to_string()));
    assert!(plan.required_columns.contains(&"open".to_string()));
    assert!(plan.required_columns.contains(&"signal".to_string()));
    assert!(plan.required_columns.contains(&"vwap".to_string()));
}

#[test]
fn compile_strategy_with_columns_rejects_unknown_column() {
    let def = StrategyDef {
        name: "bad".to_string(),
        signals: HashMap::from([(
            "signal".to_string(),
            Expr::Column("missing_col".to_string()),
        )]),
        position_sizing: Expr::Column("signal".to_string()),
        parameters: HashMap::new(),
        constraints: Vec::new(),
        metadata: StrategyMetadata::default(),
    };

    let columns = HashMap::from([("close".to_string(), ValueType::Float64)]);
    let error = compile_strategy_with_columns(&def, Some(&columns))
        .expect_err("unknown column should fail");
    assert!(error.to_string().contains("unknown columns"));
}

#[test]
fn compile_strategy_rejects_undefined_parameter_reference() {
    let def = StrategyDef {
        name: "undef_param".to_string(),
        signals: HashMap::from([("signal".to_string(), Expr::Parameter("x".to_string()))]),
        position_sizing: Expr::Column("signal".to_string()),
        parameters: HashMap::new(),
        constraints: Vec::new(),
        metadata: StrategyMetadata::default(),
    };

    let error = compile_strategy(&def).expect_err("undefined parameter should fail");
    assert!(error.to_string().contains("undefined parameters"));
}
