use crate::events::events::{Consumer, Event};
use crate::parsers::wasot::parser::TargetType;
use crate::parsers::wasot::{Link, QuotedString};

/// A consumer that prints all links found in Wasot documents
pub struct LinksPrinter {
    current_block_stack: Vec<String>,
    link_count: usize,
}

impl LinksPrinter {
    pub fn new() -> Self {
        Self {
            current_block_stack: Vec::new(),
            link_count: 0,
        }
    }

    fn print_link(&mut self, link: &Link, context: &str, location: &str) {
        self.link_count += 1;

        println!("\n🔗 Link #{}", self.link_count);
        println!("  📍 Location: {location}");
        println!("  📝 Context: {context}");

        if let Some(name) = &link.name {
            println!("  📛 Name: {}", name.content);
            // Check if the name itself has nested links
            if !name.links.is_empty() {
                println!("     (Name contains {} nested link(s))", name.links.len());
            }
        }

        if let Some(target) = &link.target {
            let target_type_icon = match link.target_type {
                TargetType::Uri => "🌐",
                TargetType::Id => "🔖",
                TargetType::FootNote => "📝",
                TargetType::IdInDocument(_) => "📄",
            };
            println!(
                "  {} Target: {} ({:?})",
                target_type_icon, target, link.target_type
            );
        }

        if !link.arguments.args.is_empty() || !link.arguments.kwargs.is_empty() {
            println!("  ⚙️  Arguments:");
            for (i, arg) in link.arguments.args.iter().enumerate() {
                println!("     [{}] {}", i, arg.content);
                if !arg.links.is_empty() {
                    println!("         (Contains {} nested link(s))", arg.links.len());
                }
            }
            for (key, value) in &link.arguments.kwargs {
                println!("     {} = {}", key.content, value.content);
                if !value.links.is_empty() {
                    println!("         (Contains {} nested link(s))", value.links.len());
                }
            }
        }

        println!(
            "  📏 Position: chars {} to {}",
            link.start_index,
            link.start_index + link.length
        );
    }

    fn process_quoted_string_links(&mut self, qs: &QuotedString, context: &str, location: &str) {
        for link in &qs.links {
            self.print_link(link, context, location);

            // Recursively check links in the link's arguments
            for arg in &link.arguments.args {
                self.process_quoted_string_links(
                    arg,
                    &format!("Link argument in {context}"),
                    location,
                );
            }
            for (key, value) in &link.arguments.kwargs {
                self.process_quoted_string_links(
                    value,
                    &format!("Link argument '{}' in {}", key.content, context),
                    location,
                );
            }

            // Check links in the link's name if present
            if let Some(name) = &link.name {
                self.process_quoted_string_links(
                    name,
                    &format!("Link name in {context}"),
                    location,
                );
            }
        }
    }

    fn get_current_path(&self) -> String {
        if self.current_block_stack.is_empty() {
            "Document root".to_string()
        } else {
            self.current_block_stack.join(" > ")
        }
    }
}

impl Consumer for LinksPrinter {
    fn on_start(&mut self) {
        println!("=== 🔍 Wasot Link Report ===");
        println!("Searching for all links in the document...\n");
    }

    fn handle(&mut self, event: Event) {
        match event {
            Event::BlockStarted { meta_line } => {
                let block_name = if meta_line.type_name.is_empty() {
                    format!("#{}", "#".repeat(meta_line.level - 1))
                } else {
                    meta_line.type_name.clone()
                };

                self.current_block_stack.push(block_name.clone());

                // Check links in inline text
                if !meta_line.inline_text.content.is_empty()
                    && !meta_line.inline_text.links.is_empty()
                {
                    let location = self.get_current_path();
                    self.process_quoted_string_links(
                        &meta_line.inline_text,
                        "Block inline text",
                        &location,
                    );
                }

                // Check links in arguments
                for (i, arg) in meta_line.arguments.args.iter().enumerate() {
                    if !arg.links.is_empty() {
                        let location = self.get_current_path();
                        self.process_quoted_string_links(
                            arg,
                            &format!("Block positional argument [{i}]"),
                            &location,
                        );
                    }
                }

                for (key, value) in &meta_line.arguments.kwargs {
                    if !value.links.is_empty() {
                        let location = self.get_current_path();
                        self.process_quoted_string_links(
                            value,
                            &format!("Block named argument '{}'", key.content),
                            &location,
                        );
                    }
                }
            }

            Event::BlockFinished => {
                self.current_block_stack.pop();
            }

            Event::TextFinished {
                lines,
                links,
                footnotes: _,
            } => {
                let location = self.get_current_path();

                for (line_idx, line_links) in links.iter().enumerate() {
                    if !line_links.is_empty() {
                        let line_content = &lines[line_idx];
                        for link in line_links {
                            let context = format!(
                                "Text line {}: \"{}\"",
                                line_idx + 1,
                                if line_content.len() > 50 {
                                    format!("{}...", &line_content[..50])
                                } else {
                                    line_content.to_string()
                                }
                            );
                            self.print_link(link, &context, &location);
                        }
                    }
                }
            }

            Event::ContextLine { meta_line } => {
                // Check links in context line inline text
                if !meta_line.inline_text.content.is_empty()
                    && !meta_line.inline_text.links.is_empty()
                {
                    let location = self.get_current_path();
                    self.process_quoted_string_links(
                        &meta_line.inline_text,
                        "Context line inline text",
                        &location,
                    );
                }

                // Check links in context line arguments
                for (i, arg) in meta_line.arguments.args.iter().enumerate() {
                    if !arg.links.is_empty() {
                        let location = self.get_current_path();
                        self.process_quoted_string_links(
                            arg,
                            &format!("Context line positional argument [{i}]"),
                            &location,
                        );
                    }
                }

                for (key, value) in &meta_line.arguments.kwargs {
                    if !value.links.is_empty() {
                        let location = self.get_current_path();
                        self.process_quoted_string_links(
                            value,
                            &format!("Context line named argument '{}'", key.content),
                            &location,
                        );
                    }
                }
            }

            Event::VoidElement { meta_line } => {
                // Check links in void element inline text
                if !meta_line.inline_text.content.is_empty()
                    && !meta_line.inline_text.links.is_empty()
                {
                    let location = self.get_current_path();
                    self.process_quoted_string_links(
                        &meta_line.inline_text,
                        "Void element inline text",
                        &location,
                    );
                }

                // Check links in void element arguments
                for (i, arg) in meta_line.arguments.args.iter().enumerate() {
                    if !arg.links.is_empty() {
                        let location = self.get_current_path();
                        self.process_quoted_string_links(
                            arg,
                            &format!("Void element positional argument [{i}]"),
                            &location,
                        );
                    }
                }

                for (key, value) in &meta_line.arguments.kwargs {
                    if !value.links.is_empty() {
                        let location = self.get_current_path();
                        self.process_quoted_string_links(
                            value,
                            &format!("Void element named argument '{}'", key.content),
                            &location,
                        );
                    }
                }
            }
        }
    }

    fn on_finish(&mut self) {
        println!("\n=== 📊 Summary ===");
        println!("Total links found: {}", self.link_count);

        if !self.current_block_stack.is_empty() {
            println!("\n⚠️  Warning: Unclosed blocks detected!");
            println!("  {:?}", self.current_block_stack);
        }
    }
}

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

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

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

    // Helper function to create a QuotedString with links
    fn create_quoted_string_with_links(content: String, links: Vec<Link>) -> QuotedString {
        QuotedString {
            content,
            quoted: false,
            links,
        }
    }

    // Helper function to create a MetaLine for testing
    fn create_test_meta_line(
        type_name: String,
        level: usize,
        inline_text: QuotedString,
        args: Vec<QuotedString>,
        kwargs: Vec<(QuotedString, QuotedString)>,
    ) -> MetaLine {
        MetaLine {
            type_name,
            level,
            inline_text,
            id: Id::empty(),
            line_index: Some(0),
            is_void: false,
            arguments: ParsedArguments { args, kwargs },
        }
    }

    #[test]
    fn test_new() {
        let printer = LinksPrinter::new();
        assert!(printer.current_block_stack.is_empty());
        assert_eq!(printer.link_count, 0);
    }

    #[test]
    fn test_default() {
        let printer = LinksPrinter::default();
        assert!(printer.current_block_stack.is_empty());
        assert_eq!(printer.link_count, 0);
    }

    #[test]
    fn test_print_link_simple() {
        let mut printer = LinksPrinter::new();
        let link = create_test_link(
            Some("Example".to_string()),
            Some("https://example.com".to_string()),
            TargetType::Uri,
            10,
            20,
        );

        printer.print_link(&link, "test context", "test location");
        assert_eq!(printer.link_count, 1);
    }

    #[test]
    fn test_print_link_with_nested_links_in_name() {
        let mut printer = LinksPrinter::new();
        let nested_link =
            create_test_link(None, Some("nested.com".to_string()), TargetType::Uri, 5, 10);

        let name_with_links = QuotedString {
            content: "Name with nested link".to_string(),
            quoted: false,
            links: vec![nested_link],
        };

        let link = Link {
            name: Some(name_with_links),
            target: Some("main.com".to_string()),
            target_type: TargetType::Uri,
            arguments: ParsedArguments {
                args: Vec::new(),
                kwargs: Vec::new(),
            },
            start_index: 0,
            length: 25,
        };

        printer.print_link(&link, "test context", "test location");
        assert_eq!(printer.link_count, 1);
    }

    #[test]
    fn test_print_link_with_arguments() {
        let mut printer = LinksPrinter::new();
        let arg1 = QuotedString::new("arg1".to_string(), false);
        let arg2 = create_quoted_string_with_links(
            "arg2".to_string(),
            vec![create_test_link(
                None,
                Some("arg.com".to_string()),
                TargetType::Uri,
                0,
                5,
            )],
        );

        let key = QuotedString::new("key".to_string(), false);
        let value = create_quoted_string_with_links(
            "value".to_string(),
            vec![create_test_link(
                None,
                Some("value.com".to_string()),
                TargetType::Uri,
                0,
                8,
            )],
        );

        let link = Link {
            name: Some(QuotedString::new("Test".to_string(), false)),
            target: Some("test.com".to_string()),
            target_type: TargetType::Uri,
            arguments: ParsedArguments {
                args: vec![arg1, arg2],
                kwargs: vec![(key, value)],
            },
            start_index: 0,
            length: 15,
        };

        printer.print_link(&link, "test context", "test location");
        assert_eq!(printer.link_count, 1);
    }

    #[test]
    fn test_print_link_different_target_types() {
        let mut printer = LinksPrinter::new();

        // Test URI target
        let uri_link = create_test_link(
            Some("URI Link".to_string()),
            Some("https://example.com".to_string()),
            TargetType::Uri,
            0,
            10,
        );
        printer.print_link(&uri_link, "uri context", "uri location");

        // Test ID target
        let id_link = create_test_link(
            Some("ID Link".to_string()),
            Some("element-id".to_string()),
            TargetType::Id,
            20,
            15,
        );
        printer.print_link(&id_link, "id context", "id location");

        // Test FootNote target
        let footnote_link = create_test_link(
            Some("Footnote Link".to_string()),
            Some("footnote-ref".to_string()),
            TargetType::FootNote,
            40,
            12,
        );
        printer.print_link(&footnote_link, "footnote context", "footnote location");

        assert_eq!(printer.link_count, 3);
    }

    #[test]
    fn test_process_quoted_string_links() {
        let mut printer = LinksPrinter::new();
        let link1 = create_test_link(
            Some("Link1".to_string()),
            Some("example1.com".to_string()),
            TargetType::Uri,
            0,
            10,
        );
        let link2 = create_test_link(
            Some("Link2".to_string()),
            Some("example2.com".to_string()),
            TargetType::Uri,
            15,
            10,
        );

        let quoted_string =
            create_quoted_string_with_links("Text with links".to_string(), vec![link1, link2]);

        printer.process_quoted_string_links(&quoted_string, "test context", "test location");
        assert_eq!(printer.link_count, 2);
    }

    #[test]
    fn test_process_quoted_string_links_recursive() {
        let mut printer = LinksPrinter::new();

        // Create a nested structure: link with arguments that contain links
        let nested_link =
            create_test_link(None, Some("nested.com".to_string()), TargetType::Uri, 0, 5);
        let arg_with_link = create_quoted_string_with_links("arg".to_string(), vec![nested_link]);

        let main_link = Link {
            name: Some(QuotedString::new("Main".to_string(), false)),
            target: Some("main.com".to_string()),
            target_type: TargetType::Uri,
            arguments: ParsedArguments {
                args: vec![arg_with_link],
                kwargs: Vec::new(),
            },
            start_index: 0,
            length: 20,
        };

        let quoted_string =
            create_quoted_string_with_links("Text with nested links".to_string(), vec![main_link]);

        printer.process_quoted_string_links(&quoted_string, "test context", "test location");
        assert_eq!(printer.link_count, 2); // Main link + nested link in argument
    }

    #[test]
    fn test_get_current_path_empty() {
        let printer = LinksPrinter::new();
        assert_eq!(printer.get_current_path(), "Document root");
    }

    #[test]
    fn test_get_current_path_with_blocks() {
        let mut printer = LinksPrinter::new();
        printer.current_block_stack.push("block1".to_string());
        printer.current_block_stack.push("block2".to_string());
        printer.current_block_stack.push("block3".to_string());

        assert_eq!(printer.get_current_path(), "block1 > block2 > block3");
    }

    #[test]
    fn test_consumer_on_start() {
        let mut printer = LinksPrinter::new();
        printer.on_start();
        // This test mainly ensures on_start doesn't panic
        // The output goes to stdout, so we can't easily test the exact output
    }

    #[test]
    fn test_handle_block_started_simple() {
        let mut printer = LinksPrinter::new();
        let meta_line = create_test_meta_line(
            "paragraph".to_string(),
            1,
            QuotedString::new("".to_string(), false),
            Vec::new(),
            Vec::new(),
        );
        let event = Event::BlockStarted { meta_line };

        printer.handle(event);
        assert_eq!(printer.current_block_stack.len(), 1);
        assert_eq!(printer.current_block_stack[0], "paragraph");
    }

    #[test]
    fn test_handle_block_started_with_empty_type_name() {
        let mut printer = LinksPrinter::new();
        let meta_line = create_test_meta_line(
            "".to_string(),
            2,
            QuotedString::new("".to_string(), false),
            Vec::new(),
            Vec::new(),
        );
        let event = Event::BlockStarted { meta_line };

        printer.handle(event);
        assert_eq!(printer.current_block_stack.len(), 1);
        assert_eq!(printer.current_block_stack[0], "##"); // Level 2 = ##
    }

    #[test]
    fn test_handle_block_started_with_inline_text_links() {
        let mut printer = LinksPrinter::new();
        let link = create_test_link(
            Some("Inline Link".to_string()),
            Some("inline.com".to_string()),
            TargetType::Uri,
            0,
            15,
        );
        let inline_text =
            create_quoted_string_with_links("Text with inline link".to_string(), vec![link]);
        let meta_line = create_test_meta_line(
            "paragraph".to_string(),
            1,
            inline_text,
            Vec::new(),
            Vec::new(),
        );
        let event = Event::BlockStarted { meta_line };

        printer.handle(event);
        assert_eq!(printer.current_block_stack.len(), 1);
        assert_eq!(printer.link_count, 1);
    }

    #[test]
    fn test_handle_block_started_with_argument_links() {
        let mut printer = LinksPrinter::new();
        let link = create_test_link(
            Some("Arg Link".to_string()),
            Some("arg.com".to_string()),
            TargetType::Uri,
            0,
            10,
        );
        let arg_with_link = create_quoted_string_with_links("argument".to_string(), vec![link]);
        let meta_line = create_test_meta_line(
            "paragraph".to_string(),
            1,
            QuotedString::new("".to_string(), false),
            vec![arg_with_link],
            Vec::new(),
        );
        let event = Event::BlockStarted { meta_line };

        printer.handle(event);
        assert_eq!(printer.current_block_stack.len(), 1);
        assert_eq!(printer.link_count, 1);
    }

    #[test]
    fn test_handle_block_started_with_kwarg_links() {
        let mut printer = LinksPrinter::new();
        let link = create_test_link(
            Some("Kwarg Link".to_string()),
            Some("kwarg.com".to_string()),
            TargetType::Uri,
            0,
            12,
        );
        let key = QuotedString::new("param".to_string(), false);
        let value_with_link = create_quoted_string_with_links("value".to_string(), vec![link]);
        let meta_line = create_test_meta_line(
            "paragraph".to_string(),
            1,
            QuotedString::new("".to_string(), false),
            Vec::new(),
            vec![(key, value_with_link)],
        );
        let event = Event::BlockStarted { meta_line };

        printer.handle(event);
        assert_eq!(printer.current_block_stack.len(), 1);
        assert_eq!(printer.link_count, 1);
    }

    #[test]
    fn test_handle_block_finished() {
        let mut printer = LinksPrinter::new();
        printer.current_block_stack.push("test_block".to_string());

        let event = Event::BlockFinished;
        printer.handle(event);

        assert!(printer.current_block_stack.is_empty());
    }

    #[test]
    fn test_handle_text_finished_with_links() {
        let mut printer = LinksPrinter::new();
        printer.current_block_stack.push("paragraph".to_string());

        let link1 = create_test_link(
            Some("Text Link 1".to_string()),
            Some("text1.com".to_string()),
            TargetType::Uri,
            5,
            10,
        );
        let link2 = create_test_link(
            Some("Text Link 2".to_string()),
            Some("text2.com".to_string()),
            TargetType::Uri,
            20,
            8,
        );

        let lines = vec![
            "This is a line with links".to_string(),
            "Another line with more links".to_string(),
            "Plain line without links".to_string(),
        ];
        let links = vec![
            vec![link1], // First line has one link
            vec![link2], // Second line has one link
            vec![],      // Third line has no links
        ];
        let footnotes = Vec::new();

        let event = Event::TextFinished {
            lines,
            links,
            footnotes,
        };

        printer.handle(event);
        assert_eq!(printer.link_count, 2);
    }

    #[test]
    fn test_handle_text_finished_with_long_line() {
        let mut printer = LinksPrinter::new();
        printer.current_block_stack.push("paragraph".to_string());

        let link = create_test_link(
            Some("Long Line Link".to_string()),
            Some("long.com".to_string()),
            TargetType::Uri,
            60,
            10,
        );

        let long_line = "A".repeat(100); // Line longer than 50 characters
        let lines = vec![long_line];
        let links = vec![vec![link]];
        let footnotes = Vec::new();

        let event = Event::TextFinished {
            lines,
            links,
            footnotes,
        };

        printer.handle(event);
        assert_eq!(printer.link_count, 1);
    }

    #[test]
    fn test_handle_text_finished_no_links() {
        let mut printer = LinksPrinter::new();
        printer.current_block_stack.push("paragraph".to_string());

        let lines = vec![
            "Plain text line 1".to_string(),
            "Plain text line 2".to_string(),
        ];
        let links = vec![vec![], vec![]]; // No links on any line
        let footnotes = Vec::new();

        let event = Event::TextFinished {
            lines,
            links,
            footnotes,
        };

        printer.handle(event);
        assert_eq!(printer.link_count, 0);
    }

    #[test]
    fn test_on_finish_no_unclosed_blocks() {
        let mut printer = LinksPrinter::new();
        printer.link_count = 5;

        printer.on_finish();
        // Test mainly ensures on_finish doesn't panic with empty stack
    }

    #[test]
    fn test_on_finish_with_unclosed_blocks() {
        let mut printer = LinksPrinter::new();
        printer.link_count = 3;
        printer.current_block_stack.push("unclosed1".to_string());
        printer.current_block_stack.push("unclosed2".to_string());

        printer.on_finish();
        // Test mainly ensures on_finish doesn't panic with unclosed blocks
        // The warning output goes to stdout
    }

    #[test]
    fn test_full_workflow() {
        let mut printer = LinksPrinter::new();

        // Start processing
        printer.on_start();

        // Block started
        let link_in_inline = create_test_link(
            Some("Inline".to_string()),
            Some("inline.com".to_string()),
            TargetType::Uri,
            0,
            10,
        );
        let inline_text = create_quoted_string_with_links(
            "Inline text with link".to_string(),
            vec![link_in_inline],
        );
        let meta_line = create_test_meta_line(
            "section".to_string(),
            1,
            inline_text,
            Vec::new(),
            Vec::new(),
        );
        printer.handle(Event::BlockStarted { meta_line });

        // Text with links
        let text_link = create_test_link(
            Some("Text".to_string()),
            Some("text.com".to_string()),
            TargetType::Uri,
            5,
            8,
        );
        let lines = vec!["Some text with a link".to_string()];
        let links = vec![vec![text_link]];
        printer.handle(Event::TextFinished {
            lines,
            links,
            footnotes: Vec::new(),
        });

        // Block finished
        printer.handle(Event::BlockFinished);

        // Finish processing
        printer.on_finish();

        assert_eq!(printer.link_count, 2); // One from inline, one from text
        assert!(printer.current_block_stack.is_empty());
    }

    #[test]
    fn test_nested_blocks() {
        let mut printer = LinksPrinter::new();

        // Start outer block
        let outer_meta = create_test_meta_line(
            "outer".to_string(),
            1,
            QuotedString::new("".to_string(), false),
            Vec::new(),
            Vec::new(),
        );
        printer.handle(Event::BlockStarted {
            meta_line: outer_meta,
        });

        // Start inner block
        let inner_meta = create_test_meta_line(
            "inner".to_string(),
            2,
            QuotedString::new("".to_string(), false),
            Vec::new(),
            Vec::new(),
        );
        printer.handle(Event::BlockStarted {
            meta_line: inner_meta,
        });

        assert_eq!(printer.current_block_stack.len(), 2);
        assert_eq!(printer.get_current_path(), "outer > inner");

        // Finish inner block
        printer.handle(Event::BlockFinished);
        assert_eq!(printer.current_block_stack.len(), 1);
        assert_eq!(printer.get_current_path(), "outer");

        // Finish outer block
        printer.handle(Event::BlockFinished);
        assert!(printer.current_block_stack.is_empty());
        assert_eq!(printer.get_current_path(), "Document root");
    }
}
