//! §Fase 33.y.d D12 fuzz pack — adversarial coverage of the 6
//! orchestration async handlers (Let / Conditional / ForIn /
//! Break / Continue / Return).
//!
//! Hand-rolled deterministic 64-bit LCG (Knuth/MMIX constants),
//! no external dep. Same pattern as Fase 33.x.k + 33.y.c fuzz packs.
//!
//! # Coverage targets (~1450 iters)
//!
//! - Let fuzz             — 200 iters (random target/value/kind + ref-missing-fallback)
//! - Conditional fuzz     — 200 iters (random operators × LHS bindings × RHS)
//! - ForIn fuzz           — 200 iters (random CSV iterables + body sizes 0..5)
//! - Sentinel fuzz        — 200 iters (Break / Continue / Return inside random bodies)
//! - Nested composition   — 200 iters (3-deep random nesting)
//! - Cancel propagation   — 200 iters (random pre-cancel × handler)
//! - Predicate operators  — 250 iters (all closed-catalog operators × random values)
//!
//! **Grand total: 1,450 deterministic LCG iters**, runtime <1s.

use axon::cancel_token::CancellationFlag;
use axon::flow_dispatcher::orchestration::{
    run_break, run_conditional, run_continue, run_for_in, run_let, run_return,
};
use axon::flow_dispatcher::{dispatch_node, DispatchCtx, DispatchError, NodeOutcome};
use axon::ir_nodes::*;
use tokio::sync::mpsc;

// ────────────────────────────────────────────────────────────────────
//  LCG
// ────────────────────────────────────────────────────────────────────

struct Lcg(u64);

impl Lcg {
    fn new(seed: u64) -> Self {
        let mixed = seed
            .wrapping_mul(0x9E37_79B9_7F4A_7C15)
            .wrapping_add(0xBB67_AE85_84CA_A73B);
        Self(mixed.max(1))
    }
    fn next_u64(&mut self) -> u64 {
        self.0 = self
            .0
            .wrapping_mul(6_364_136_223_846_793_005)
            .wrapping_add(1_442_695_040_888_963_407);
        self.0
    }
    fn range(&mut self, max: usize) -> usize {
        (self.next_u64() as usize) % max.max(1)
    }
    fn ascii_string(&mut self, len: usize) -> String {
        let mut s = String::with_capacity(len);
        for _ in 0..len {
            let c = (self.range(95) + 32) as u8;
            s.push(c as char);
        }
        s
    }
    fn ascii_with_random_len(&mut self, max: usize) -> String {
        let len = self.range(max) + 1;
        self.ascii_string(len)
    }
    fn ascii_with_random_len_or_empty(&mut self, max: usize) -> String {
        let len = self.range(max);
        self.ascii_string(len)
    }
    fn csv_with_random_len(&mut self, max_items: usize, max_item_len: usize) -> String {
        let n = self.range(max_items);
        let mut parts = Vec::with_capacity(n);
        for _ in 0..n {
            let len = self.range(max_item_len) + 1;
            parts.push(self.ascii_string(len));
        }
        parts.join(",")
    }
    fn boolean(&mut self) -> bool {
        self.next_u64() & 1 == 1
    }
}

// ────────────────────────────────────────────────────────────────────
//  Helpers
// ────────────────────────────────────────────────────────────────────

fn fresh_ctx() -> (
    DispatchCtx,
    mpsc::UnboundedReceiver<axon::flow_execution_event::FlowExecutionEvent>,
) {
    let (tx, rx) = mpsc::unbounded_channel();
    let ctx = DispatchCtx::new(
        "FuzzFlow",
        "stub",
        "",
        CancellationFlag::new(),
        tx,
    );
    (ctx, rx)
}

fn assert_no_panic_outcome(
    label: &str,
    outcome: &Result<NodeOutcome, DispatchError>,
) {
    match outcome {
        Ok(_) => {}
        Err(DispatchError::UpstreamCancelled) => {}
        Err(DispatchError::ChannelClosed) => {}
        Err(other) => panic!("{label}: unexpected error variant: {other:?}"),
    }
}

// ────────────────────────────────────────────────────────────────────
//  §1 — Let fuzz: 200 iters
// ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn fuzz_let_handler_never_panics() {
    let mut lcg = Lcg::new(0x1E_72_BD_31_AC_F0_F1_FF);
    let kinds = ["literal", "reference", "expression", "unknown_kind"];
    for iter in 0..200 {
        let target = lcg.ascii_with_random_len(15);
        let value = lcg.ascii_with_random_len(40);
        let kind = kinds[lcg.range(4)];
        let binding = IRLetBinding {
            node_type: "let",
            source_line: 0,
            source_column: 0,
            target: target.clone(),
            value,
            value_kind: kind.into(),
        };
        let (mut ctx, _rx) = fresh_ctx();
        let outcome = run_let(&binding, &mut ctx).await;
        assert_no_panic_outcome(&format!("let iter={iter}"), &outcome);
        if outcome.is_ok() {
            // Target binding MUST be present in let_bindings after a
            // successful run (D10 parity contract).
            assert!(
                ctx.let_bindings.contains_key(&target),
                "let iter={iter}: target binding not recorded"
            );
        }
    }
}

#[tokio::test]
async fn fuzz_let_reference_to_missing_binding_returns_empty() {
    let mut lcg = Lcg::new(0x4D_15_5C_A1_AB_AB_AB_FF);
    for iter in 0..100 {
        let target = lcg.ascii_with_random_len(15);
        let ref_name = format!("never_set_{iter}");
        let binding = IRLetBinding {
            node_type: "let",
            source_line: 0,
            source_column: 0,
            target: target.clone(),
            value: ref_name,
            value_kind: "reference".into(),
        };
        let (mut ctx, _rx) = fresh_ctx();
        let outcome = run_let(&binding, &mut ctx).await.unwrap();
        match outcome {
            NodeOutcome::Completed { output, .. } => {
                assert_eq!(
                    output, "",
                    "iter={iter}: missing reference must resolve to empty"
                );
            }
            other => panic!("iter={iter}: expected Completed, got {other:?}"),
        }
    }
}

// ────────────────────────────────────────────────────────────────────
//  §2 — Conditional fuzz: 200 iters
// ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn fuzz_conditional_never_panics_random_predicates() {
    let mut lcg = Lcg::new(0xC0_AD_17_10_AA_15_FF_EE);
    let ops = ["==", "!=", ">", ">=", "<", "<=", "=", ""];
    for iter in 0..200 {
        let cond_var = lcg.ascii_with_random_len(10);
        let op = ops[lcg.range(ops.len())];
        let rhs = lcg.ascii_with_random_len_or_empty(20);
        // Maybe seed the LHS binding.
        let (mut ctx, _rx) = fresh_ctx();
        if lcg.boolean() {
            let v = lcg.ascii_with_random_len_or_empty(15);
            ctx.let_bindings.insert(cond_var.clone(), v);
        }
        let cond = IRConditional {
            node_type: "conditional",
            source_line: 0,
            source_column: 0,
            condition: cond_var,
            comparison_op: op.into(),
            comparison_value: rhs,
            then_body: Vec::new(),
            else_body: Vec::new(),
            conditions: Vec::new(),
            conjunctor: String::new(),
        };
        let outcome = run_conditional(&cond, &mut ctx).await;
        assert_no_panic_outcome(&format!("cond iter={iter}"), &outcome);
    }
}

// ────────────────────────────────────────────────────────────────────
//  §3 — ForIn fuzz: 200 iters
// ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn fuzz_for_in_never_panics_random_iterables() {
    let mut lcg = Lcg::new(0xFC_4F_1E_AB_C1_C1_FF_DD);
    for iter in 0..200 {
        let variable = lcg.ascii_with_random_len(8);
        let iterable_var = lcg.ascii_with_random_len(8);
        let (mut ctx, _rx) = fresh_ctx();
        if lcg.boolean() {
            let csv = lcg.csv_with_random_len(5, 6);
            ctx.let_bindings.insert(iterable_var.clone(), csv);
        }
        let for_in = IRForIn {
            node_type: "for_in",
            source_line: 0,
            source_column: 0,
            variable,
            iterable: iterable_var,
            body: Vec::new(),
        };
        let outcome = run_for_in(&for_in, &mut ctx).await;
        assert_no_panic_outcome(&format!("for_in iter={iter}"), &outcome);
    }
}

// ────────────────────────────────────────────────────────────────────
//  §4 — Sentinel fuzz: 200 iters (Break/Continue/Return)
// ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn fuzz_sentinels_never_panic() {
    let mut lcg = Lcg::new(0x5E_71_13_AE_71_AE_1E_F0);
    for iter in 0..200 {
        let (mut ctx, _rx) = fresh_ctx();
        // Round-robin Break / Continue / Return
        let outcome = match lcg.range(3) {
            0 => run_break(
                &IRBreakStep {
                    node_type: "break",
                    source_line: 0,
                    source_column: 0,
                },
                &mut ctx,
            )
            .await
            .map(|o| {
                assert!(matches!(o, NodeOutcome::Break));
                o
            }),
            1 => run_continue(
                &IRContinueStep {
                    node_type: "continue",
                    source_line: 0,
                    source_column: 0,
                },
                &mut ctx,
            )
            .await
            .map(|o| {
                assert!(matches!(o, NodeOutcome::LoopContinue));
                o
            }),
            _ => {
                let value_expr = lcg.ascii_with_random_len_or_empty(20);
                run_return(
                    &IRReturnStep {
                        node_type: "return",
                        source_line: 0,
                        source_column: 0,
                        value_expr,
                    },
                    &mut ctx,
                )
                .await
                .map(|o| {
                    assert!(matches!(o, NodeOutcome::Return { .. }));
                    o
                })
            }
        };
        assert_no_panic_outcome(&format!("sentinel iter={iter}"), &outcome);
    }
}

// ────────────────────────────────────────────────────────────────────
//  §5 — Nested composition fuzz: 200 iters
// ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn fuzz_nested_orchestration_3_deep_never_panics() {
    let mut lcg = Lcg::new(0xCE_57_3D_DE_EF_BB_AA_FF);
    for iter in 0..200 {
        let (mut ctx, _rx) = fresh_ctx();
        ctx.let_bindings.insert("xs".into(), "a,b".into());
        ctx.let_bindings.insert("flag".into(), "yes".into());

        // Build a random nesting:
        //   if flag == "yes" {
        //     for x in xs {
        //       <random sentinel or let>
        //     }
        //   }
        let inner: IRFlowNode = match lcg.range(4) {
            0 => IRFlowNode::Break(IRBreakStep {
                node_type: "break",
                source_line: 0,
                source_column: 0,
            }),
            1 => IRFlowNode::Continue(IRContinueStep {
                node_type: "continue",
                source_line: 0,
                source_column: 0,
            }),
            2 => IRFlowNode::Return(IRReturnStep {
                node_type: "return",
                source_line: 0,
                source_column: 0,
                value_expr: "v".into(),
            }),
            _ => IRFlowNode::Let(IRLetBinding {
                node_type: "let",
                source_line: 0,
                source_column: 0,
                target: format!("inner_{iter}"),
                value: "v".into(),
                value_kind: "literal".into(),
            }),
        };

        let outer = IRFlowNode::Conditional(IRConditional {
            node_type: "conditional",
            source_line: 0,
            source_column: 0,
            condition: "flag".into(),
            comparison_op: "==".into(),
            comparison_value: "yes".into(),
            then_body: vec![IRFlowNode::ForIn(IRForIn {
                node_type: "for_in",
                source_line: 0,
                source_column: 0,
                variable: "x".into(),
                iterable: "xs".into(),
                body: vec![inner],
            })],
            else_body: Vec::new(),
            conditions: Vec::new(),
            conjunctor: String::new(),
        });

        let outcome = dispatch_node(&outer, &mut ctx).await;
        assert_no_panic_outcome(&format!("nested iter={iter}"), &outcome);
    }
}

// ────────────────────────────────────────────────────────────────────
//  §6 — Cancel propagation fuzz: 200 iters
// ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn fuzz_cancel_propagation_random_handler_and_timing() {
    let mut lcg = Lcg::new(0xC4_C5_AB_AB_FF_FF_EE_DD);
    for iter in 0..200 {
        let cancel = CancellationFlag::new();
        let pre_cancel = lcg.boolean();
        if pre_cancel {
            cancel.cancel();
        }
        let (tx, _rx) = mpsc::unbounded_channel();
        let mut ctx = DispatchCtx::new("F", "stub", "", cancel, tx);
        ctx.let_bindings.insert("xs".into(), "a,b".into());

        let node = match lcg.range(6) {
            0 => IRFlowNode::Let(IRLetBinding {
                node_type: "let",
                source_line: 0,
                source_column: 0,
                target: "k".into(),
                value: "v".into(),
                value_kind: "literal".into(),
            }),
            1 => IRFlowNode::Conditional(IRConditional {
                node_type: "conditional",
                source_line: 0,
                source_column: 0,
                condition: "".into(),
                comparison_op: "".into(),
                comparison_value: "".into(),
                then_body: Vec::new(),
                else_body: Vec::new(),
                conditions: Vec::new(),
                conjunctor: String::new(),
            }),
            2 => IRFlowNode::ForIn(IRForIn {
                node_type: "for_in",
                source_line: 0,
                source_column: 0,
                variable: "x".into(),
                iterable: "xs".into(),
                body: Vec::new(),
            }),
            3 => IRFlowNode::Break(IRBreakStep {
                node_type: "break",
                source_line: 0,
                source_column: 0,
            }),
            4 => IRFlowNode::Continue(IRContinueStep {
                node_type: "continue",
                source_line: 0,
                source_column: 0,
            }),
            _ => IRFlowNode::Return(IRReturnStep {
                node_type: "return",
                source_line: 0,
                source_column: 0,
                value_expr: "v".into(),
            }),
        };

        let outcome = dispatch_node(&node, &mut ctx).await;
        if pre_cancel {
            assert!(
                matches!(outcome, Err(DispatchError::UpstreamCancelled)),
                "iter={iter} pre-cancel: expected UpstreamCancelled, got {outcome:?}"
            );
        } else {
            assert_no_panic_outcome(&format!("cancel iter={iter}"), &outcome);
        }
    }
}

// ────────────────────────────────────────────────────────────────────
//  §7 — Predicate operator coverage: 250 iters (all ops × random values)
// ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn fuzz_all_predicate_operators_with_random_values() {
    let mut lcg = Lcg::new(0xC0_C1_4E_C0_4A_70_F0_F0);
    let ops = ["==", "!=", ">", ">=", "<", "<=", "=", ""];
    for iter in 0..250 {
        let op = ops[lcg.range(ops.len())];
        // Mix numeric + string LHS.
        let lhs = if lcg.boolean() {
            format!("{}", lcg.range(100))
        } else {
            lcg.ascii_with_random_len(5)
        };
        let rhs = if lcg.boolean() {
            format!("{}", lcg.range(100))
        } else {
            lcg.ascii_with_random_len(5)
        };
        let (mut ctx, _rx) = fresh_ctx();
        ctx.let_bindings.insert("lhs".into(), lhs);
        let cond = IRConditional {
            node_type: "conditional",
            source_line: 0,
            source_column: 0,
            condition: "lhs".into(),
            comparison_op: op.into(),
            comparison_value: rhs,
            then_body: vec![IRFlowNode::Let(IRLetBinding {
                node_type: "let",
                source_line: 0,
                source_column: 0,
                target: "took".into(),
                value: "then".into(),
                value_kind: "literal".into(),
            })],
            else_body: vec![IRFlowNode::Let(IRLetBinding {
                node_type: "let",
                source_line: 0,
                source_column: 0,
                target: "took".into(),
                value: "else".into(),
                value_kind: "literal".into(),
            })],
            conditions: Vec::new(),
            conjunctor: String::new(),
        };
        let outcome = run_conditional(&cond, &mut ctx).await;
        assert_no_panic_outcome(&format!("pred iter={iter} op={op:?}"), &outcome);
        if outcome.is_ok() {
            // Exactly one of then/else branches must have run.
            let took = ctx.let_bindings.get("took");
            assert!(took.is_some(), "iter={iter} op={op:?}: a branch must run");
            let v = took.unwrap();
            assert!(
                v == "then" || v == "else",
                "iter={iter} op={op:?}: took = {v}"
            );
        }
    }
}

// ────────────────────────────────────────────────────────────────────
//  §8 — Total iter count pin
// ────────────────────────────────────────────────────────────────────

#[test]
fn fuzz_pack_total_iter_count() {
    // Per surface:
    //   Let:                200 + 100 (missing-ref) = 300
    //   Conditional:        200
    //   ForIn:              200
    //   Sentinels:          200
    //   Nested:             200
    //   Cancel propagation: 200
    //   Predicates:         250
    let total = 300 + 200 + 200 + 200 + 200 + 200 + 250;
    assert_eq!(
        total, 1550,
        "33.y.d fuzz pack target: 1550 deterministic LCG iters \
         across orchestration surfaces."
    );
}
