//! Tests that generators work correctly with non-'static (borrowed) types.
//!
//! These tests exercise the lifetime logic in BasicGenerator<'a, T> and
//! the phantom type parameters on composite generators.

use hegel::TestCase;
use hegel::generators::{self as gs, Generator};

#[hegel::test]
fn test_sampled_from_references(tc: TestCase) {
    let options = [10, 20, 30, 40, 50];
    let refs: Vec<&i32> = options.iter().collect();
    let value: &i32 = tc.draw(gs::sampled_from(refs));
    assert!(options.contains(value));
}

#[hegel::test]
fn test_sampled_from_str_references(tc: TestCase) {
    let strings = ["hello", "world", "foo", "bar"];
    let value: &str = tc.draw(gs::sampled_from(strings.to_vec()));
    assert!(strings.contains(&value));
}

#[hegel::test]
fn test_tuple_of_references(tc: TestCase) {
    let xs = [1, 2, 3];
    let ys = ["a", "b", "c"];
    let x_refs: Vec<&i32> = xs.iter().collect();
    let y_refs: Vec<&&str> = ys.iter().collect();
    let (x, y): (&i32, &&str) = tc.draw(gs::tuples!(
        gs::sampled_from(x_refs),
        gs::sampled_from(y_refs),
    ));
    assert!(xs.contains(x));
    assert!(ys.contains(y));
}

#[hegel::test]
fn test_optional_of_references(tc: TestCase) {
    let values = [100, 200, 300];
    let refs: Vec<&i32> = values.iter().collect();
    let result: Option<&i32> = tc.draw(gs::optional(gs::sampled_from(refs)));
    if let Some(v) = result {
        assert!(values.contains(v));
    }
}

#[hegel::test]
fn test_one_of_with_references(tc: TestCase) {
    let small = [1, 2, 3];
    let big = [100, 200, 300];
    let small_refs: Vec<&i32> = small.iter().collect();
    let big_refs: Vec<&i32> = big.iter().collect();
    let value: &i32 = tc.draw(hegel::one_of!(
        gs::sampled_from(small_refs),
        gs::sampled_from(big_refs),
    ));
    assert!(small.contains(value) || big.contains(value));
}

#[hegel::test]
fn test_vec_of_references(tc: TestCase) {
    let options = [10, 20, 30];
    let refs: Vec<&i32> = options.iter().collect();
    let result: Vec<&i32> = tc.draw(gs::vecs(gs::sampled_from(refs)).min_size(1).max_size(5));
    assert!(!result.is_empty());
    for v in &result {
        assert!(options.contains(v));
    }
}

#[hegel::test]
fn test_map_over_references(tc: TestCase) {
    let values = [10, 20, 30];
    let refs: Vec<&i32> = values.iter().collect();
    let doubled: i32 = tc.draw(gs::sampled_from(refs).map(|r| r * 2));
    assert!([20, 40, 60].contains(&doubled));
}

#[hegel::test]
fn test_tuple3_of_references(tc: TestCase) {
    let xs = [1, 2];
    let ys = ["a", "b"];
    let zs = [true, false];
    let xr: Vec<&i32> = xs.iter().collect();
    let yr: Vec<&&str> = ys.iter().collect();
    let zr: Vec<&bool> = zs.iter().collect();
    let (x, y, z): (&i32, &&str, &bool) = tc.draw(gs::tuples!(
        gs::sampled_from(xr),
        gs::sampled_from(yr),
        gs::sampled_from(zr),
    ));
    assert!(xs.contains(x));
    assert!(ys.contains(y));
    assert!(zs.contains(z));
}

#[hegel::test]
fn test_nested_optional_tuple_of_references(tc: TestCase) {
    let names = ["alice", "bob", "carol"];
    let ages = [25u32, 30, 35];
    let name_refs: Vec<&&str> = names.iter().collect();
    let age_refs: Vec<&u32> = ages.iter().collect();
    let result: Option<(&&str, &u32)> = tc.draw(gs::optional(gs::tuples!(
        gs::sampled_from(name_refs),
        gs::sampled_from(age_refs),
    )));
    if let Some((name, age)) = result {
        assert!(names.contains(name));
        assert!(ages.contains(age));
    }
}

#[hegel::test]
fn test_vec_of_tuples_of_references(tc: TestCase) {
    let keys = [1, 2, 3];
    let vals = ["x", "y", "z"];
    let kr: Vec<&i32> = keys.iter().collect();
    let vr: Vec<&&str> = vals.iter().collect();
    let result: Vec<(&i32, &&str)> =
        tc.draw(gs::vecs(gs::tuples!(gs::sampled_from(kr), gs::sampled_from(vr),)).max_size(5));
    for (k, v) in &result {
        assert!(keys.contains(k));
        assert!(vals.contains(v));
    }
}

#[hegel::test]
fn test_one_of_mapped_references(tc: TestCase) {
    let positives = [1, 2, 3];
    let negatives = [-1, -2, -3];
    let pos_refs: Vec<&i32> = positives.iter().collect();
    let neg_refs: Vec<&i32> = negatives.iter().collect();
    let description: String = tc.draw(hegel::one_of!(
        gs::sampled_from(pos_refs).map(|r| format!("positive: {}", r)),
        gs::sampled_from(neg_refs).map(|r| format!("negative: {}", r)),
    ));
    assert!(description.starts_with("positive:") || description.starts_with("negative:"));
}

#[hegel::test]
fn test_boxed_generator_with_references(tc: TestCase) {
    let options = [10, 20, 30];
    let refs: Vec<&i32> = options.iter().collect();
    let g = gs::sampled_from(refs).boxed();
    let value: &i32 = tc.draw(g);
    assert!(options.contains(value));
}

#[hegel::test]
fn test_deeply_nested_reference_composition(tc: TestCase) {
    // References flowing through: sampled_from -> tuple -> optional -> vec -> map
    let xs = [1i32, 2, 3];
    let ys = [4i32, 5, 6];
    let xr: Vec<&i32> = xs.iter().collect();
    let yr: Vec<&i32> = ys.iter().collect();

    let result: Vec<i32> = tc.draw(
        gs::vecs(
            gs::optional(gs::tuples!(gs::sampled_from(xr), gs::sampled_from(yr),)).map(|opt| {
                match opt {
                    Some((a, b)) => a + b,
                    None => 0,
                }
            }),
        )
        .max_size(5),
    );

    for v in &result {
        assert!(
            *v == 0 || (5..=9).contains(v),
            "Expected 0 or 5..=9, got {}",
            v
        );
    }
}

#[hegel::test]
fn test_boxed_generator_with_local_lifetime(tc: TestCase) {
    // This tests that we can created boxed generators boxed
    // generators whose lifetimes may not outlive the test.
    let x = ["foo", "bar", "baz"];

    let ix = gs::integers().min_value(0).max_value(2);

    // Generator for a reference into x, which necessarily
    // means that `refs` may not outlive `x`.
    let refs = ix.map(|i| &x[i]).boxed();

    let t = tc.draw(refs);

    assert!(t.len() == 3);
}
