use rok_utils::{
    apply, compose, memoize, or_default, pipe, retry, tap, Lazy, RokError, RokResultExt,
};

#[test]
fn error_enum() {
    let err = RokError::NotFound("User #42".into());
    assert_eq!(err.code(), "E_NOT_FOUND");
    assert_eq!(err.status(), 404);
    assert!(err.is_self_handled());

    let err = RokError::Unauthorized("token".into());
    assert_eq!(err.code(), "E_UNAUTHORIZED");
    assert_eq!(err.status(), 401);

    let err = RokError::ValidationFailure {
        field: "email".into(),
        reason: "invalid".into(),
    };
    assert_eq!(err.code(), "E_VALIDATION_FAILURE");
    assert_eq!(err.status(), 422);

    let err = RokError::Internal("oops".into());
    assert_eq!(err.status(), 500);
}

#[test]
fn error_result_ext() {
    let result: Result<i32, RokError> = Ok(42);
    assert_eq!(result.unwrap(), 42);

    let result: Result<i32, RokError> = Err(RokError::NotFound("test".into()));
    let with_context = result.context("failed");
    assert!(matches!(with_context, Err(RokError::Wrapped { .. })));
}

#[test]
fn fp_pipe() {
    let result = pipe(1, vec![|x| x + 1, |x| x * 2]);
    assert_eq!(result, 4);
}

#[test]
fn fp_compose() {
    let add_one = |x: i32| x + 1;
    let double = |x: i32| x * 2;
    let composed = compose(double, add_one);
    assert_eq!(composed(5), 12);
}

#[test]
fn fp_tap() {
    let mut captured = 0;
    let _ = tap(42, |v| captured = *v);
    assert_eq!(captured, 42);
}

#[test]
fn fp_apply() {
    let result = apply(5, |x| x * 3);
    assert_eq!(result, 15);
}

#[test]
fn fp_or_default() {
    assert_eq!(or_default(Some(42)), 42);
    assert_eq!(or_default(None::<i32>), 0);
}

#[test]
fn fp_retry_success() {
    let result = retry(3, || Ok::<i32, i32>(42));
    assert_eq!(result.unwrap(), 42);
}

#[test]
fn fp_retry_eventual() {
    let mut attempts = 0;
    let result = retry(3, &mut || {
        attempts += 1;
        if attempts < 3 {
            Err(1)
        } else {
            Ok(42)
        }
    });
    assert_eq!(result.unwrap(), 42);
    assert_eq!(attempts, 3);
}

#[test]
fn fp_lazy() {
    let lazy: Lazy<i32, fn() -> i32> = Lazy::new(|| 42);
    assert_eq!(*lazy.get(), 42);
    assert_eq!(*lazy.get(), 42);
}

#[test]
fn fp_memoize() {
    let memoized = memoize(|x: i32| x * 2);

    assert_eq!(memoized(5), 10);
    assert_eq!(memoized(5), 10);
    assert_eq!(memoized(3), 6);
    assert_eq!(memoized(3), 6);
}

#[cfg(feature = "json")]
use rok_utils::types::{
    deep_equal, get_path, is_array, is_bool, is_defined, is_null, is_number, is_object, is_string,
    set_path,
};

#[cfg(feature = "json")]
#[test]
fn json_type_guards() {
    use serde_json::json;

    assert!(is_string(&json!("hello")));
    assert!(!is_string(&json!(42)));

    assert!(is_number(&json!(42)));
    assert!(!is_number(&json!("hello")));

    assert!(is_bool(&json!(true)));
    assert!(!is_bool(&json!(42)));

    assert!(is_array(&json!([1, 2, 3])));
    assert!(!is_array(&json!(42)));

    assert!(is_object(&json!({"a": 1})));
    assert!(!is_object(&json!([1, 2])));

    assert!(is_null(&json!(null)));
    assert!(!is_null(&json!(42)));

    assert!(is_defined(&json!(42)));
    assert!(!is_defined(&json!(null)));
}

#[cfg(feature = "json")]
#[test]
fn json_deep_equal() {
    use serde_json::json;

    assert!(deep_equal(&json!({"a": 1}), &json!({"a": 1})));
    assert!(!deep_equal(&json!({"a": 1}), &json!({"a": 2})));
    assert!(deep_equal(&json!([1, 2, 3]), &json!([1, 2, 3])));
}

#[cfg(feature = "json")]
#[test]
fn json_get_path() {
    use serde_json::json;

    let val = json!({"user": {"name": "Alice", "age": 30}});
    assert_eq!(get_path(&val, "user.name"), Some(&json!("Alice")));
    assert_eq!(get_path(&val, "user.age"), Some(&json!(30)));
    assert_eq!(get_path(&val, "user.foo"), None);
}

#[cfg(feature = "json")]
#[test]
fn json_set_path() {
    use serde_json::json;

    let val = json!({"a": 1});
    let result = set_path(val, "b", json!(2));
    assert_eq!(result.get("b"), Some(&json!(2)));
}
