//! Property-based fuzzing tests for ClickType
//!
//! These tests use proptest to generate random inputs and verify that:
//! 1. Serialization followed by deserialization returns the original value (roundtrip property)
//! 2. Edge cases are handled correctly (empty strings, null bytes, max values, etc.)
//! 3. No panics occur regardless of input

use clicktype_core::traits::ClickHouseType;
use clicktype_core::types::{Nullable, Array};
use proptest::prelude::*;
use std::io::Cursor;

/// Helper function to test roundtrip property for any ClickHouseType
fn roundtrip<T: ClickHouseType + PartialEq + std::fmt::Debug>(value: T) {
    let mut buffer = Vec::new();
    value.write_binary(&mut buffer).expect("write_binary failed");

    let mut reader = Cursor::new(buffer);
    let decoded = T::read_binary(&mut reader).expect("read_binary failed");

    assert_eq!(value, decoded, "Roundtrip failed for value: {:?}", value);
}

// ============================================================================
// Primitive Integer Types
// ============================================================================

proptest! {
    #[test]
    fn fuzz_u8(value: u8) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_u16(value: u16) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_u32(value: u32) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_u64(value: u64) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_u128(value: u128) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_i8(value: i8) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_i16(value: i16) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_i32(value: i32) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_i64(value: i64) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_i128(value: i128) {
        roundtrip(value);
    }

    #[test]
    fn fuzz_f32(value in prop::num::f32::ANY) {
        // NaN is not equal to itself, so handle it specially
        if value.is_nan() {
            let mut buffer = Vec::new();
            value.write_binary(&mut buffer).expect("write_binary failed");
            let mut reader = Cursor::new(buffer);
            let decoded = f32::read_binary(&mut reader).expect("read_binary failed");
            assert!(decoded.is_nan(), "Expected NaN");
        } else {
            roundtrip(value);
        }
    }

    #[test]
    fn fuzz_f64(value in prop::num::f64::ANY) {
        // NaN is not equal to itself, so handle it specially
        if value.is_nan() {
            let mut buffer = Vec::new();
            value.write_binary(&mut buffer).expect("write_binary failed");
            let mut reader = Cursor::new(buffer);
            let decoded = f64::read_binary(&mut reader).expect("read_binary failed");
            assert!(decoded.is_nan(), "Expected NaN");
        } else {
            roundtrip(value);
        }
    }
}

// ============================================================================
// String Type
// ============================================================================

proptest! {
    #[test]
    fn fuzz_string(s: String) {
        roundtrip(s);
    }

    #[test]
    fn fuzz_string_with_nulls(s in "[\\x00-\\xFF]{0,100}") {
        // Test strings with null bytes and special characters
        roundtrip(s);
    }

    #[test]
    fn fuzz_string_unicode(s in "[\\u{0}-\\u{10FFFF}]{0,50}") {
        // Test full Unicode range
        roundtrip(s);
    }

    #[test]
    fn fuzz_string_large(s in prop::collection::vec(any::<u8>(), 0..10_000)) {
        // Test large strings (up to 10KB)
        let s = String::from_utf8_lossy(&s).into_owned();
        roundtrip(s);
    }
}

// ============================================================================
// Nullable Types
// ============================================================================

proptest! {
    #[test]
    fn fuzz_nullable_u64(value: Option<u64>) {
        roundtrip(Nullable(value));
    }

    #[test]
    fn fuzz_nullable_i64(value: Option<i64>) {
        roundtrip(Nullable(value));
    }

    #[test]
    fn fuzz_nullable_f64(value: Option<f64>) {
        let value = Nullable(value);
        if let Some(v) = value.0 {
            if v.is_nan() {
                // Special handling for NaN
                let mut buffer = Vec::new();
                value.write_binary(&mut buffer).expect("write_binary failed");
                let mut reader = Cursor::new(buffer);
                let decoded = Nullable::<f64>::read_binary(&mut reader).expect("read_binary failed");
                assert!(decoded.0.is_some() && decoded.0.unwrap().is_nan(), "Expected Some(NaN)");
                return Ok(());
            }
        }
        roundtrip(value);
    }

    #[test]
    fn fuzz_nullable_string(value: Option<String>) {
        roundtrip(Nullable(value));
    }

    #[test]
    fn fuzz_nullable_none_heavy(value in prop::option::weighted(0.9, any::<u64>())) {
        // Test with 90% None values
        roundtrip(Nullable(value));
    }

    #[test]
    fn fuzz_nullable_some_heavy(value in prop::option::weighted(0.1, any::<u64>())) {
        // Test with 10% None values (90% Some)
        roundtrip(Nullable(value));
    }
}

// ============================================================================
// Array Types
// ============================================================================

proptest! {
    #[test]
    fn fuzz_array_u64(values: Vec<u64>) {
        let array_values: Array<u64> = values;
        roundtrip(array_values);
    }

    #[test]
    fn fuzz_array_i32(values: Vec<i32>) {
        let array_values: Array<i32> = values;
        roundtrip(array_values);
    }

    #[test]
    fn fuzz_array_f64(values: Vec<f64>) {
        // Filter out NaN for simplicity in equality check
        let filtered: Array<f64> = values.into_iter().filter(|v| !v.is_nan()).collect();
        roundtrip(filtered);
    }

    #[test]
    fn fuzz_array_string(values: Vec<String>) {
        let array_values: Array<String> = values;
        roundtrip(array_values);
    }

    #[test]
    fn fuzz_array_empty(n in 0usize..10) {
        // Test empty arrays of various types
        let _ = n; // Use n to satisfy proptest
        roundtrip(vec![] as Array<u64>);
        roundtrip(vec![] as Array<String>);
        roundtrip(vec![] as Array<i32>);
    }

    #[test]
    fn fuzz_array_single(value: u64) {
        // Test single-element arrays
        roundtrip(vec![value]);
    }

    #[test]
    fn fuzz_array_large(size in 0usize..1000) {
        // Test arrays up to 1000 elements
        let values: Array<u64> = (0..size as u64).collect();
        roundtrip(values);
    }
}

// ============================================================================
// Nested Nullable Arrays
// ============================================================================

proptest! {
    #[test]
    fn fuzz_array_nullable_u64(values: Vec<Option<u64>>) {
        let nullable_values: Array<Nullable<u64>> = values.into_iter().map(Nullable).collect();
        roundtrip(nullable_values);
    }

    #[test]
    fn fuzz_nested_array(outer_len in 0usize..10, inner_len in 0usize..10) {
        // Test nested arrays Array<Array<u64>>
        let nested: Array<Array<u64>> = (0..outer_len)
            .map(|i| {
                let inner: Array<u64> = (0..inner_len).map(|j| (i * 100 + j) as u64).collect();
                inner
            })
            .collect();
        roundtrip(nested);
    }
}

// ============================================================================
// Edge Cases
// ============================================================================

#[test]
fn test_edge_case_max_integers() {
    roundtrip(u8::MAX);
    roundtrip(u16::MAX);
    roundtrip(u32::MAX);
    roundtrip(u64::MAX);
    roundtrip(u128::MAX);

    roundtrip(i8::MAX);
    roundtrip(i16::MAX);
    roundtrip(i32::MAX);
    roundtrip(i64::MAX);
    roundtrip(i128::MAX);

    roundtrip(i8::MIN);
    roundtrip(i16::MIN);
    roundtrip(i32::MIN);
    roundtrip(i64::MIN);
    roundtrip(i128::MIN);
}

#[test]
fn test_edge_case_floats() {
    roundtrip(f32::INFINITY);
    roundtrip(f32::NEG_INFINITY);
    roundtrip(f32::MIN);
    roundtrip(f32::MAX);
    roundtrip(0.0f32);
    roundtrip(-0.0f32);

    roundtrip(f64::INFINITY);
    roundtrip(f64::NEG_INFINITY);
    roundtrip(f64::MIN);
    roundtrip(f64::MAX);
    roundtrip(0.0f64);
    roundtrip(-0.0f64);
}

#[test]
fn test_edge_case_empty_string() {
    roundtrip(String::new());
    roundtrip("".to_string());
}

#[test]
fn test_edge_case_string_with_nulls() {
    roundtrip("\0".to_string());
    roundtrip("before\0after".to_string());
    roundtrip("\0\0\0".to_string());
}

#[test]
fn test_edge_case_unicode() {
    roundtrip("🦀".to_string());
    roundtrip("🔥🚀🎉".to_string());
    roundtrip("αβγδε".to_string());
    roundtrip("你好世界".to_string());
    roundtrip("مرحبا".to_string());
}

#[test]
fn test_edge_case_nullable_none() {
    roundtrip(Nullable::<u64>(None));
    roundtrip(Nullable::<String>(None));
    roundtrip(Nullable::<i32>(None));
}

#[test]
fn test_edge_case_nullable_some_zero() {
    roundtrip(Nullable(Some(0u64)));
    roundtrip(Nullable(Some(0i32)));
    roundtrip(Nullable(Some(0.0f64)));
}

#[test]
fn test_edge_case_empty_arrays() {
    roundtrip(vec![] as Array<u64>);
    roundtrip(vec![] as Array<String>);
    roundtrip(vec![] as Array<Nullable<i32>>);
}

#[test]
fn test_edge_case_array_with_nulls() {
    let values: Array<Nullable<u64>> = vec![
        Nullable(Some(1u64)),
        Nullable(None),
        Nullable(Some(2u64)),
        Nullable(None),
        Nullable(None),
        Nullable(Some(3u64)),
    ];
    roundtrip(values);
}

// ============================================================================
// Stress Tests
// ============================================================================

#[test]
fn stress_test_large_string() {
    // 1 MB string
    let large_string = "a".repeat(1_000_000);
    roundtrip(large_string);
}

#[test]
fn stress_test_large_array() {
    // Array with 100k elements
    let large_array: Array<u64> = (0..100_000u64).collect();
    roundtrip(large_array);
}

#[test]
fn stress_test_deeply_nested() {
    // Array<Array<Array<u64>>>
    let inner: Array<u64> = vec![1, 2, 3];
    let middle: Array<Array<u64>> = vec![inner.clone(), inner.clone()];
    let outer: Array<Array<Array<u64>>> = vec![middle.clone(), middle];
    roundtrip(outer);
}
