//! Pure criterion helpers for `service::story`.
//!
//! These functions parse, validate, and manipulate story acceptance criteria
//! without performing any I/O.

use crate::workflow::{TestCaseResult, TestStatus};
use serde_json::Value;

/// Parse an optional JSON array of test-case objects into a `Vec<TestCaseResult>`.
///
/// Each object must have `"name"` (string) and `"status"` (`"pass"` or `"fail"`)
/// fields. The optional `"details"` field is preserved when present.
///
/// Returns an empty vector for `None` or `Value::Null` inputs.
///
/// # Errors
/// Returns `Err(String)` if the value is not an array, or if any item is
/// missing a required field or has an unrecognised status string.
pub fn parse_test_cases(value: Option<&Value>) -> Result<Vec<TestCaseResult>, String> {
    let arr = match value {
        Some(Value::Array(a)) => a,
        Some(Value::Null) | None => return Ok(Vec::new()),
        _ => return Err("Expected array for test cases".to_string()),
    };

    arr.iter()
        .map(|item| {
            let name = item
                .get("name")
                .and_then(|v| v.as_str())
                .ok_or("Test case missing 'name'")?
                .to_string();
            let status_str = item
                .get("status")
                .and_then(|v| v.as_str())
                .ok_or("Test case missing 'status'")?;
            let status = match status_str {
                "pass" => TestStatus::Pass,
                "fail" => TestStatus::Fail,
                other => {
                    return Err(format!(
                        "Invalid test status '{other}'. Use 'pass' or 'fail'."
                    ));
                }
            };
            let details = item
                .get("details")
                .and_then(|v| v.as_str())
                .map(String::from);
            Ok(TestCaseResult {
                name,
                status,
                details,
            })
        })
        .collect()
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn parse_none_returns_empty() {
        let result = parse_test_cases(None).unwrap();
        assert!(result.is_empty());
    }

    #[test]
    fn parse_null_value_returns_empty() {
        let null_val = json!(null);
        let result = parse_test_cases(Some(&null_val)).unwrap();
        assert!(result.is_empty());
    }

    #[test]
    fn parse_valid_cases() {
        let input = json!([
            {"name": "test1", "status": "pass"},
            {"name": "test2", "status": "fail", "details": "assertion failed"}
        ]);
        let result = parse_test_cases(Some(&input)).unwrap();
        assert_eq!(result.len(), 2);
        assert_eq!(result[0].name, "test1");
        assert_eq!(result[0].status, TestStatus::Pass);
        assert_eq!(result[1].status, TestStatus::Fail);
        assert_eq!(result[1].details, Some("assertion failed".to_string()));
    }

    #[test]
    fn parse_invalid_status_returns_error() {
        let input = json!([{"name": "t", "status": "maybe"}]);
        assert!(parse_test_cases(Some(&input)).is_err());
    }

    #[test]
    fn parse_non_array_returns_error() {
        let obj = json!({"invalid": "input"});
        let result = parse_test_cases(Some(&obj));
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Expected array"));
    }

    #[test]
    fn parse_missing_name_returns_error() {
        let input = json!([{"status": "pass"}]);
        let result = parse_test_cases(Some(&input));
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("name"));
    }

    #[test]
    fn parse_missing_status_returns_error() {
        let input = json!([{"name": "test1"}]);
        let result = parse_test_cases(Some(&input));
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("status"));
    }

    #[test]
    fn parse_details_is_optional() {
        let input = json!([{"name": "no_details", "status": "pass"}]);
        let result = parse_test_cases(Some(&input)).unwrap();
        assert_eq!(result[0].details, None);
    }
}
