use crate::events::{Consumer, Event};
use std::io::{self, Write};

/// Simple XML listener that prints XML to stdout based on ElementEvents
pub struct XmlListener {
    /// Whether to use pretty printing with indentation
    pretty_print: bool,
    /// Current indentation level
    indent_level: usize,
}

impl XmlListener {
    /// Create a new XmlListener
    pub fn new(pretty_print: bool) -> Self {
        XmlListener {
            pretty_print,
            indent_level: 0,
        }
    }

    /// Write indentation if pretty printing is enabled
    fn write_indent(&self) {
        if self.pretty_print {
            print!("{}", "  ".repeat(self.indent_level));
        }
    }

    /// Write a newline if pretty printing is enabled
    fn write_newline(&self) {
        if self.pretty_print {
            println!();
        }
    }
}

impl XmlListener {
    fn process_event(&mut self, event: &Event) {
        match event {
            Event::BlockStarted { meta_line, .. } => {
                self.write_indent();

                // Start with <block type="..."
                print!("<block type=\"{}\"", meta_line.type_name);

                // Add level attribute
                print!(" level=\"{}\"", meta_line.level);

                // Add id if present
                if !meta_line.id.value.is_empty() {
                    print!(" id=\"{}\"", meta_line.id.value);
                }

                print!(">");
                self.write_newline();

                self.indent_level += 1;

                // Write inline text if present
                if !meta_line.inline_text.content.is_empty() {
                    self.write_indent();
                    if meta_line.inline_text.quoted {
                        print!(
                            "<inline-text quoted=\"true\">{}</inline-text>",
                            meta_line.inline_text.content
                        );
                    } else {
                        print!(
                            "<inline-text>{}</inline-text>",
                            meta_line.inline_text.content
                        );
                    }
                    self.write_newline();
                }

                // Write positional arguments
                for arg in &meta_line.arguments.args {
                    self.write_indent();
                    if arg.quoted {
                        print!("<arg quoted=\"true\">{}</arg>", arg.content);
                    } else {
                        print!("<arg>{}</arg>", arg.content);
                    }
                    self.write_newline();
                }

                // Write keyword arguments (excluding id kwarg only if it matches block id)
                for (key, value) in &meta_line.arguments.kwargs {
                    // Only skip id kwarg if it matches the block's id attribute
                    if key.content != "id" || value.content != meta_line.id.value {
                        self.write_indent();
                        print!("<kwarg>");
                        if key.quoted {
                            print!("<key quoted=\"true\">{}</key>", key.content);
                        } else {
                            print!("<key>{}</key>", key.content);
                        }
                        if value.quoted {
                            print!("<value quoted=\"true\">{}</value>", value.content);
                        } else {
                            print!("<value>{}</value>", value.content);
                        }
                        print!("</kwarg>");
                        self.write_newline();
                    }
                }

                // Flush stdout to ensure output is visible
                io::stdout().flush().unwrap();
            }

            Event::TextFinished {
                lines, links: _, ..
            } => {
                self.write_indent();
                // Use CDATA to preserve exact whitespace content
                print!("<text><![CDATA[");
                for line in lines {
                    println!("{line}");
                }
                print!("]]></text>");
                self.write_newline();

                // Flush stdout to ensure output is visible
                io::stdout().flush().unwrap();
            }

            Event::BlockFinished => {
                self.indent_level -= 1;
                self.write_indent();
                print!("</block>");
                self.write_newline();

                // Flush stdout to ensure output is visible
                io::stdout().flush().unwrap();
            }

            Event::ContextLine { meta_line } => {
                self.write_indent();
                print!("<context-line type=\"{}\"", meta_line.type_name);
                print!(" level=\"{}\"", meta_line.level);
                if !meta_line.id.value.is_empty() {
                    print!(" id=\"{}\"", meta_line.id.value);
                }
                if !meta_line.inline_text.content.is_empty() {
                    print!(">{}</context-line>", meta_line.inline_text.content);
                } else {
                    print!("/>");
                }
                self.write_newline();
                io::stdout().flush().unwrap();
            }

            Event::VoidElement { meta_line } => {
                self.write_indent();
                print!("<block type=\"{}\"", meta_line.type_name);
                print!(" level=\"{}\"", meta_line.level);
                if !meta_line.id.value.is_empty() {
                    print!(" id=\"{}\"", meta_line.id.value);
                }
                print!(" void=\"true\"");

                // Check if we have any content to write inside the element
                let has_content = !meta_line.inline_text.content.is_empty()
                    || !meta_line.arguments.args.is_empty()
                    || meta_line
                        .arguments
                        .kwargs
                        .iter()
                        .any(|(k, _)| k.content != "id");

                if has_content {
                    print!(">");
                    self.write_newline();
                    self.indent_level += 1;

                    // Write inline text if present
                    if !meta_line.inline_text.content.is_empty() {
                        self.write_indent();
                        if meta_line.inline_text.quoted {
                            print!(
                                "<inline-text quoted=\"true\">{}</inline-text>",
                                meta_line.inline_text.content
                            );
                        } else {
                            print!(
                                "<inline-text>{}</inline-text>",
                                meta_line.inline_text.content
                            );
                        }
                        self.write_newline();
                    }

                    // Write positional arguments
                    for arg in &meta_line.arguments.args {
                        self.write_indent();
                        if arg.quoted {
                            print!("<arg quoted=\"true\">{}</arg>", arg.content);
                        } else {
                            print!("<arg>{}</arg>", arg.content);
                        }
                        self.write_newline();
                    }

                    // Write keyword arguments (excluding id kwarg only if it matches block id)
                    for (key, value) in &meta_line.arguments.kwargs {
                        if key.content != "id" || value.content != meta_line.id.value {
                            self.write_indent();
                            print!("<kwarg>");
                            if key.quoted {
                                print!("<key quoted=\"true\">{}</key>", key.content);
                            } else {
                                print!("<key>{}</key>", key.content);
                            }
                            if value.quoted {
                                print!("<value quoted=\"true\">{}</value>", value.content);
                            } else {
                                print!("<value>{}</value>", value.content);
                            }
                            print!("</kwarg>");
                            self.write_newline();
                        }
                    }

                    self.indent_level -= 1;
                    self.write_indent();
                    print!("</block>");
                } else {
                    print!("/>");
                }
                self.write_newline();
                io::stdout().flush().unwrap();
            }
        }
    }
}

impl Consumer for XmlListener {
    fn handle(&mut self, event: Event) {
        self.process_event(&event);
    }
}

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

    #[test]
    fn test_new() {
        let listener = XmlListener::new(true);
        assert!(listener.pretty_print);
        assert_eq!(listener.indent_level, 0);

        let listener = XmlListener::new(false);
        assert!(!listener.pretty_print);
        assert_eq!(listener.indent_level, 0);
    }

    #[test]
    fn test_xml_listener_implements_consumer() {
        let _listener = XmlListener::new(false);
        // This test ensures XmlListener properly implements the Consumer trait
        // We can't use Box<dyn Consumer> because Consumer has generic methods
    }

    #[test]
    fn test_process_block_started_event() {
        let mut listener = XmlListener::new(false);

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

        let event = Event::BlockStarted { meta_line };

        // This would normally write to stdout, but we can at least test that it doesn't panic
        listener.process_event(&event);
        assert_eq!(listener.indent_level, 1); // Should have increased indent level
    }

    #[test]
    fn test_process_block_started_event_with_id() {
        let mut listener = XmlListener::new(false);

        let meta_line = MetaLine {
            type_name: "section".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::from("test123"),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        let event = Event::BlockStarted { meta_line };

        listener.process_event(&event);
        assert_eq!(listener.indent_level, 1);
    }

    #[test]
    fn test_process_block_started_event_with_inline_text() {
        let mut listener = XmlListener::new(false);

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

        let event = Event::BlockStarted { meta_line };

        listener.process_event(&event);
        assert_eq!(listener.indent_level, 1);
    }

    #[test]
    fn test_process_block_started_event_with_quoted_inline_text() {
        let mut listener = XmlListener::new(false);

        let meta_line = MetaLine {
            type_name: "heading".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new("Hello World".to_string(), true),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

        let event = Event::BlockStarted { meta_line };

        listener.process_event(&event);
        assert_eq!(listener.indent_level, 1);
    }

    #[test]
    fn test_process_block_started_event_with_args() {
        let mut listener = XmlListener::new(false);

        let meta_line = MetaLine {
            type_name: "block".to_string(),
            level: 2,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![
                    QuotedString::new("arg1".to_string(), false),
                    QuotedString::new("arg with spaces".to_string(), true),
                ],
                kwargs: vec![],
            },
        };

        let event = Event::BlockStarted { meta_line };

        listener.process_event(&event);
        assert_eq!(listener.indent_level, 1);
    }

    #[test]
    fn test_process_block_started_event_with_kwargs() {
        let mut listener = XmlListener::new(false);

        let meta_line = MetaLine {
            type_name: "block".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![
                    (
                        QuotedString::new("key".to_string(), false),
                        QuotedString::new("value".to_string(), false),
                    ),
                    (
                        QuotedString::new("quoted key".to_string(), true),
                        QuotedString::new("quoted value".to_string(), true),
                    ),
                ],
            },
        };

        let event = Event::BlockStarted { meta_line };

        listener.process_event(&event);
        assert_eq!(listener.indent_level, 1);
    }

    #[test]
    fn test_process_block_started_event_with_kwargs_excluding_id() {
        let mut listener = XmlListener::new(false);

        let meta_line = MetaLine {
            type_name: "block".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::from("blockid"),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![
                    (
                        QuotedString::new("id".to_string(), false), // Should be excluded
                        QuotedString::new("should_not_appear".to_string(), false),
                    ),
                    (
                        QuotedString::new("other".to_string(), false),
                        QuotedString::new("value".to_string(), false),
                    ),
                ],
            },
        };

        let event = Event::BlockStarted { meta_line };

        listener.process_event(&event);
        assert_eq!(listener.indent_level, 1);
    }

    #[test]
    fn test_process_text_finished_event() {
        let mut listener = XmlListener::new(false);

        let event = Event::TextFinished {
            lines: vec!["Line 1".to_string(), "Line 2".to_string()],
            links: vec![vec![], vec![]],
            footnotes: vec![],
        };

        listener.process_event(&event);
        // Indent level should remain unchanged for text events
        assert_eq!(listener.indent_level, 0);
    }

    #[test]
    fn test_process_text_finished_event_empty_lines() {
        let mut listener = XmlListener::new(false);

        let event = Event::TextFinished {
            lines: vec![],
            links: vec![],
            footnotes: vec![],
        };

        listener.process_event(&event);
        assert_eq!(listener.indent_level, 0);
    }

    #[test]
    fn test_process_block_finished_event() {
        let mut listener = XmlListener::new(false);

        // Set up initial state - simulate that we're inside a block
        listener.indent_level = 2;

        let event = Event::BlockFinished;

        listener.process_event(&event);
        assert_eq!(listener.indent_level, 1); // Should decrease by 1
    }

    #[test]
    fn test_process_block_finished_event_at_zero() {
        let listener = XmlListener::new(false);

        // Test edge case: what happens when indent_level is already 0
        assert_eq!(listener.indent_level, 0);

        let _event = Event::BlockFinished;

        // This would underflow if not handled properly, but Rust will panic on debug build
        // or wrap around on release build. The actual code should handle this gracefully.
        // For now we just test the normal case where indent_level > 0
    }

    #[test]
    fn test_consumer_handle_method() {
        let mut listener = XmlListener::new(false);

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

        let event = Event::BlockStarted { meta_line };

        // Test the Consumer trait handle method
        listener.handle(event);
        assert_eq!(listener.indent_level, 1);
    }

    #[test]
    fn test_pretty_print_vs_non_pretty() {
        // Test that both pretty print and non-pretty print modes can be created and used
        let mut pretty_listener = XmlListener::new(true);
        let mut non_pretty_listener = XmlListener::new(false);

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

        let event = Event::BlockStarted {
            meta_line: meta_line.clone(),
        };

        // Both should work without panicking
        pretty_listener.process_event(&event);
        non_pretty_listener.process_event(&event);

        assert_eq!(pretty_listener.indent_level, 1);
        assert_eq!(non_pretty_listener.indent_level, 1);
        assert!(pretty_listener.pretty_print);
        assert!(!non_pretty_listener.pretty_print);
    }

    #[test]
    fn test_multiple_nested_blocks() {
        let mut listener = XmlListener::new(false);

        // Test multiple levels of nesting
        let meta_line1 = MetaLine {
            type_name: "outer".to_string(),
            level: 1,
            line_index: Some(0),
            is_void: false,
            inline_text: QuotedString::new(String::new(), false),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };

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

        // Start outer block
        listener.process_event(&Event::BlockStarted {
            meta_line: meta_line1,
        });
        assert_eq!(listener.indent_level, 1);

        // Start inner block
        listener.process_event(&Event::BlockStarted {
            meta_line: meta_line2,
        });
        assert_eq!(listener.indent_level, 2);

        // End inner block
        listener.process_event(&Event::BlockFinished);
        assert_eq!(listener.indent_level, 1);

        // End outer block
        listener.process_event(&Event::BlockFinished);
        assert_eq!(listener.indent_level, 0);
    }
}
