use crate::events::{Consumer, Event};
use crate::parsers::wasot::{MetaLine, QuotedString};
use prost::Message;
use std::io::{self, Write};

// Include the generated protobuf code
pub mod proto {
    include!(concat!(env!("OUT_DIR"), "/wasot.rs"));
}

/// Magic bytes for Wasot binary format identification
/// Header layout (10 bytes total):
/// - Bytes 0-1: Invalid UTF-8 sequence (0xFF, 0xFE) - ensures text files won't match
/// - Bytes 2-5: ASCII magic "WSOT" - human readable in hex dumps
/// - Bytes 6-9: Version (u32 little-endian) - allows for future versioning
pub mod binary_format {
    /// Invalid UTF-8 prefix bytes (ensures text files won't accidentally match)
    pub const INVALID_UTF8_PREFIX: [u8; 2] = [0xFF, 0xFE];

    /// ASCII magic identifier
    pub const MAGIC_ASCII: [u8; 4] = *b"WSOT";

    /// Current binary format version
    pub const VERSION: u32 = 1;

    /// Total header size in bytes
    pub const HEADER_SIZE: usize = 10;

    /// Build the complete magic header with version
    #[must_use]
    pub fn build_header() -> [u8; HEADER_SIZE] {
        let mut header = [0u8; HEADER_SIZE];
        header[0..2].copy_from_slice(&INVALID_UTF8_PREFIX);
        header[2..6].copy_from_slice(&MAGIC_ASCII);
        header[6..10].copy_from_slice(&VERSION.to_le_bytes());
        header
    }

    /// Check if data starts with valid Wasot binary magic header
    /// Returns Some(version) if valid, None otherwise
    #[must_use]
    pub fn check_header(data: &[u8]) -> Option<u32> {
        if data.len() < HEADER_SIZE {
            return None;
        }
        if data[0..2] != INVALID_UTF8_PREFIX {
            return None;
        }
        if data[2..6] != MAGIC_ASCII {
            return None;
        }
        let version = u32::from_le_bytes([data[6], data[7], data[8], data[9]]);
        Some(version)
    }
}

use proto::*;

/// A listener that converts Wasot events to binary protobuf format using length-delimited encoding
pub struct BinaryProtobufConsumer<W: Write> {
    writer: W,
    header_written: bool,
}

impl<W: Write> BinaryProtobufConsumer<W> {
    /// Create a new binary protobuf listener that writes to the given writer
    pub fn new(writer: W) -> Self {
        Self {
            writer,
            header_written: false,
        }
    }

    /// Write the magic header if not already written
    fn ensure_header_written(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        if !self.header_written {
            let header = binary_format::build_header();
            self.writer.write_all(&header)?;
            self.header_written = true;
        }
        Ok(())
    }

    /// Convert a Wasot event to protobuf format and write as length-delimited binary
    pub fn convert_and_write(&mut self, event: &Event) -> Result<(), Box<dyn std::error::Error>> {
        debug_assert!(event.validate(), "Event should be valid before conversion");

        // Ensure magic header is written before first event
        self.ensure_header_written()?;

        let proto_event = self.convert_event(event);
        debug_assert!(
            proto_event.event.is_some(),
            "Protobuf event should have event field set"
        );

        // Encode to a buffer first, then write to the writer
        let mut buffer = Vec::new();
        let initial_len = buffer.len();
        proto_event.encode_length_delimited(&mut buffer)?;
        debug_assert!(
            buffer.len() > initial_len,
            "Buffer should grow after encoding"
        );

        self.writer.write_all(&buffer)?;
        self.writer.flush()?;

        Ok(())
    }

    /// Convert a Wasot ElementEvent to a protobuf WasotEvent
    fn convert_event(&self, event: &Event) -> WasotEvent {
        match event {
            Event::BlockStarted { meta_line } => {
                if let Some(line_idx) = meta_line.line_index {
                    debug_assert!(line_idx < 1_000_000, "Line index should be reasonable");
                }
                WasotEvent {
                    event: Some(wasot_event::Event::BlockStarted(BlockStartedEvent {
                        meta_line: Some(self.convert_meta_line(meta_line)),
                    })),
                }
            }
            Event::BlockFinished => WasotEvent {
                event: Some(wasot_event::Event::BlockFinished(BlockFinishedEvent {})),
            },
            Event::TextFinished {
                lines,
                links: _,
                footnotes,
                ..
            } => {
                debug_assert!(!lines.is_empty(), "Lines should not be empty");
                debug_assert!(lines.len() < 10_000, "Lines count should be reasonable");

                // Convert footnotes to protobuf format
                let proto_footnotes = footnotes
                    .iter()
                    .map(|f| proto::Footnote {
                        marker: f.marker,
                        link: f.link.clone(),
                    })
                    .collect();

                WasotEvent {
                    event: Some(wasot_event::Event::TextFinished(TextEvent {
                        lines: lines.clone(),
                        links: Vec::new(), // TODO: Convert links to protobuf format
                        footnotes: proto_footnotes,
                    })),
                }
            }
            Event::ContextLine { meta_line } => {
                if let Some(line_idx) = meta_line.line_index {
                    debug_assert!(line_idx < 1_000_000, "Line index should be reasonable");
                }
                WasotEvent {
                    event: Some(wasot_event::Event::ContextLine(proto::ContextLineEvent {
                        meta_line: Some(self.convert_meta_line(meta_line)),
                    })),
                }
            }
            Event::VoidElement { meta_line } => {
                // For now, emit VoidElement as BlockStarted (until protobuf schema is updated)
                if let Some(line_idx) = meta_line.line_index {
                    debug_assert!(line_idx < 1_000_000, "Line index should be reasonable");
                }
                WasotEvent {
                    event: Some(wasot_event::Event::BlockStarted(BlockStartedEvent {
                        meta_line: Some(self.convert_meta_line(meta_line)),
                    })),
                }
            }
        }
    }

    /// Convert a Wasot MetaLine to protobuf format
    fn convert_meta_line(&self, meta_line: &MetaLine) -> proto::MetaLine {
        proto::MetaLine {
            type_name: meta_line.type_name.clone(),
            level: meta_line.level as u32,
            line_index: meta_line.line_index.map(|idx| idx as u32),
            inline_text: Some(self.convert_quoted_text(&meta_line.inline_text)),
            arguments: Some(self.convert_parsed_params(&meta_line.arguments)),
            block_id: if meta_line.id.is_empty() {
                None
            } else {
                Some(meta_line.id.as_str().to_string())
            },
            is_void: meta_line.is_void,
        }
    }

    /// Convert ParsedArguments to protobuf Arguments format
    fn convert_parsed_params(
        &self,
        params: &crate::parsers::wasot::ParsedArguments,
    ) -> proto::Arguments {
        proto::Arguments {
            inline_text: Some(QuotedText {
                content: String::new(), // ParsedArguments doesn't have inline_text
                quoted: false,
                links: Vec::new(), // TODO: Convert links to protobuf format
            }),
            args: params
                .args
                .iter()
                .map(|arg| self.convert_quoted_text(arg))
                .collect(),
            kwargs: params
                .kwargs
                .iter()
                .map(|(key, value)| KeyValuePair {
                    key: Some(self.convert_quoted_text(key)),
                    value: Some(self.convert_quoted_text(value)),
                })
                .collect(),
        }
    }

    /// Convert QuotedString to protobuf QuotedText
    fn convert_quoted_text(&self, quoted_string: &QuotedString) -> QuotedText {
        QuotedText {
            content: quoted_string.content.clone(),
            quoted: quoted_string.quoted,
            links: Vec::new(), // TODO: Convert links to protobuf format
        }
    }
}

impl Consumer for BinaryProtobufConsumer<std::io::Stdout> {
    fn handle(&mut self, event: Event) {
        if let Err(e) = self.convert_and_write(&event) {
            eprintln!("Error writing binary protobuf event: {e}");
        }
    }
}

/// Binary protobuf reader that can read length-delimited WasotEvent messages
pub struct BinaryProtobufReader<R: io::Read> {
    reader: R,
    header_read: bool,
    format_version: Option<u32>,
}

impl<R: io::Read> BinaryProtobufReader<R> {
    /// Create a new binary protobuf reader
    pub fn new(reader: R) -> Self {
        Self {
            reader,
            header_read: false,
            format_version: None,
        }
    }

    /// Get the format version (available after reading header)
    #[allow(dead_code)]
    pub fn format_version(&self) -> Option<u32> {
        self.format_version
    }

    /// Read and validate the magic header
    fn ensure_header_read(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        if self.header_read {
            return Ok(());
        }

        let mut header = [0u8; binary_format::HEADER_SIZE];
        self.reader.read_exact(&mut header)?;

        match binary_format::check_header(&header) {
            Some(version) => {
                self.format_version = Some(version);
                self.header_read = true;
                Ok(())
            }
            None => Err("Invalid Wasot binary format: magic header not found".into()),
        }
    }

    /// Read the next WasotEvent from the binary stream
    pub fn read_next_event(&mut self) -> Result<Option<WasotEvent>, Box<dyn std::error::Error>> {
        // Ensure header is read first
        if !self.header_read {
            if let Err(e) = self.ensure_header_read() {
                // If we can't read the header, it might be EOF on empty file or old format
                // For backwards compat with old format without header, try reading as-is
                // This can be removed once migration is complete
                let err_str = e.to_string();
                if err_str.contains("magic header not found") {
                    return Err(e);
                }
                return Ok(None);
            }
        }

        // Try to read a varint for the length
        let length = match self.read_varint() {
            Ok(len) => len,
            Err(_) => return Ok(None), // EOF or error reading length
        };

        // Read the message data
        let mut message_buf = vec![0u8; length as usize];
        match self.reader.read_exact(&mut message_buf) {
            Ok(_) => {}
            Err(_) => return Ok(None), // EOF or error reading message
        }

        // Decode the message
        match WasotEvent::decode(&message_buf[..]) {
            Ok(event) => Ok(Some(event)),
            Err(e) => Err(Box::new(e)),
        }
    }

    /// Read a varint from the reader (protobuf variable-length integer encoding)
    fn read_varint(&mut self) -> Result<u64, Box<dyn std::error::Error>> {
        let mut result = 0u64;
        let mut shift = 0;

        loop {
            let mut byte = [0u8; 1];
            self.reader.read_exact(&mut byte)?;
            let b = byte[0];

            result |= ((b & 0x7F) as u64) << shift;

            if (b & 0x80) == 0 {
                break;
            }

            shift += 7;
            if shift >= 64 {
                return Err("varint too long".into());
            }
        }

        Ok(result)
    }

    /// Read all events from the binary stream
    #[allow(dead_code)]
    pub fn read_all_events(&mut self) -> Result<Vec<WasotEvent>, Box<dyn std::error::Error>> {
        let mut events = Vec::new();

        while let Some(event) = self.read_next_event()? {
            events.push(event);
        }

        Ok(events)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parsers::wasot::{Id, ParsedArguments, QuotedString};
    use std::io::Cursor;

    #[test]
    fn test_binary_protobuf_roundtrip() {
        // Create a test event
        let meta_line = MetaLine {
            type_name: "paragraph".to_string(),
            level: 1,
            line_index: Some(1),
            is_void: false,
            inline_text: QuotedString::new("Hello".to_string(), false),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
            id: Id::new("test123".to_string()),
        };

        let event = Event::BlockStarted { meta_line };

        // Write to binary format
        let mut buffer = Vec::new();
        let mut listener = BinaryProtobufConsumer::new(&mut buffer);
        listener.convert_and_write(&event).unwrap();

        // Read back from binary format
        let cursor = Cursor::new(buffer);
        let mut reader = BinaryProtobufReader::new(cursor);
        let events = reader.read_all_events().unwrap();

        // Verify we got one event back
        assert_eq!(events.len(), 1);

        // Verify the event structure
        let read_event = &events[0];
        assert!(matches!(
            read_event.event,
            Some(wasot_event::Event::BlockStarted(_))
        ));

        if let Some(wasot_event::Event::BlockStarted(block_event)) = &read_event.event {
            assert!(block_event.meta_line.is_some());

            let meta = block_event.meta_line.as_ref().unwrap();
            assert_eq!(meta.line_index, Some(1));
            assert_eq!(meta.type_name, "paragraph");
            assert_eq!(meta.level, 1);
            assert_eq!(meta.block_id, Some("test123".to_string()));
        }
    }

    #[test]
    fn test_multiple_events_roundtrip() {
        let events = vec![
            Event::BlockStarted {
                meta_line: MetaLine {
                    type_name: "div".to_string(),
                    level: 1,
                    line_index: Some(0),
                    is_void: false,
                    inline_text: QuotedString::new(String::new(), false),
                    arguments: ParsedArguments {
                        args: vec![],
                        kwargs: vec![],
                    },
                    id: Id::empty(),
                },
            },
            Event::TextFinished {
                lines: vec!["Hello world".to_string()],
                links: vec![Vec::new()],
                footnotes: vec![],
            },
            Event::BlockFinished,
        ];

        // Write all events to binary format
        let mut buffer = Vec::new();
        let mut listener = BinaryProtobufConsumer::new(&mut buffer);
        for event in &events {
            listener.convert_and_write(event).unwrap();
        }

        // Read all events back
        let cursor = Cursor::new(buffer);
        let mut reader = BinaryProtobufReader::new(cursor);
        let read_events = reader.read_all_events().unwrap();

        // Verify we got the same number of events
        assert_eq!(read_events.len(), 3);

        // Verify event types in correct order
        assert!(matches!(
            read_events[0].event,
            Some(wasot_event::Event::BlockStarted(_))
        ));
        assert!(matches!(
            read_events[1].event,
            Some(wasot_event::Event::TextFinished(_))
        ));
        assert!(matches!(
            read_events[2].event,
            Some(wasot_event::Event::BlockFinished(_))
        ));
    }

    #[test]
    fn test_new_constructor() {
        let buffer = Vec::new();
        let consumer = BinaryProtobufConsumer::new(buffer);
        // Just verify the constructor creates the struct properly
        assert_eq!(consumer.writer.len(), 0);
        assert!(!consumer.header_written);
    }

    #[test]
    fn test_header_written_only_once() {
        let mut buffer = Vec::new();

        // Write first event
        let event1 = Event::BlockStarted {
            meta_line: MetaLine {
                type_name: "test1".to_string(),
                level: 1,
                line_index: Some(1),
                is_void: false,
                inline_text: QuotedString::new(String::new(), false),
                arguments: ParsedArguments {
                    args: vec![],
                    kwargs: vec![],
                },
                id: Id::empty(),
            },
        };

        {
            let mut consumer = BinaryProtobufConsumer::new(&mut buffer);
            consumer.convert_and_write(&event1).unwrap();
            assert!(consumer.header_written);
        }

        let _len_after_first = buffer.len();

        // Write second event using a new consumer on the same buffer
        // This simulates continuing to write but we need to track header state
        // In practice, one consumer is used - let's test with fresh consumer that owns buffer
        let mut buffer2 = Vec::new();
        let mut consumer = BinaryProtobufConsumer::new(&mut buffer2);

        // Write multiple events
        consumer.convert_and_write(&event1).unwrap();
        assert!(consumer.header_written);

        let event2 = Event::BlockFinished;
        consumer.convert_and_write(&event2).unwrap();
        assert!(consumer.header_written);

        // Verify header is at the start only
        assert_eq!(buffer2[0..2], binary_format::INVALID_UTF8_PREFIX);
        assert_eq!(buffer2[2..6], binary_format::MAGIC_ASCII);

        // Count occurrences of header prefix in the buffer
        let header_count = buffer2
            .windows(2)
            .filter(|w| w == &binary_format::INVALID_UTF8_PREFIX)
            .count();
        assert_eq!(header_count, 1, "Header should appear exactly once");
    }

    #[test]
    fn test_output_starts_with_header() {
        let mut buffer = Vec::new();
        let mut consumer = BinaryProtobufConsumer::new(&mut buffer);

        let event = Event::TextFinished {
            lines: vec!["test".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        };
        consumer.convert_and_write(&event).unwrap();

        // Verify output starts with the magic header
        assert!(buffer.len() >= binary_format::HEADER_SIZE);
        assert_eq!(buffer[0..2], binary_format::INVALID_UTF8_PREFIX);
        assert_eq!(buffer[2..6], binary_format::MAGIC_ASCII);

        // Verify version
        let version = u32::from_le_bytes([buffer[6], buffer[7], buffer[8], buffer[9]]);
        assert_eq!(version, binary_format::VERSION);
    }

    #[test]
    fn test_context_line_event_conversion() {
        let mut buffer = Vec::new();
        let mut consumer = BinaryProtobufConsumer::new(&mut buffer);

        let event = Event::ContextLine {
            meta_line: MetaLine {
                type_name: "id".to_string(),
                level: 0,
                line_index: Some(0),
                is_void: false,
                inline_text: QuotedString::new("doc123".to_string(), false),
                arguments: ParsedArguments {
                    args: vec![],
                    kwargs: vec![],
                },
                id: Id::empty(),
            },
        };

        consumer.convert_and_write(&event).unwrap();

        // Verify it can be read back
        let cursor = Cursor::new(buffer);
        let mut reader = BinaryProtobufReader::new(cursor);
        let events = reader.read_all_events().unwrap();

        assert_eq!(events.len(), 1);
        assert!(matches!(
            events[0].event,
            Some(wasot_event::Event::ContextLine(_))
        ));
    }

    #[test]
    fn test_convert_and_write_with_debug_assertions() {
        let mut buffer = Vec::new();
        let mut consumer = BinaryProtobufConsumer::new(&mut buffer);

        // Test with BlockStarted event
        let meta_line = MetaLine {
            type_name: "test".to_string(),
            level: 2,
            line_index: Some(42),
            is_void: false,
            inline_text: QuotedString::new("content".to_string(), true),
            arguments: ParsedArguments {
                args: vec![QuotedString::new("arg1".to_string(), false)],
                kwargs: vec![(
                    QuotedString::new("key".to_string(), false),
                    QuotedString::new("value".to_string(), true),
                )],
            },
            id: Id::new("test_id".to_string()),
        };
        let event = Event::BlockStarted { meta_line };

        let result = consumer.convert_and_write(&event);
        assert!(result.is_ok());
        assert!(!buffer.is_empty());
    }

    #[test]
    fn test_convert_event_all_types() {
        let consumer = BinaryProtobufConsumer::new(Vec::new());

        // Test BlockStarted conversion
        let meta_line = MetaLine {
            type_name: "paragraph".to_string(),
            level: 1,
            line_index: Some(10),
            is_void: false,
            inline_text: QuotedString::new("test".to_string(), false),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
            id: Id::empty(),
        };
        let block_event = Event::BlockStarted { meta_line };
        let proto_event = consumer.convert_event(&block_event);
        assert!(matches!(
            proto_event.event,
            Some(wasot_event::Event::BlockStarted(_))
        ));

        // Test BlockFinished conversion
        let finish_event = Event::BlockFinished;
        let proto_event = consumer.convert_event(&finish_event);
        assert!(matches!(
            proto_event.event,
            Some(wasot_event::Event::BlockFinished(_))
        ));

        // Test TextFinished conversion
        use crate::parsers::wasot::Footnote;
        let text_event = Event::TextFinished {
            lines: vec!["line1".to_string(), "line2".to_string()],
            links: vec![Vec::new(), Vec::new()],
            footnotes: vec![
                Footnote {
                    marker: 1,
                    link: "http://example.com".to_string(),
                },
                Footnote {
                    marker: 2,
                    link: "http://example2.com".to_string(),
                },
            ],
        };
        let proto_event = consumer.convert_event(&text_event);
        if let Some(wasot_event::Event::TextFinished(text)) = proto_event.event {
            assert_eq!(text.lines.len(), 2);
            assert_eq!(text.footnotes.len(), 2);
            assert_eq!(text.footnotes[0].marker, 1);
            assert_eq!(text.footnotes[0].link, "http://example.com");
        } else {
            panic!("Expected TextFinished event");
        }
    }

    #[test]
    fn test_convert_meta_line_variations() {
        let consumer = BinaryProtobufConsumer::new(Vec::new());

        // Test with all fields populated
        let meta_line = MetaLine {
            type_name: "section".to_string(),
            level: 3,
            line_index: Some(123),
            is_void: false,
            inline_text: QuotedString::new("inline content".to_string(), true),
            arguments: ParsedArguments {
                args: vec![QuotedString::new("arg".to_string(), false)],
                kwargs: vec![(
                    QuotedString::new("param".to_string(), false),
                    QuotedString::new("val".to_string(), true),
                )],
            },
            id: Id::new("my_id".to_string()),
        };

        let proto_meta = consumer.convert_meta_line(&meta_line);
        assert_eq!(proto_meta.type_name, "section");
        assert_eq!(proto_meta.level, 3);
        assert_eq!(proto_meta.line_index, Some(123));
        assert_eq!(proto_meta.block_id, Some("my_id".to_string()));

        // Test with empty ID
        let meta_line_no_id = MetaLine {
            type_name: "div".to_string(),
            level: 1,
            line_index: None,
            inline_text: QuotedString::new(String::new(), false),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
            id: Id::empty(),
            is_void: false,
        };

        let proto_meta_no_id = consumer.convert_meta_line(&meta_line_no_id);
        assert_eq!(proto_meta_no_id.block_id, None);
        assert_eq!(proto_meta_no_id.line_index, None);
    }

    #[test]
    fn test_convert_parsed_params() {
        let consumer = BinaryProtobufConsumer::new(Vec::new());

        let params = ParsedArguments {
            args: vec![
                QuotedString::new("first".to_string(), false),
                QuotedString::new("second".to_string(), true),
            ],
            kwargs: vec![
                (
                    QuotedString::new("key1".to_string(), false),
                    QuotedString::new("value1".to_string(), false),
                ),
                (
                    QuotedString::new("key2".to_string(), false),
                    QuotedString::new("value2".to_string(), true),
                ),
            ],
        };

        let proto_args = consumer.convert_parsed_params(&params);
        assert_eq!(proto_args.args.len(), 2);
        assert_eq!(proto_args.kwargs.len(), 2);

        // Check args conversion
        assert_eq!(proto_args.args[0].content, "first");
        assert!(!proto_args.args[0].quoted);
        assert_eq!(proto_args.args[1].content, "second");
        assert!(proto_args.args[1].quoted);

        // Check kwargs conversion
        assert_eq!(proto_args.kwargs[0].key.as_ref().unwrap().content, "key1");
        assert_eq!(
            proto_args.kwargs[0].value.as_ref().unwrap().content,
            "value1"
        );
        assert!(!proto_args.kwargs[0].value.as_ref().unwrap().quoted);

        assert_eq!(proto_args.kwargs[1].key.as_ref().unwrap().content, "key2");
        assert_eq!(
            proto_args.kwargs[1].value.as_ref().unwrap().content,
            "value2"
        );
        assert!(proto_args.kwargs[1].value.as_ref().unwrap().quoted);
    }

    #[test]
    fn test_convert_quoted_text() {
        let consumer = BinaryProtobufConsumer::new(Vec::new());

        // Test unquoted string
        let unquoted = QuotedString::new("plain text".to_string(), false);
        let proto_unquoted = consumer.convert_quoted_text(&unquoted);
        assert_eq!(proto_unquoted.content, "plain text");
        assert!(!proto_unquoted.quoted);

        // Test quoted string
        let quoted = QuotedString::new("quoted text".to_string(), true);
        let proto_quoted = consumer.convert_quoted_text(&quoted);
        assert_eq!(proto_quoted.content, "quoted text");
        assert!(proto_quoted.quoted);
    }

    #[test]
    fn test_consumer_handle_trait() {
        let mut consumer = BinaryProtobufConsumer::new(std::io::stdout());

        let meta_line = MetaLine {
            type_name: "test".to_string(),
            level: 1,
            line_index: Some(1),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
            id: Id::empty(),
        };
        let event = Event::BlockStarted { meta_line };

        // This should not panic - just testing the handle method compiles and runs
        consumer.handle(event);
    }

    #[test]
    fn test_binary_protobuf_reader_new() {
        let data = b"test data";
        let cursor = Cursor::new(data);
        let reader = BinaryProtobufReader::new(cursor);
        // Just verify the constructor works
        assert_eq!(reader.reader.get_ref().as_slice(), data);
        assert!(!reader.header_read);
        assert!(reader.format_version.is_none());
    }

    #[test]
    fn test_binary_format_header() {
        let header = binary_format::build_header();
        assert_eq!(header.len(), binary_format::HEADER_SIZE);

        // Check magic bytes
        assert_eq!(header[0..2], binary_format::INVALID_UTF8_PREFIX);
        assert_eq!(header[2..6], binary_format::MAGIC_ASCII);

        // Check version
        let version = u32::from_le_bytes([header[6], header[7], header[8], header[9]]);
        assert_eq!(version, binary_format::VERSION);

        // Verify check_header works
        assert_eq!(
            binary_format::check_header(&header),
            Some(binary_format::VERSION)
        );

        // Verify check_header rejects invalid data
        assert_eq!(binary_format::check_header(b"invalid"), None);
        assert_eq!(
            binary_format::check_header(&[
                0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
            ]),
            None
        );
    }

    #[test]
    fn test_binary_format_check_header_too_short() {
        // Data shorter than HEADER_SIZE should return None
        assert_eq!(binary_format::check_header(&[]), None);
        assert_eq!(binary_format::check_header(&[0xFF]), None);
        assert_eq!(binary_format::check_header(&[0xFF, 0xFE]), None);
        assert_eq!(
            binary_format::check_header(&[0xFF, 0xFE, 0x57, 0x53, 0x4F, 0x54]),
            None
        );
        assert_eq!(
            binary_format::check_header(&[0xFF, 0xFE, 0x57, 0x53, 0x4F, 0x54, 0x01, 0x00, 0x00]),
            None
        ); // 9 bytes, need 10
    }

    #[test]
    fn test_binary_format_check_header_wrong_utf8_prefix() {
        // Wrong UTF-8 prefix bytes
        assert_eq!(
            binary_format::check_header(&[
                0x00, 0x00, 0x57, 0x53, 0x4F, 0x54, 0x01, 0x00, 0x00, 0x00
            ]),
            None
        );
        assert_eq!(
            binary_format::check_header(&[
                0xFF, 0x00, 0x57, 0x53, 0x4F, 0x54, 0x01, 0x00, 0x00, 0x00
            ]),
            None
        );
        assert_eq!(
            binary_format::check_header(&[
                0x00, 0xFE, 0x57, 0x53, 0x4F, 0x54, 0x01, 0x00, 0x00, 0x00
            ]),
            None
        );
    }

    #[test]
    fn test_binary_format_check_header_wrong_magic() {
        // Correct UTF-8 prefix but wrong ASCII magic
        assert_eq!(
            binary_format::check_header(&[
                0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00
            ]),
            None
        );
        assert_eq!(
            binary_format::check_header(&[
                0xFF, 0xFE, 0x57, 0x53, 0x4F, 0x00, 0x01, 0x00, 0x00, 0x00
            ]),
            None
        ); // "WSO\0" instead of "WSOT"
        assert_eq!(
            binary_format::check_header(&[
                0xFF, 0xFE, b'X', b'Y', b'Z', b'W', 0x01, 0x00, 0x00, 0x00
            ]),
            None
        );
    }

    #[test]
    fn test_binary_format_check_header_various_versions() {
        // Version 0
        let mut header_v0 = binary_format::build_header();
        header_v0[6..10].copy_from_slice(&0u32.to_le_bytes());
        assert_eq!(binary_format::check_header(&header_v0), Some(0));

        // Version 2
        let mut header_v2 = binary_format::build_header();
        header_v2[6..10].copy_from_slice(&2u32.to_le_bytes());
        assert_eq!(binary_format::check_header(&header_v2), Some(2));

        // Max version
        let mut header_max = binary_format::build_header();
        header_max[6..10].copy_from_slice(&u32::MAX.to_le_bytes());
        assert_eq!(binary_format::check_header(&header_max), Some(u32::MAX));
    }

    #[test]
    fn test_binary_format_constants() {
        // Verify the constants are as documented
        assert_eq!(binary_format::INVALID_UTF8_PREFIX, [0xFF, 0xFE]);
        assert_eq!(binary_format::MAGIC_ASCII, *b"WSOT");
        assert_eq!(binary_format::VERSION, 1);
        assert_eq!(binary_format::HEADER_SIZE, 10);
    }

    #[test]
    fn test_read_varint() {
        let data = vec![0x96, 0x01]; // 150 in varint encoding
        let cursor = Cursor::new(data);
        let mut reader = BinaryProtobufReader::new(cursor);

        let result = reader.read_varint().unwrap();
        assert_eq!(result, 150);
    }

    #[test]
    fn test_read_varint_error() {
        // Test varint that's too long (more than 64 bits)
        let data = vec![0xFF; 10]; // 10 bytes of 0xFF will exceed 64 bits
        let cursor = Cursor::new(data);
        let mut reader = BinaryProtobufReader::new(cursor);

        let result = reader.read_varint();
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("varint too long"));
    }

    #[test]
    fn test_read_next_event_eof() {
        // Empty cursor should return Ok(None)
        let cursor = Cursor::new(Vec::<u8>::new());
        let mut reader = BinaryProtobufReader::new(cursor);

        let result = reader.read_next_event().unwrap();
        assert!(result.is_none());
    }

    #[test]
    fn test_read_all_events_empty() {
        let cursor = Cursor::new(Vec::<u8>::new());
        let mut reader = BinaryProtobufReader::new(cursor);

        let events = reader.read_all_events().unwrap();
        assert!(events.is_empty());
    }

    #[test]
    fn test_format_version_after_reading() {
        // Create valid binary data with header
        let mut buffer = Vec::new();
        let mut consumer = BinaryProtobufConsumer::new(&mut buffer);
        let event = Event::BlockFinished;
        consumer.convert_and_write(&event).unwrap();

        // Read it back and check version
        let cursor = Cursor::new(buffer);
        let mut reader = BinaryProtobufReader::new(cursor);

        // Before reading, version should be None
        assert!(reader.format_version().is_none());

        // Read an event (which triggers header read)
        let _ = reader.read_next_event().unwrap();

        // After reading, version should be set
        assert_eq!(reader.format_version(), Some(binary_format::VERSION));
    }

    #[test]
    fn test_read_next_event_invalid_header() {
        // Data with wrong magic bytes
        let invalid_data = vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09];
        let cursor = Cursor::new(invalid_data);
        let mut reader = BinaryProtobufReader::new(cursor);

        let result = reader.read_next_event();
        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("magic header not found"));
    }

    #[test]
    fn test_read_next_event_header_only() {
        // Just the header, no events
        let header = binary_format::build_header();
        let cursor = Cursor::new(header.to_vec());
        let mut reader = BinaryProtobufReader::new(cursor);

        // Should successfully read header but find no events
        let result = reader.read_next_event().unwrap();
        assert!(result.is_none());

        // Header should have been read
        assert!(reader.header_read);
        assert_eq!(reader.format_version(), Some(binary_format::VERSION));
    }

    #[test]
    fn test_read_next_event_header_already_read() {
        // Create valid binary data with multiple events
        let mut buffer = Vec::new();
        let mut consumer = BinaryProtobufConsumer::new(&mut buffer);
        consumer
            .convert_and_write(&Event::BlockStarted {
                meta_line: MetaLine {
                    type_name: "test".to_string(),
                    level: 1,
                    line_index: None,
                    inline_text: QuotedString::new(String::new(), false),
                    arguments: ParsedArguments {
                        args: vec![],
                        kwargs: vec![],
                    },
                    id: Id::empty(),
                    is_void: false,
                },
            })
            .unwrap();
        consumer.convert_and_write(&Event::BlockFinished).unwrap();

        let cursor = Cursor::new(buffer);
        let mut reader = BinaryProtobufReader::new(cursor);

        // Read first event (reads header)
        let event1 = reader.read_next_event().unwrap();
        assert!(event1.is_some());
        assert!(reader.header_read);

        // Read second event (header already read, should not try again)
        let event2 = reader.read_next_event().unwrap();
        assert!(event2.is_some());

        // Read third time (should be EOF)
        let event3 = reader.read_next_event().unwrap();
        assert!(event3.is_none());
    }

    #[test]
    fn test_read_varint_single_byte() {
        // Single byte varints (0-127)
        let cursor = Cursor::new(vec![0x00]); // 0
        let mut reader = BinaryProtobufReader::new(cursor);
        assert_eq!(reader.read_varint().unwrap(), 0);

        let cursor = Cursor::new(vec![0x01]); // 1
        let mut reader = BinaryProtobufReader::new(cursor);
        assert_eq!(reader.read_varint().unwrap(), 1);

        let cursor = Cursor::new(vec![0x7F]); // 127
        let mut reader = BinaryProtobufReader::new(cursor);
        assert_eq!(reader.read_varint().unwrap(), 127);
    }

    #[test]
    fn test_read_varint_multi_byte() {
        // Two byte varint: 128 = 0x80 0x01
        let cursor = Cursor::new(vec![0x80, 0x01]);
        let mut reader = BinaryProtobufReader::new(cursor);
        assert_eq!(reader.read_varint().unwrap(), 128);

        // Three byte varint: 16384 = 0x80 0x80 0x01
        let cursor = Cursor::new(vec![0x80, 0x80, 0x01]);
        let mut reader = BinaryProtobufReader::new(cursor);
        assert_eq!(reader.read_varint().unwrap(), 16384);
    }

    #[test]
    fn test_read_varint_eof() {
        // Empty stream should error
        let cursor = Cursor::new(Vec::<u8>::new());
        let mut reader = BinaryProtobufReader::new(cursor);
        assert!(reader.read_varint().is_err());

        // Incomplete varint (continuation bit set but no more data)
        let cursor = Cursor::new(vec![0x80]); // continuation bit set
        let mut reader = BinaryProtobufReader::new(cursor);
        assert!(reader.read_varint().is_err());
    }

    #[test]
    fn test_ensure_header_read_idempotent() {
        // Create valid binary with header
        let header = binary_format::build_header();
        let cursor = Cursor::new(header.to_vec());
        let mut reader = BinaryProtobufReader::new(cursor);

        // First call should read the header
        assert!(!reader.header_read);
        reader.ensure_header_read().unwrap();
        assert!(reader.header_read);
        assert_eq!(reader.format_version(), Some(binary_format::VERSION));

        // Second call should be a no-op (early return)
        reader.ensure_header_read().unwrap();
        assert!(reader.header_read);
        assert_eq!(reader.format_version(), Some(binary_format::VERSION));
    }

    #[test]
    fn test_read_next_event_truncated_message() {
        // Header + partial varint/message
        let mut data = binary_format::build_header().to_vec();
        data.push(0x10); // Length varint saying 16 bytes follow, but they don't

        let cursor = Cursor::new(data);
        let mut reader = BinaryProtobufReader::new(cursor);

        // Should return None (EOF during message read)
        let result = reader.read_next_event().unwrap();
        assert!(result.is_none());
    }

    #[test]
    #[allow(invalid_from_utf8)] // We're intentionally testing invalid UTF-8
    fn test_binary_format_header_is_invalid_utf8() {
        let header = binary_format::build_header();

        // The header should NOT be valid UTF-8 (that's the whole point)
        assert!(std::str::from_utf8(&header).is_err());

        // Specifically, the INVALID_UTF8_PREFIX should be invalid UTF-8
        let prefix = binary_format::INVALID_UTF8_PREFIX;
        assert!(std::str::from_utf8(&prefix).is_err());
    }
}
