//! Combination meta-test — see `docs/internal/scenario-meta-testing.md`
//! ("3. Combination with active reset").
//!
//! Real `BufferScenario`s from the canonical [`super::corpus`] run on
//! ONE long-lived harness with an *active reset* (`reset::reset_actions`,
//! not a fresh harness) between them. Each scenario asserts its **own**
//! expectations at its checkpoint — right after its actions, before the
//! next reset — in fixed and randomly-permuted orders. A scenario that
//! passes alone but fails after some predecessor means either the reset
//! is incomplete or the scenario depends on ambient state a predecessor
//! left behind: bugs the fresh-harness-per-test model can't surface.
//!
//! Scope: the driver replays each scenario's `actions` (not `events`)
//! on a default no-plugins harness (auto-close/indent off, so the
//! reset's retyping is literal). So we take the corpus subset that is
//! `actions`-only, default-language, default-behavior.

use crate::common::scenario::buffer_scenario::BufferScenario;
use crate::common::scenario::property::run_scenarios_with_reset_between;
use crate::semantic::corpus;
use proptest::prelude::*;

/// Corpus scenarios the combination driver can faithfully replay.
fn combination_scenarios() -> Vec<BufferScenario> {
    corpus::buffer_scenarios()
        .into_iter()
        .filter(|s| {
            s.events.is_empty()
                && s.language.is_none()
                && !s.behavior.auto_close
                && !s.behavior.auto_indent
                && !s.behavior.auto_surround
        })
        .collect()
}

/// Representative fixed orders: forward, reverse, and a rotation.
fn orders(n: usize) -> Vec<Vec<usize>> {
    let fwd: Vec<usize> = (0..n).collect();
    let rev: Vec<usize> = (0..n).rev().collect();
    let rot: Vec<usize> = (0..n).map(|i| (i + n / 2) % n).collect();
    vec![fwd, rev, rot]
}

/// A random permutation of `0..n`, generated by sorting the indices by
/// a random key. Always a valid permutation; reproducible via
/// proptest's seed / regression file.
/// A random permutation of a balanced multiset of `length` scenario
/// indices: each of the `n` corpus scenarios appears ~`length / n`
/// times, shuffled into a random order. So one run is `length`
/// scenarios "mixed up" — every scenario exercised many times in a
/// random ordering (not independent sampling, which would leave the
/// per-scenario count uneven).
fn permuted_run(n: usize, length: usize) -> impl Strategy<Value = Vec<usize>> {
    prop::collection::vec(any::<u64>(), length).prop_map(move |keys| {
        // Shuffle 0..length by a random key, then fold onto the n
        // scenario indices — a balanced multiset in random order.
        let mut pos: Vec<usize> = (0..length).collect();
        pos.sort_by_key(|&k| keys[k]);
        pos.into_iter().map(|k| k % n).collect()
    })
}

/// Permutation length per soak case (default 100; set FRESH_SOAK_LEN
/// to change).
fn soak_len() -> usize {
    std::env::var("FRESH_SOAK_LEN")
        .ok()
        .and_then(|s| s.parse().ok())
        .unwrap_or(100)
}

/// Number of random permutations to run (default 3; set
/// FRESH_SOAK_CASES to run more).
fn soak_cases() -> u32 {
    std::env::var("FRESH_SOAK_CASES")
        .ok()
        .and_then(|s| s.parse().ok())
        .unwrap_or(3)
}

fn assert_all_pass(scenarios: &[BufferScenario], order: &[usize]) -> Result<(), String> {
    let results = run_scenarios_with_reset_between(scenarios, order);
    for (slot, &i) in order.iter().enumerate() {
        if let Err(f) = &results[slot] {
            return Err(format!(
                "scenario {i} ({:?}) failed its own assertion at position {slot}/{} \
                 of a random sequence: {f}",
                scenarios[i].description,
                order.len(),
            ));
        }
    }
    Ok(())
}

#[test]
fn combination_corpus_passes_under_fixed_orders() {
    let scenarios = combination_scenarios();
    assert!(
        !scenarios.is_empty(),
        "no combination-eligible scenarios in corpus::buffer_scenarios()"
    );
    for order in orders(scenarios.len()) {
        if let Err(e) = assert_all_pass(&scenarios, &order) {
            panic!("{e}");
        }
    }
}

proptest! {
    #![proptest_config(ProptestConfig {
        cases: soak_cases(),
        .. ProptestConfig::default()
    })]

    /// Soak: take ~100 corpus scenarios (each appearing many times),
    /// shuffle them into a random order, and run the whole permutation
    /// on ONE shared harness with an active reset between each. Every
    /// scenario must still pass its own assertions no matter what ran
    /// before it — a failure means the reset is incomplete or a
    /// scenario leaks/depends on ambient state; proptest then shrinks
    /// the permutation to the minimal offending run.
    ///
    /// Length / repetitions are tunable: defaults run 3 permutations of
    /// 100 scenarios; `FRESH_SOAK_LEN=...  FRESH_SOAK_CASES=...`.
    #[test]
    fn combination_corpus_survives_random_permutation(
        seq in permuted_run(combination_scenarios().len(), soak_len())
    ) {
        let scenarios = combination_scenarios();
        if let Err(e) = assert_all_pass(&scenarios, &seq) {
            prop_assert!(false, "{}", e);
        }
    }
}
