use indexmap::IndexMap;
use serde_yaml::Value;
use std::collections::HashMap;
use wrkflw::matrix::{self, MatrixCombination, MatrixConfig};

fn create_test_matrix() -> MatrixConfig {
    let mut matrix = MatrixConfig::default();

    // Add basic parameters
    let mut params = IndexMap::new();

    // Add 'os' parameter with array values
    let os_array = vec![
        Value::String("ubuntu".to_string()),
        Value::String("windows".to_string()),
        Value::String("macos".to_string()),
    ];
    params.insert("os".to_string(), Value::Sequence(os_array));

    // Add 'node' parameter with array values
    let node_array = vec![
        Value::Number(serde_yaml::Number::from(14)),
        Value::Number(serde_yaml::Number::from(16)),
    ];
    params.insert("node".to_string(), Value::Sequence(node_array));

    matrix.parameters = params;

    // Add exclude pattern
    let mut exclude_item = HashMap::new();
    exclude_item.insert("os".to_string(), Value::String("windows".to_string()));
    exclude_item.insert(
        "node".to_string(),
        Value::Number(serde_yaml::Number::from(14)),
    );
    matrix.exclude = vec![exclude_item];

    // Add include pattern
    let mut include_item = HashMap::new();
    include_item.insert("os".to_string(), Value::String("ubuntu".to_string()));
    include_item.insert(
        "node".to_string(),
        Value::Number(serde_yaml::Number::from(18)),
    );
    include_item.insert("experimental".to_string(), Value::Bool(true));
    matrix.include = vec![include_item];

    // Set max-parallel
    matrix.max_parallel = Some(2);

    // Set fail-fast
    matrix.fail_fast = Some(true);

    matrix
}

#[test]
fn test_matrix_expansion() {
    let matrix = create_test_matrix();

    // Expand the matrix
    let combinations = matrix::expand_matrix(&matrix).unwrap();

    // We should have 6 combinations:
    // 3 OS x 2 Node versions = 6 base combinations
    // - 1 excluded (windows + node 14)
    // + 1 included (ubuntu + node 18 + experimental)
    // = 6 total combinations
    assert_eq!(combinations.len(), 6);

    // Check that the excluded combination is not present
    let excluded = combinations
        .iter()
        .find(|c| match (c.values.get("os"), c.values.get("node")) {
            (Some(Value::String(os)), Some(Value::Number(node))) => {
                os == "windows" && node.as_u64() == Some(14)
            }
            _ => false,
        });
    assert!(
        excluded.is_none(),
        "Excluded combination should not be present"
    );

    // Check that the included combination is present
    let included = combinations.iter().find(|c| {
        match (
            c.values.get("os"),
            c.values.get("node"),
            c.values.get("experimental"),
        ) {
            (Some(Value::String(os)), Some(Value::Number(node)), Some(Value::Bool(exp))) => {
                os == "ubuntu" && node.as_u64() == Some(18) && *exp
            }
            _ => false,
        }
    });
    assert!(included.is_some(), "Included combination should be present");
    assert!(
        included.unwrap().is_included,
        "Combination should be marked as included"
    );
}

#[test]
fn test_format_combination_name() {
    let mut values = HashMap::new();
    values.insert("os".to_string(), Value::String("ubuntu".to_string()));
    values.insert(
        "node".to_string(),
        Value::Number(serde_yaml::Number::from(14)),
    );

    let combination = MatrixCombination {
        values,
        is_included: false,
    };

    let formatted = matrix::format_combination_name("test-job", &combination);

    // Should format as "test-job (os: ubuntu, node: 14)" or similar
    assert!(formatted.contains("test-job"));
    assert!(formatted.contains("os: ubuntu"));
    assert!(formatted.contains("node: 14"));
}
