use crate::consumers::binary_protobuf_consumer::{proto::WasotEvent, BinaryProtobufReader};
use crate::events::events::Emitter;
use crate::events::{BusReaderIterator, Event};
use crate::parsers::wasot::{Id, MetaLine, ParsedArguments, QuotedString};
use bus::Bus;
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use std::sync::{Arc, Mutex};

/// Emitter that reads binary protobuf files containing length-delimited WasotEvent messages
pub struct ProtobufBinaryEmitter {
    /// Path to the binary protobuf file to read
    file_path: Option<String>,
    /// Optional reader for stdin or other sources
    reader: Option<Box<dyn Read + Send + Sync>>,
    /// Bus for events
    bus: Arc<Mutex<Bus<Event>>>,
}

impl ProtobufBinaryEmitter {
    /// Create a new binary protobuf emitter from a file path
    pub fn from_file<P: AsRef<Path>>(file_path: P) -> Self {
        let bus = Arc::new(Mutex::new(Bus::new(1000)));
        Self {
            file_path: Some(file_path.as_ref().to_string_lossy().into_owned()),
            reader: None,
            bus,
        }
    }

    /// Create a new binary protobuf emitter from a reader (e.g., stdin)
    pub fn from_reader<R: Read + Send + Sync + 'static>(reader: R) -> Self {
        let bus = Arc::new(Mutex::new(Bus::new(1000)));
        Self {
            file_path: None,
            reader: Some(Box::new(reader)),
            bus,
        }
    }

    /// Create a new binary protobuf emitter from stdin
    pub fn from_stdin() -> Self {
        Self::from_reader(io::stdin())
    }

    /// Create a new binary protobuf emitter from bytes
    pub fn from_bytes(bytes: Vec<u8>) -> Self {
        Self::from_reader(io::Cursor::new(bytes))
    }

    /// Convert a protobuf WasotEvent to an ElementEvent
    fn convert_proto_event(&self, proto_event: WasotEvent) -> Option<Event> {
        use crate::consumers::binary_protobuf_consumer::proto::wasot_event;

        match proto_event.event? {
            wasot_event::Event::BlockStarted(block_started) => {
                let proto_meta = block_started.meta_line?;
                let meta_line = self.convert_proto_meta_line(proto_meta);
                if meta_line.is_void {
                    Some(Event::VoidElement { meta_line })
                } else {
                    Some(Event::BlockStarted { meta_line })
                }
            }
            wasot_event::Event::BlockFinished(_) => Some(Event::BlockFinished),
            wasot_event::Event::TextFinished(text_event) => {
                // Convert protobuf footnotes back to Rust footnotes
                let footnotes = text_event
                    .footnotes
                    .into_iter()
                    .map(|proto_footnote| crate::parsers::wasot::Footnote {
                        marker: proto_footnote.marker,
                        link: proto_footnote.link,
                    })
                    .collect();

                Some(Event::TextFinished {
                    lines: text_event.lines.clone(),
                    links: vec![Vec::new(); text_event.lines.len()], // TODO: Convert from protobuf links
                    footnotes,
                })
            }
            wasot_event::Event::ContextLine(context_line_event) => {
                let proto_meta = context_line_event.meta_line?;
                Some(Event::ContextLine {
                    meta_line: self.convert_proto_meta_line(proto_meta),
                })
            }
        }
    }

    /// Convert a protobuf MetaLine to a Wasot MetaLine
    fn convert_proto_meta_line(
        &self,
        proto_meta: crate::consumers::binary_protobuf_consumer::proto::MetaLine,
    ) -> MetaLine {
        debug_assert!(
            proto_meta.level <= 10,
            "Proto meta level should be reasonable"
        );
        if let Some(line_idx) = proto_meta.line_index {
            debug_assert!(
                line_idx < 1_000_000,
                "Proto line index should be reasonable"
            );
        }

        let converted = MetaLine {
            type_name: proto_meta.type_name,
            level: proto_meta.level as usize,
            line_index: proto_meta.line_index.map(|idx| idx as usize),
            inline_text: proto_meta
                .inline_text
                .map(|qt| QuotedString::new(qt.content, qt.quoted))
                .unwrap_or_else(|| QuotedString::new(String::new(), false)),
            id: proto_meta.block_id.map(Id::new).unwrap_or_else(Id::empty),
            arguments: proto_meta
                .arguments
                .map(|args| self.convert_proto_arguments(args))
                .unwrap_or_else(|| ParsedArguments {
                    args: vec![],
                    kwargs: vec![],
                }),
            is_void: proto_meta.is_void,
        };

        debug_assert!(
            converted.level <= 10,
            "Converted meta line level should be reasonable"
        );
        converted
    }

    /// Convert protobuf Arguments to ParsedArguments
    fn convert_proto_arguments(
        &self,
        proto_args: crate::consumers::binary_protobuf_consumer::proto::Arguments,
    ) -> ParsedArguments {
        ParsedArguments {
            args: proto_args
                .args
                .into_iter()
                .map(|qt| QuotedString::new(qt.content, qt.quoted))
                .collect(),
            kwargs: proto_args
                .kwargs
                .into_iter()
                .filter_map(|kvp| {
                    let key = kvp.key?;
                    let value = kvp.value?;
                    Some((
                        QuotedString::new(key.content, key.quoted),
                        QuotedString::new(value.content, value.quoted),
                    ))
                })
                .collect(),
        }
    }
}

impl Emitter for ProtobufBinaryEmitter {
    type Reader = BusReaderIterator;

    fn subscribe(&self) -> Self::Reader {
        let bus_reader = self.bus.lock().unwrap().add_rx();
        BusReaderIterator::new(bus_reader)
    }

    fn run(mut self) {
        debug_assert!(
            self.file_path.is_some() || self.reader.is_some(),
            "Either file path or reader should be provided"
        );

        // Get the reader
        let reader: Box<dyn Read + Send + Sync> = match (&self.file_path, &mut self.reader) {
            (Some(path), _) => {
                debug_assert!(!path.is_empty(), "File path should not be empty");
                match File::open(path) {
                    Ok(file) => Box::new(file),
                    Err(e) => {
                        eprintln!("Error opening file: {e}");
                        return;
                    }
                }
            }
            (None, Some(reader)) => {
                // We need to take ownership of the reader to avoid double-borrow
                let mut buffer = Vec::new();
                match reader.read_to_end(&mut buffer) {
                    Ok(bytes_read) => {
                        debug_assert!(bytes_read > 0, "Should read some bytes from reader");
                        Box::new(io::Cursor::new(buffer))
                    }
                    Err(e) => {
                        eprintln!("Error reading from reader: {e}");
                        return;
                    }
                }
            }
            (None, None) => {
                eprintln!("No file path or reader provided");
                return;
            }
        };

        // Create a binary protobuf reader
        let mut binary_reader = BinaryProtobufReader::new(reader);
        let mut event_count = 0;

        // Read and emit all events
        loop {
            let proto_event = match binary_reader.read_next_event() {
                Ok(Some(event)) => event,
                Ok(None) => break, // End of stream
                Err(e) => {
                    eprintln!("Error reading protobuf event: {e}");
                    break;
                }
            };

            debug_assert!(
                proto_event.event.is_some(),
                "Proto event should have event field"
            );
            if let Some(element_event) = self.convert_proto_event(proto_event) {
                debug_assert!(element_event.validate(), "Converted event should be valid");
                if let Ok(mut bus) = self.bus.lock() {
                    bus.broadcast(element_event);
                }
                event_count += 1;
                debug_assert!(event_count < 1_000_000, "Event count should be reasonable");
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::io::Cursor;
    use tempfile::tempdir;

    #[test]
    fn test_from_file_creates_bus() {
        let dir = tempdir().unwrap();
        let file_path = dir.path().join("test.bin");
        fs::write(&file_path, b"test data").unwrap();

        let emitter = ProtobufBinaryEmitter::from_file(&file_path);
        assert!(emitter.file_path.is_some());
        assert_eq!(emitter.file_path.unwrap(), file_path.to_string_lossy());
        assert!(emitter.reader.is_none());
    }

    #[test]
    fn test_from_reader_creates_bus() {
        let data = b"test data";
        let reader = Cursor::new(data);
        let emitter = ProtobufBinaryEmitter::from_reader(reader);

        assert!(emitter.file_path.is_none());
        assert!(emitter.reader.is_some());
    }

    #[test]
    fn test_from_stdin() {
        let emitter = ProtobufBinaryEmitter::from_stdin();
        assert!(emitter.file_path.is_none());
        assert!(emitter.reader.is_some());
    }

    #[test]
    fn test_subscribe() {
        let emitter = ProtobufBinaryEmitter::from_reader(Cursor::new(b""));
        let _reader = emitter.subscribe();
        // Test that subscribe returns a BusReaderIterator without panicking
    }

    #[test]
    fn test_run_with_empty_file() {
        let dir = tempdir().unwrap();
        let file_path = dir.path().join("empty.bin");
        fs::write(&file_path, b"").unwrap();

        let emitter = ProtobufBinaryEmitter::from_file(&file_path);
        // This will complete without errors on an empty file
        emitter.run();
    }

    #[test]
    fn test_run_with_empty_reader() {
        let emitter = ProtobufBinaryEmitter::from_reader(Cursor::new(b"dummy"));
        // This will complete without errors on non-empty but invalid data
        emitter.run();
    }

    #[test]
    fn test_run_with_invalid_file_path() {
        let emitter = ProtobufBinaryEmitter::from_file("/nonexistent/path/file.bin");
        // Should handle file open error gracefully without panicking
        emitter.run();
    }

    #[test]
    fn test_convert_proto_event_none_returns_none() {
        let emitter = ProtobufBinaryEmitter::from_reader(Cursor::new(b""));

        // Mock proto event with missing event field
        let proto_event =
            crate::consumers::binary_protobuf_consumer::proto::WasotEvent { event: None };
        let converted = emitter.convert_proto_event(proto_event);
        assert!(converted.is_none());
    }

    #[test]
    fn test_convert_proto_meta_line_with_level_zero() {
        let emitter = ProtobufBinaryEmitter::from_reader(Cursor::new(b""));

        let proto_meta = crate::consumers::binary_protobuf_consumer::proto::MetaLine {
            type_name: "div".to_string(),
            level: 0, // Edge case: level 0
            line_index: None,
            inline_text: None,
            block_id: None,
            arguments: None,
            is_void: false,
        };

        let converted = emitter.convert_proto_meta_line(proto_meta);
        assert_eq!(converted.type_name, "div");
        assert_eq!(converted.level, 0);
        assert_eq!(converted.line_index, None);
    }

    #[test]
    fn test_convert_proto_arguments_empty() {
        let emitter = ProtobufBinaryEmitter::from_reader(Cursor::new(b""));

        let proto_args = crate::consumers::binary_protobuf_consumer::proto::Arguments {
            args: vec![],
            kwargs: vec![],
            inline_text: None,
        };

        let converted = emitter.convert_proto_arguments(proto_args);
        assert!(converted.args.is_empty());
        assert!(converted.kwargs.is_empty());
    }

    #[test]
    fn test_convert_proto_arguments_with_single_arg() {
        let emitter = ProtobufBinaryEmitter::from_reader(Cursor::new(b""));

        let proto_args = crate::consumers::binary_protobuf_consumer::proto::Arguments {
            args: vec![
                crate::consumers::binary_protobuf_consumer::proto::QuotedText {
                    content: "test_arg".to_string(),
                    quoted: true,
                    links: vec![],
                },
            ],
            kwargs: vec![],
            inline_text: None,
        };

        let converted = emitter.convert_proto_arguments(proto_args);
        assert_eq!(converted.args.len(), 1);
        assert_eq!(converted.args[0].content, "test_arg");
        assert!(converted.args[0].quoted);
        assert!(converted.kwargs.is_empty());
    }
}
