#![cfg(test)]
#![allow(warnings)]
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::unreadable_literal)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unwrap_used, clippy::panic)]
//! Chaos Engineering Tests
//!
//! Based on docs/ruchy-transpiler-docs.md Phase 3: Formal Methods
//! These tests prove resilience to environmental variation

#![allow(
    clippy::unused_self,
    clippy::uninlined_format_args,
    clippy::single_match_else,
    clippy::redundant_closure_for_method_calls,
    clippy::items_after_statements,
    clippy::unwrap_used,
    clippy::cast_lossless,
    clippy::panic
)]

use ruchy::transpiler::{AstNormalizer, ReferenceInterpreter};
use ruchy::{Parser, Transpiler};
use std::env;
use std::thread;

/// Chaos Monkey for introducing controlled perturbations
struct ChaosMonkey {
    seed: u64,
}

impl ChaosMonkey {
    fn new(seed: u64) -> Self {
        Self { seed }
    }

    /// Perturb the environment in deterministic but unusual ways
    fn perturb(&mut self) {
        // Use seed to generate deterministic but varied hash seeds
        let hash_seed = self
            .seed
            .wrapping_mul(6_364_136_223_846_793_005)
            .wrapping_add(1_442_695_040_888_963_407);
        env::set_var("RUST_HASH_SEED", hash_seed.to_string());

        // Could simulate allocation failures, thread scheduling, etc.
        // For now, we focus on hash randomization which affects iteration order
    }

    /// Reset perturbations
    fn reset(&self) {
        env::remove_var("RUST_HASH_SEED");
    }
}

#[test]
fn test_chaos_determinism_basic() {
    let test_cases = vec![
        "let x = 10",
        "x + y * z",
        "fun f(a, b, c) { a + b + c }",
        "[1, 2, 3, 4, 5]",
        "if true { 1 } else { 2 }",
    ];

    for input in test_cases {
        let mut outputs = Vec::new();

        // Run with different perturbations
        for seed in 0..10 {
            let mut monkey = ChaosMonkey::new(seed);
            monkey.perturb();

            // Parse and transpile
            let mut parser = Parser::new(input);
            if let Ok(ast) = parser.parse() {
                let transpiler = Transpiler::new();
                if let Ok(tokens) = transpiler.transpile(&ast) {
                    outputs.push(tokens.to_string());
                }
            }

            monkey.reset();
        }

        // All outputs must be identical
        if !outputs.is_empty() {
            let first = &outputs[0];
            for (i, output) in outputs.iter().enumerate() {
                assert_eq!(
                    first, output,
                    "Non-deterministic output for '{}' at iteration {}",
                    input, i
                );
            }
        }
    }
}

#[test]
fn test_chaos_normalization() {
    // Test that normalization is deterministic under perturbation
    let test_cases = vec!["let x = 10", "fun f(x) { x + 1 }", "x + y * z"];

    for input in test_cases {
        let mut normalized_results = Vec::new();

        for seed in 0..5 {
            let mut monkey = ChaosMonkey::new(seed);
            monkey.perturb();

            let mut parser = Parser::new(input);
            if let Ok(ast) = parser.parse() {
                let mut normalizer = AstNormalizer::new();
                // We need to handle unbound variables for this test
                // In real usage, we'd have proper context
                match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                    normalizer.normalize(&ast)
                })) {
                    Ok(core) => {
                        normalized_results.push(format!("{:?}", core));
                    }
                    Err(_) => {
                        // Expected for expressions with free variables
                    }
                }
            }

            monkey.reset();
        }

        // Check determinism of successful normalizations
        if normalized_results.len() > 1 {
            let first = &normalized_results[0];
            for result in &normalized_results[1..] {
                assert_eq!(first, result, "Non-deterministic normalization");
            }
        }
    }
}

#[test]
fn test_concurrent_transpilation() {
    // Test that concurrent transpilation produces identical results
    let input = "fun factorial(n) { if n <= 1 { 1 } else { n * factorial(n - 1) } }";

    let handles: Vec<_> = (0..10)
        .map(|i| {
            let input = input.to_string();
            thread::spawn(move || {
                // Each thread uses different perturbation
                let mut monkey = ChaosMonkey::new(i);
                monkey.perturb();

                let mut parser = Parser::new(&input);
                let result = parser
                    .parse()
                    .and_then(|ast| {
                        let transpiler = Transpiler::new();
                        transpiler.transpile(&ast)
                    })
                    .map(|tokens| tokens.to_string());

                monkey.reset();
                result
            })
        })
        .collect();

    let results: Vec<_> = handles
        .into_iter()
        .filter_map(|h| h.join().ok())
        .filter_map(|r| r.ok())
        .collect();

    // All successful results should be identical
    if results.len() > 1 {
        let first = &results[0];
        for result in &results[1..] {
            assert_eq!(first, result, "Non-deterministic concurrent transpilation");
        }
    }
}

#[test]
fn test_reference_interpreter_determinism() {
    // Test that the reference interpreter is deterministic
    let test_cases = vec![("1 + 2", 3), ("2 * 3", 6), ("10 - 5", 5), ("20 / 4", 5)];

    for (input, expected) in test_cases {
        let mut results = Vec::new();

        for seed in 0..5 {
            let mut monkey = ChaosMonkey::new(seed);
            monkey.perturb();

            let mut parser = Parser::new(input);
            if let Ok(ast) = parser.parse() {
                // Try to normalize and interpret
                match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                    let mut normalizer = AstNormalizer::new();
                    let core = normalizer.normalize(&ast);

                    let mut interp = ReferenceInterpreter::new();
                    interp.eval(&core)
                })) {
                    Ok(Ok(value)) => {
                        results.push(format!("{:?}", value));
                    }
                    _ => {
                        // Expected for expressions with undefined behavior
                    }
                }
            }

            monkey.reset();
        }

        // All results should be identical
        if !results.is_empty() {
            let first = &results[0];
            for result in &results {
                assert_eq!(first, result, "Non-deterministic interpretation");
            }

            // Also check the expected value
            use ruchy::transpiler::Value;
            if let Ok(Ok(value)) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                let mut parser = Parser::new(input);
                let ast = parser.parse().unwrap();
                let mut normalizer = AstNormalizer::new();
                let core = normalizer.normalize(&ast);
                let mut interp = ReferenceInterpreter::new();
                interp.eval(&core)
            })) {
                match value {
                    Value::Integer(i) => assert_eq!(i, expected as i64),
                    _ => panic!("Unexpected value type"),
                }
            }
        }
    }
}

/// Property: Idempotent normalization
#[test]
fn test_normalization_idempotent() {
    let test_cases = vec![
        "42",
        "true",
        "\"hello\"",
        // Complex expressions would need proper variable binding
    ];

    for input in test_cases {
        let mut parser = Parser::new(input);
        if let Ok(ast) = parser.parse() {
            // First normalization
            let mut normalizer1 = AstNormalizer::new();
            if let Ok(core1) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                normalizer1.normalize(&ast)
            })) {
                // Second normalization of the same AST
                let mut normalizer2 = AstNormalizer::new();
                if let Ok(core2) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                    normalizer2.normalize(&ast)
                })) {
                    assert_eq!(
                        format!("{:?}", core1),
                        format!("{:?}", core2),
                        "Normalization not idempotent for: {}",
                        input
                    );
                }
            }
        }
    }
}
