//! Comprehensive test suite for SSTable data parsing improvements
//!
//! This test suite validates the enhanced data parsing capabilities including:
//! - UUID false positive elimination
//! - Improved text field extraction
//! - Better timestamp detection
//! - Robust handling of null values and edge cases

use cqlite_core::parser::types::{parse_cql_value, CqlTypeId};
use cqlite_core::types::Value;

#[cfg(test)]
mod parsing_improvements_tests {
    use super::*;

    #[test]
    fn test_uuid_false_positive_elimination() {
        // Test case 1: Valid UUID should be detected as UUID
        let valid_uuid = [
            0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4,
            0x30, 0xc8,
        ];

        // This should be detected as a valid UUID (version 1, variant bits correct)
        let (_, value) = parse_cql_value(&valid_uuid, CqlTypeId::Uuid).unwrap();
        match value {
            Value::Uuid(uuid) => assert_eq!(uuid, valid_uuid),
            _ => panic!("Expected UUID value"),
        }

        // Test case 2: 16-byte text should NOT be detected as UUID
        let text_16_bytes = b"Hello, World!16b"; // Exactly 16 bytes
        assert_eq!(text_16_bytes.len(), 16);

        // This should be detected as text, not UUID, due to improved validation
        if let Ok(text) = std::str::from_utf8(text_16_bytes) {
            let _text_value = Value::Text(text.to_string());
            assert_eq!(text, "Hello, World!16b");
        }

        // Test case 3: Invalid UUID should be rejected
        let _invalid_uuid = [
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00,
        ]; // All zeros - not a valid UUID

        // The improved parser should reject this as a UUID
        // and treat it as blob or other data type
    }

    #[test]
    fn test_text_field_extraction_deterministic() {
        // SSTable cell framing: the caller strips the cell length prefix before
        // invoking parse_cql_value.  The entire `input` slice IS the text value.

        // Test case 1: Plain ASCII text (cell already framed — no extra length prefix)
        let text = "Hello, CQLite!";
        let (remaining, value) = parse_cql_value(text.as_bytes(), CqlTypeId::Varchar).unwrap();
        match value {
            Value::Text(parsed_text) => assert_eq!(parsed_text, text),
            _ => panic!("Expected text value"),
        }
        assert!(remaining.is_empty(), "should consume all input");

        // Test case 2: Ascii type — same raw bytes, no prefix
        let ascii_text = "Hello from Ascii";
        let (remaining, value) = parse_cql_value(ascii_text.as_bytes(), CqlTypeId::Ascii).unwrap();
        match value {
            Value::Text(parsed_text) => assert_eq!(parsed_text, ascii_text),
            _ => panic!("Expected text value for Ascii type"),
        }
        assert!(remaining.is_empty(), "should consume all input");

        // Test case 3: UTF-8 with multi-byte characters
        let valid_utf8 = "🚀 CQLite with emojis! 🎉";
        let (remaining, value) =
            parse_cql_value(valid_utf8.as_bytes(), CqlTypeId::Varchar).unwrap();
        match value {
            Value::Text(parsed_text) => {
                assert!(parsed_text.contains("🚀"));
                assert_eq!(parsed_text, valid_utf8);
            }
            _ => panic!("Expected text value"),
        }
        assert!(remaining.is_empty(), "should consume all input");

        // Test case 4: Empty string (cell with 0 bytes; caller handles null vs empty at cell level)
        let (remaining, value) = parse_cql_value(&[], CqlTypeId::Varchar).unwrap();
        match value {
            Value::Text(text) => assert!(text.is_empty()),
            _ => panic!("Expected empty text value"),
        }
        assert!(remaining.is_empty());

        // Test case 5: Invalid UTF-8 must be rejected (not silently accepted)
        let invalid_utf8 = &[0xFF, 0xFE, 0xFD, 0xFC];
        assert!(
            parse_cql_value(invalid_utf8, CqlTypeId::Varchar).is_err(),
            "invalid UTF-8 must be a parse error, not silently accepted"
        );
    }

    #[cfg(feature = "legacy-heuristics")]
    #[test]
    fn test_text_field_extraction_legacy_heuristics() {
        // These tests only run when the legacy-heuristics feature is enabled.
        // They document the old behavior that is now opt-in only.

        // Legacy path 1: 4-byte big-endian length prefix
        let text = "Hello, CQLite!";
        let mut length_prefixed = Vec::new();
        length_prefixed.extend_from_slice(&(text.len() as u32).to_be_bytes());
        length_prefixed.extend_from_slice(text.as_bytes());

        let (_, value) = parse_cql_value(&length_prefixed, CqlTypeId::Varchar).unwrap();
        match value {
            Value::Text(parsed_text) => assert_eq!(parsed_text, text),
            _ => panic!("Expected text value"),
        }

        // Legacy path 2: Null-terminated string
        let null_terminated = b"Hello\0";
        let (_, value) = parse_cql_value(null_terminated, CqlTypeId::Varchar).unwrap();
        match value {
            Value::Text(parsed_text) => assert_eq!(parsed_text, "Hello"),
            _ => panic!("Expected text value"),
        }
    }

    #[test]
    fn test_timestamp_detection_improvements() {
        // Test case 1: Valid timestamp in milliseconds (stored as milliseconds per Value::Timestamp definition)
        // Issue #258 fix: parse_timestamp stores milliseconds directly, no conversion to microseconds
        let timestamp_millis = 1640995200000i64; // 2022-01-01 00:00:00 UTC in milliseconds
        let timestamp_bytes = timestamp_millis.to_be_bytes();

        let (_, value) = parse_cql_value(&timestamp_bytes, CqlTypeId::Timestamp).unwrap();
        match value {
            Value::Timestamp(ts) => {
                // Should be stored as milliseconds (no conversion)
                assert_eq!(ts, timestamp_millis);
            }
            _ => panic!("Expected timestamp value"),
        }

        // Test case 2: Another timestamp value
        let timestamp_millis2 = 1702900000000i64; // 2023-12-18 in milliseconds
        let timestamp_bytes2 = timestamp_millis2.to_be_bytes();

        let (_, value) = parse_cql_value(&timestamp_bytes2, CqlTypeId::Timestamp).unwrap();
        match value {
            Value::Timestamp(ts) => {
                // Should be stored as milliseconds (no conversion)
                assert_eq!(ts, timestamp_millis2);
            }
            _ => panic!("Expected timestamp value"),
        }

        // Test case 3: Non-timestamp 8-byte value
        let big_number = i64::MAX;
        let big_number_bytes = big_number.to_be_bytes();

        let (_, value) = parse_cql_value(&big_number_bytes, CqlTypeId::BigInt).unwrap();
        match value {
            Value::BigInt(n) => assert_eq!(n, big_number),
            _ => panic!("Expected BigInt value"),
        }
    }

    #[test]
    fn test_null_and_empty_value_handling() {
        // Test case 1: Empty data should be null
        let _empty_data: &[u8] = &[];
        // Should handle empty data gracefully

        // Test case 2: Zero-length string
        // In SSTable cell framing a zero-length cell is handled at the cell level
        // (typically encoded as null/absent).  When parse_cql_value IS called with
        // an empty slice the deterministic path returns an empty Text value.
        let (_, value) = parse_cql_value(&[], CqlTypeId::Varchar).unwrap();
        match value {
            Value::Text(text) => assert!(text.is_empty()),
            _ => panic!("Expected empty text value"),
        }

        // Test case 3: Null value handling
        // Different ways null values might be represented in SSTable format
    }

    #[test]
    fn test_binary_data_validation() {
        // Test case 1: Pure binary data should be blob
        let binary_data = &[
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
            0x0E, 0x0F,
        ];

        let (_, value) = parse_cql_value(binary_data, CqlTypeId::Blob).unwrap();
        match value {
            Value::Blob(blob) => assert_eq!(blob, binary_data),
            _ => panic!("Expected blob value"),
        }

        // Test case 2: Mixed binary/text should be handled appropriately
        let _mixed_data = b"Hello\xFF\xFEWorld";
        // Should be detected as blob due to invalid UTF-8 sequences
    }

    #[test]
    fn test_type_detection_robustness() {
        // Test various data patterns that could cause false positives

        // Test case 1: 16-byte data that looks like UUID but isn't
        let _fake_uuid_1 = [
            b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n',
            b'o', b'p',
        ]; // 16 letters - should be text, not UUID

        // Test case 2: 8-byte data that could be timestamp or bigint
        let _maybe_timestamp = 1234567890123456i64.to_be_bytes();
        // Should be detected based on value range

        // Test case 3: 4-byte data that could be int, float, or date
        let maybe_int = 42i32.to_be_bytes();
        let (_, value) = parse_cql_value(&maybe_int, CqlTypeId::Int).unwrap();
        match value {
            Value::Integer(n) => assert_eq!(n, 42),
            _ => panic!("Expected integer value"),
        }

        // Test case 4: Boolean values
        let true_byte = &[0x01];
        let (_, value) = parse_cql_value(true_byte, CqlTypeId::Boolean).unwrap();
        match value {
            Value::Boolean(b) => assert!(b),
            _ => panic!("Expected boolean value"),
        }

        let false_byte = &[0x00];
        let (_, value) = parse_cql_value(false_byte, CqlTypeId::Boolean).unwrap();
        match value {
            Value::Boolean(b) => assert!(!b),
            _ => panic!("Expected boolean value"),
        }
    }

    #[test]
    fn test_collection_type_parsing() {
        // Test case 1: Simple list
        // Format: [count:vint][element_type:u8][element1][element2]...

        // Test case 2: Simple set (similar to list but with uniqueness)

        // Test case 3: Simple map
        // Format: [count:vint][key_type:u8][value_type:u8][key1][value1][key2][value2]...

        // These tests would validate that collections are correctly parsed
        // and don't trigger false positives for other types
    }

    #[test]
    fn test_edge_cases_and_error_handling() {
        // Test case 1: Truncated data
        let _truncated_uuid = &[0x6b, 0xa7, 0xb8, 0x10]; // Only 4 bytes of UUID
                                                         // Should handle gracefully without panicking

        // Test case 2: Oversized data
        // Test with very large data that could cause memory issues

        // Test case 3: Malformed length prefixes
        let _bad_length_prefix = &[0xFF, 0xFF, 0xFF, 0xFF]; // Huge length
                                                            // Should handle gracefully

        // Test case 4: Nested error conditions
        // Complex scenarios that could trigger multiple error paths
    }
}

#[cfg(test)]
mod integration_tests {
    use super::*;

    #[test]
    fn test_real_world_parsing_scenarios() {
        // Test parsing data patterns that would be found in actual Cassandra SSTable files

        // Scenario 1: User table with mixed data types
        // id UUID, name VARCHAR, age INT, email VARCHAR, created_at TIMESTAMP

        // Scenario 2: Time series data
        // sensor_id UUID, timestamp TIMESTAMP, value DOUBLE, metadata MAP<TEXT, TEXT>

        // Scenario 3: Large text fields
        // document_id UUID, title VARCHAR, content TEXT (large), tags SET<TEXT>

        // These tests would validate end-to-end parsing with realistic data
    }

    #[test]
    fn test_performance_improvements() {
        // Test that the improved parsing doesn't significantly impact performance

        use std::time::Instant;

        let start = Instant::now();

        // Parse a large number of different data types
        for i in 0..10000 {
            let data = format!("test_string_{}", i);
            let bytes = data.as_bytes();
            let _ = parse_cql_value(bytes, CqlTypeId::Varchar);
        }

        let duration = start.elapsed();

        // Should complete within reasonable time
        assert!(
            duration.as_millis() < 1000,
            "Parsing took too long: {:?}",
            duration
        );
    }
}
