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

/// Consumer that outputs events in the YAML format used in language_e2e.md tests
pub struct ToYamlTestConsumer {
    events: Vec<Event>,
}

impl ToYamlTestConsumer {
    /// Create a new ToYamlTestConsumer
    pub fn new() -> Self {
        ToYamlTestConsumer { events: Vec::new() }
    }

    /// Output the collected events as YAML to stdout
    pub fn output_yaml(&self) -> Result<(), Box<dyn std::error::Error>> {
        if self.events.is_empty() {
            println!("[]");
            return Ok(());
        }

        for (i, event) in self.events.iter().enumerate() {
            print!("- ");
            self.output_event_yaml(event)?;
            if i < self.events.len() - 1 {
                println!();
            }
        }

        io::stdout().flush()?;
        Ok(())
    }

    fn output_event_yaml(&self, event: &Event) -> Result<(), Box<dyn std::error::Error>> {
        match event {
            Event::BlockStarted { meta_line } => {
                println!("block-started:");
                println!("    type: {}", Self::yaml_string(&meta_line.type_name));
                println!("    level: {}", meta_line.level);

                // Add ID if present and not auto-generated (numeric)
                if !meta_line.id.value.is_empty() && !meta_line.id.is_numeric() {
                    println!("    id: {}", Self::yaml_string(&meta_line.id.value));
                }

                // Add inline text if present
                if !meta_line.inline_text.content.is_empty() {
                    println!(
                        "    inline-text: {}",
                        Self::yaml_string(&meta_line.inline_text.content)
                    );
                }

                // Add positional arguments if present
                if !meta_line.arguments.args.is_empty() {
                    print!("    args: [");
                    for (i, arg) in meta_line.arguments.args.iter().enumerate() {
                        if i > 0 {
                            print!(", ");
                        }
                        print!("\"{}\"", arg.content); // Always quote for consistency with expected format
                    }
                    println!("]");
                }

                // Add keyword arguments if present
                if !meta_line.arguments.kwargs.is_empty() {
                    println!("    kwargs:");
                    for (key, value) in &meta_line.arguments.kwargs {
                        println!(
                            "      {}: {}",
                            Self::yaml_string(&key.content),
                            Self::yaml_string(&value.content)
                        );
                    }
                }
            }

            Event::TextFinished {
                lines,
                links,
                footnotes,
            } => {
                println!("text-finished:");

                // Always output lines
                if lines.len() == 1 {
                    println!("    lines: [{}]", Self::yaml_string(&lines[0]));
                } else {
                    println!("    lines:");
                    for line in lines {
                        println!("      - {}", Self::yaml_string(line));
                    }
                }

                // Add links if present and non-empty
                if !links.is_empty() && links.iter().any(|line_links| !line_links.is_empty()) {
                    println!("    links:");
                    for (line_idx, line_links) in links.iter().enumerate() {
                        if !line_links.is_empty() {
                            println!("      - line: {line_idx}");
                            println!("        links:");
                            for link in line_links {
                                if let Some(target) = &link.target {
                                    println!("          - target: {}", Self::yaml_string(target));
                                }
                                if let Some(name) = &link.name {
                                    println!(
                                        "            name: {}",
                                        Self::yaml_string(&name.content)
                                    );
                                }
                                println!("            start: {}", link.start_index);
                                println!("            length: {}", link.length);
                            }
                        }
                    }
                }

                // Add footnotes if present
                if !footnotes.is_empty() {
                    println!("    footnotes:");
                    for footnote in footnotes {
                        println!("      - marker: {}", footnote.marker);
                        println!("        link: {}", Self::yaml_string(&footnote.link));
                    }
                }
            }

            Event::BlockFinished => {
                print!("block-finished");
            }

            Event::ContextLine { meta_line } => {
                println!("context-line:");
                println!("    type: {}", Self::yaml_string(&meta_line.type_name));
                println!("    level: {}", meta_line.level);

                // Add ID if present and not auto-generated (numeric)
                if !meta_line.id.value.is_empty() && !meta_line.id.is_numeric() {
                    println!("    id: {}", Self::yaml_string(&meta_line.id.value));
                }

                // Add inline text if present
                if !meta_line.inline_text.content.is_empty() {
                    println!(
                        "    inline-text: {}",
                        Self::yaml_string(&meta_line.inline_text.content)
                    );
                }
            }

            Event::VoidElement { meta_line } => {
                println!("void-element:");
                println!("    type: {}", Self::yaml_string(&meta_line.type_name));
                println!("    level: {}", meta_line.level);

                // Add ID if present and not auto-generated (numeric)
                if !meta_line.id.value.is_empty() && !meta_line.id.is_numeric() {
                    println!("    id: {}", Self::yaml_string(&meta_line.id.value));
                }

                // Add inline text if present
                if !meta_line.inline_text.content.is_empty() {
                    println!(
                        "    inline-text: {}",
                        Self::yaml_string(&meta_line.inline_text.content)
                    );
                }

                // Add positional arguments if present
                if !meta_line.arguments.args.is_empty() {
                    print!("    args: [");
                    for (i, arg) in meta_line.arguments.args.iter().enumerate() {
                        if i > 0 {
                            print!(", ");
                        }
                        print!("\"{}\"", arg.content);
                    }
                    println!("]");
                }

                // Add keyword arguments if present
                if !meta_line.arguments.kwargs.is_empty() {
                    println!("    kwargs:");
                    for (key, value) in &meta_line.arguments.kwargs {
                        println!(
                            "      {}: {}",
                            Self::yaml_string(&key.content),
                            Self::yaml_string(&value.content)
                        );
                    }
                }
            }
        }

        Ok(())
    }

    /// Format a string for YAML output, adding quotes when necessary
    fn yaml_string(s: &str) -> String {
        // Check if the string needs quotes
        if s.is_empty()
            || s.contains(' ')
            || s.contains('"')
            || s.contains('\\')
            || s.contains('\n')
            || s.contains('\t')
            || s.contains(':')
            || s.contains('#')
            || s.contains('-')
            || s.starts_with('@')
            || s.starts_with('!')
            || s.starts_with('|')
            || s.starts_with('>')
            || s.starts_with('%')
            || s.starts_with('&')
            || s.starts_with('*')
            || s.chars().all(|c| c.is_ascii_digit())
            || s == "true"
            || s == "false"
            || s == "null"
            || s == "~"
        {
            // Escape quotes and backslashes in the string
            let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
            format!("\"{escaped}\"")
        } else {
            s.to_string()
        }
    }
}

impl Default for ToYamlTestConsumer {
    fn default() -> Self {
        Self::new()
    }
}

impl Consumer for ToYamlTestConsumer {
    fn handle(&mut self, event: Event) {
        self.events.push(event);
    }

    fn on_finish(&mut self) {
        if let Err(e) = self.output_yaml() {
            eprintln!("Error outputting YAML: {e}");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::events::Event;
    use crate::parsers::wasot::arguments::ParsedArguments;
    use crate::parsers::wasot::footnote_parser::Footnote;
    use crate::parsers::wasot::parser::{Link, TargetType};
    use crate::parsers::wasot::{Id, MetaLine, QuotedString};

    // Helper function to create a test MetaLine
    fn create_test_meta_line(
        type_name: &str,
        level: usize,
        inline_text: &str,
        id: &str,
        args: Vec<&str>,
        kwargs: Vec<(&str, &str)>,
    ) -> MetaLine {
        let args: Vec<QuotedString> = args
            .into_iter()
            .map(|s| QuotedString::new(s.to_string(), false))
            .collect();
        let kwargs: Vec<(QuotedString, QuotedString)> = kwargs
            .into_iter()
            .map(|(k, v)| {
                (
                    QuotedString::new(k.to_string(), false),
                    QuotedString::new(v.to_string(), false),
                )
            })
            .collect();

        MetaLine {
            type_name: type_name.to_string(),
            level,
            inline_text: QuotedString::new(inline_text.to_string(), false),
            id: if id.is_empty() {
                Id::empty()
            } else {
                Id::new(id.to_string())
            },
            line_index: Some(0),
            is_void: false,
            arguments: ParsedArguments { args, kwargs },
        }
    }

    // Helper function to create a test Link
    fn create_test_link(
        name: Option<&str>,
        target: Option<&str>,
        target_type: TargetType,
        start_index: usize,
        length: usize,
    ) -> Link {
        Link {
            name: name.map(|n| QuotedString::new(n.to_string(), false)),
            target: target.map(|t| t.to_string()),
            target_type,
            arguments: ParsedArguments {
                args: Vec::new(),
                kwargs: Vec::new(),
            },
            start_index,
            length,
        }
    }

    // Helper function to create a test Footnote
    fn create_test_footnote(marker: u32, link: &str) -> Footnote {
        Footnote {
            marker,
            link: link.to_string(),
        }
    }

    // Tests for ToYamlTestConsumer::new

    #[test]
    fn test_new() {
        let consumer = ToYamlTestConsumer::new();
        assert!(consumer.events.is_empty());
    }

    #[test]
    fn test_default() {
        let consumer = ToYamlTestConsumer::default();
        assert!(consumer.events.is_empty());
    }

    // Tests for output_yaml

    #[test]
    fn test_output_yaml_empty() {
        let consumer = ToYamlTestConsumer::new();
        // This will print "[]" to stdout, but we can't easily capture it in this test
        let result = consumer.output_yaml();
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_yaml_single_event() {
        let mut consumer = ToYamlTestConsumer::new();
        let meta_line = create_test_meta_line("test", 1, "", "", vec![], vec![]);
        let event = Event::BlockStarted { meta_line };
        consumer.handle(event);

        let result = consumer.output_yaml();
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_yaml_multiple_events() {
        let mut consumer = ToYamlTestConsumer::new();

        // Add BlockStarted event
        let meta_line = create_test_meta_line("test", 1, "", "", vec![], vec![]);
        consumer.handle(Event::BlockStarted { meta_line });

        // Add BlockFinished event
        consumer.handle(Event::BlockFinished);

        let result = consumer.output_yaml();
        assert!(result.is_ok());
        assert_eq!(consumer.events.len(), 2);
    }

    // Tests for output_event_yaml - BlockStarted

    #[test]
    fn test_output_event_yaml_block_started_basic() {
        let consumer = ToYamlTestConsumer::new();
        let meta_line = create_test_meta_line("paragraph", 1, "", "", vec![], vec![]);
        let event = Event::BlockStarted { meta_line };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_block_started_with_id() {
        let consumer = ToYamlTestConsumer::new();
        let meta_line = create_test_meta_line("section", 2, "", "test-id", vec![], vec![]);
        let event = Event::BlockStarted { meta_line };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_block_started_with_numeric_id() {
        let consumer = ToYamlTestConsumer::new();
        // Numeric IDs should be excluded from output
        let mut meta_line = create_test_meta_line("div", 1, "", "123", vec![], vec![]);
        // Make the ID numeric
        meta_line.id = Id::new("123".to_string());
        let event = Event::BlockStarted { meta_line };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_block_started_with_inline_text() {
        let consumer = ToYamlTestConsumer::new();
        let meta_line = create_test_meta_line("heading", 1, "Title Text", "", vec![], vec![]);
        let event = Event::BlockStarted { meta_line };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_block_started_with_args() {
        let consumer = ToYamlTestConsumer::new();
        let meta_line = create_test_meta_line(
            "div",
            1,
            "",
            "",
            vec!["arg1", "arg with space", "arg3"],
            vec![],
        );
        let event = Event::BlockStarted { meta_line };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_block_started_with_kwargs() {
        let consumer = ToYamlTestConsumer::new();
        let meta_line = create_test_meta_line(
            "article",
            1,
            "",
            "",
            vec![],
            vec![("class", "highlight"), ("theme", "dark")],
        );
        let event = Event::BlockStarted { meta_line };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_block_started_all_features() {
        let consumer = ToYamlTestConsumer::new();
        let meta_line = create_test_meta_line(
            "complex",
            2,
            "Inline content",
            "complex-id",
            vec!["pos1", "pos2"],
            vec![("key1", "value1"), ("key2", "value with spaces")],
        );
        let event = Event::BlockStarted { meta_line };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    // Tests for output_event_yaml - TextFinished

    #[test]
    fn test_output_event_yaml_text_finished_single_line() {
        let consumer = ToYamlTestConsumer::new();
        let event = Event::TextFinished {
            lines: vec!["Single line of text".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_text_finished_multiple_lines() {
        let consumer = ToYamlTestConsumer::new();
        let event = Event::TextFinished {
            lines: vec![
                "First line".to_string(),
                "Second line".to_string(),
                "Third line".to_string(),
            ],
            links: vec![vec![], vec![], vec![]],
            footnotes: vec![],
        };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_text_finished_with_links() {
        let consumer = ToYamlTestConsumer::new();
        let link1 = create_test_link(
            Some("Example"),
            Some("https://example.com"),
            TargetType::Uri,
            5,
            10,
        );
        let link2 = create_test_link(Some("Test"), Some("test-id"), TargetType::Id, 20, 8);

        let event = Event::TextFinished {
            lines: vec!["Text with links".to_string(), "More text".to_string()],
            links: vec![vec![link1], vec![link2]],
            footnotes: vec![],
        };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_text_finished_with_links_no_name() {
        let consumer = ToYamlTestConsumer::new();
        let link = create_test_link(None, Some("https://example.com"), TargetType::Uri, 0, 15);

        let event = Event::TextFinished {
            lines: vec!["Text with unnamed link".to_string()],
            links: vec![vec![link]],
            footnotes: vec![],
        };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_text_finished_with_footnotes() {
        let consumer = ToYamlTestConsumer::new();
        let footnote1 = create_test_footnote(1, "First footnote link");
        let footnote2 = create_test_footnote(2, "Second footnote link");

        let event = Event::TextFinished {
            lines: vec!["Text with footnotes".to_string()],
            links: vec![vec![]],
            footnotes: vec![footnote1, footnote2],
        };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_text_finished_empty_links() {
        let consumer = ToYamlTestConsumer::new();
        let event = Event::TextFinished {
            lines: vec!["Plain text".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    #[test]
    fn test_output_event_yaml_text_finished_mixed_links() {
        let consumer = ToYamlTestConsumer::new();
        let link = create_test_link(Some("Link"), Some("target"), TargetType::Uri, 10, 5);

        let event = Event::TextFinished {
            lines: vec![
                "Line without links".to_string(),
                "Line with link".to_string(),
                "Another line without links".to_string(),
            ],
            links: vec![vec![], vec![link], vec![]],
            footnotes: vec![],
        };

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    // Tests for output_event_yaml - BlockFinished

    #[test]
    fn test_output_event_yaml_block_finished() {
        let consumer = ToYamlTestConsumer::new();
        let event = Event::BlockFinished;

        let result = consumer.output_event_yaml(&event);
        assert!(result.is_ok());
    }

    // Tests for yaml_string formatting

    #[test]
    fn test_yaml_string_simple() {
        assert_eq!(ToYamlTestConsumer::yaml_string("simple"), "simple");
        assert_eq!(ToYamlTestConsumer::yaml_string("word"), "word");
        assert_eq!(ToYamlTestConsumer::yaml_string("CamelCase"), "CamelCase");
    }

    #[test]
    fn test_yaml_string_needs_quoting_spaces() {
        assert_eq!(
            ToYamlTestConsumer::yaml_string("with space"),
            "\"with space\""
        );
        assert_eq!(
            ToYamlTestConsumer::yaml_string("multiple words here"),
            "\"multiple words here\""
        );
    }

    #[test]
    fn test_yaml_string_needs_quoting_empty() {
        assert_eq!(ToYamlTestConsumer::yaml_string(""), "\"\"");
    }

    #[test]
    fn test_yaml_string_needs_quoting_special_chars() {
        assert_eq!(
            ToYamlTestConsumer::yaml_string("with\"quote"),
            "\"with\\\"quote\""
        );
        assert_eq!(
            ToYamlTestConsumer::yaml_string("with\\backslash"),
            "\"with\\\\backslash\""
        );
        assert_eq!(
            ToYamlTestConsumer::yaml_string("with\nnewline"),
            "\"with\nnewline\""
        );
        assert_eq!(
            ToYamlTestConsumer::yaml_string("with\ttab"),
            "\"with\ttab\""
        );
        assert_eq!(
            ToYamlTestConsumer::yaml_string("with:colon"),
            "\"with:colon\""
        );
        assert_eq!(
            ToYamlTestConsumer::yaml_string("with#hash"),
            "\"with#hash\""
        );
        assert_eq!(
            ToYamlTestConsumer::yaml_string("with-dash"),
            "\"with-dash\""
        );
    }

    #[test]
    fn test_yaml_string_needs_quoting_special_prefixes() {
        assert_eq!(ToYamlTestConsumer::yaml_string("@mention"), "\"@mention\"");
        assert_eq!(
            ToYamlTestConsumer::yaml_string("!important"),
            "\"!important\""
        );
        assert_eq!(ToYamlTestConsumer::yaml_string("|pipe"), "\"|pipe\"");
        assert_eq!(ToYamlTestConsumer::yaml_string(">greater"), "\">greater\"");
        assert_eq!(ToYamlTestConsumer::yaml_string("%percent"), "\"%percent\"");
        assert_eq!(
            ToYamlTestConsumer::yaml_string("&ampersand"),
            "\"&ampersand\""
        );
        assert_eq!(
            ToYamlTestConsumer::yaml_string("*asterisk"),
            "\"*asterisk\""
        );
    }

    #[test]
    fn test_yaml_string_needs_quoting_special_values() {
        assert_eq!(ToYamlTestConsumer::yaml_string("true"), "\"true\"");
        assert_eq!(ToYamlTestConsumer::yaml_string("false"), "\"false\"");
        assert_eq!(ToYamlTestConsumer::yaml_string("null"), "\"null\"");
        assert_eq!(ToYamlTestConsumer::yaml_string("~"), "\"~\"");
    }

    #[test]
    fn test_yaml_string_needs_quoting_numbers() {
        assert_eq!(ToYamlTestConsumer::yaml_string("123"), "\"123\"");
        assert_eq!(ToYamlTestConsumer::yaml_string("0"), "\"0\"");
        assert_eq!(ToYamlTestConsumer::yaml_string("999"), "\"999\"");
    }

    #[test]
    fn test_yaml_string_no_quoting_needed() {
        assert_eq!(ToYamlTestConsumer::yaml_string("word123"), "word123");
        assert_eq!(ToYamlTestConsumer::yaml_string("text123abc"), "text123abc");
        assert_eq!(ToYamlTestConsumer::yaml_string("Test"), "Test");
    }

    #[test]
    fn test_yaml_string_escape_sequences() {
        // Test both backslash and quote escaping
        assert_eq!(
            ToYamlTestConsumer::yaml_string("test\\\"both"),
            "\"test\\\\\\\"both\""
        );
        // Test multiple quotes
        assert_eq!(
            ToYamlTestConsumer::yaml_string("\"quoted\""),
            "\"\\\"quoted\\\"\""
        );
        // Test multiple backslashes
        assert_eq!(
            ToYamlTestConsumer::yaml_string("path\\\\file"),
            "\"path\\\\\\\\file\""
        );
    }

    // Tests for Consumer trait implementation

    #[test]
    fn test_consumer_handle() {
        let mut consumer = ToYamlTestConsumer::new();
        assert_eq!(consumer.events.len(), 0);

        let meta_line = create_test_meta_line("test", 1, "", "", vec![], vec![]);
        let event = Event::BlockStarted { meta_line };
        consumer.handle(event);

        assert_eq!(consumer.events.len(), 1);
    }

    #[test]
    fn test_consumer_handle_multiple_events() {
        let mut consumer = ToYamlTestConsumer::new();

        // Add multiple different events
        let meta_line = create_test_meta_line("block", 1, "", "", vec![], vec![]);
        consumer.handle(Event::BlockStarted { meta_line });

        consumer.handle(Event::TextFinished {
            lines: vec!["text".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        });

        consumer.handle(Event::BlockFinished);

        assert_eq!(consumer.events.len(), 3);
    }

    #[test]
    fn test_consumer_on_finish() {
        let mut consumer = ToYamlTestConsumer::new();
        let meta_line = create_test_meta_line("test", 1, "", "", vec![], vec![]);
        consumer.handle(Event::BlockStarted { meta_line });

        // This will print to stdout but should not panic
        consumer.on_finish();
    }

    #[test]
    fn test_consumer_on_finish_empty() {
        let mut consumer = ToYamlTestConsumer::new();
        // Should handle empty events gracefully
        consumer.on_finish();
    }

    // Integration tests

    #[test]
    fn test_full_workflow() {
        let mut consumer = ToYamlTestConsumer::new();

        // Simulate a complete document
        let meta_line = create_test_meta_line(
            "article",
            1,
            "Article Title",
            "art1",
            vec!["arg1"],
            vec![("class", "main")],
        );
        consumer.handle(Event::BlockStarted { meta_line });

        consumer.handle(Event::TextFinished {
            lines: vec!["Article content goes here.".to_string()],
            links: vec![vec![]],
            footnotes: vec![],
        });

        consumer.handle(Event::BlockFinished);

        assert_eq!(consumer.events.len(), 3);

        // Test output
        let result = consumer.output_yaml();
        assert!(result.is_ok());
    }

    #[test]
    fn test_complex_document_structure() {
        let mut consumer = ToYamlTestConsumer::new();

        // Nested document structure
        let outer_meta = create_test_meta_line("section", 1, "", "sec1", vec![], vec![]);
        consumer.handle(Event::BlockStarted {
            meta_line: outer_meta,
        });

        let inner_meta = create_test_meta_line("paragraph", 2, "Inline text", "", vec![], vec![]);
        consumer.handle(Event::BlockStarted {
            meta_line: inner_meta,
        });

        let link = create_test_link(
            Some("Example"),
            Some("https://example.com"),
            TargetType::Uri,
            10,
            7,
        );
        consumer.handle(Event::TextFinished {
            lines: vec!["Text with link and footnote.".to_string()],
            links: vec![vec![link]],
            footnotes: vec![create_test_footnote(1, "footnote link")],
        });

        consumer.handle(Event::BlockFinished); // Close paragraph
        consumer.handle(Event::BlockFinished); // Close section

        assert_eq!(consumer.events.len(), 5);

        let result = consumer.output_yaml();
        assert!(result.is_ok());
    }
}
