//! Comprehensive test suite for LSP server functionality

use document::Document;
use tower_lsp::lsp_types::*;

use super::*;

// Test data fixtures
const SAMPLE_STORYBOOK: &str = r#"
species Human {
    intelligence: high
    lifespan: 80
}

character Alice: Human {
    age: 7
    ---backstory
    A curious girl who loves adventures
    ---
}

template Child {
    age: 5..12
    guardian: Human
}

character Bob: Human from Child {
    age: 10
    guardian: Alice
}

life_arc Growing {
    state child {
        on enter {
            age: 5
        }
    }
    state teen {
        on enter {
            age: 13
        }
    }
    state adult {
        on enter {
            age: 18
        }
    }
}

schedule DailyRoutine {
    08:00 -> 09:00: breakfast {
        activity: eating
    }
    09:00 -> 12:00: school {
        activity: learning
    }
}

relationship Friendship {
    Alice as friend {
        bond_strength: 5
    }
    Bob as friend {
        bond_strength: 5
    }
}
"#;

#[cfg(test)]
mod document_tests {
    use super::*;

    #[test]
    fn test_document_creation() {
        let doc = Document::new(SAMPLE_STORYBOOK.to_string());

        if !doc.parse_errors.is_empty() {
            eprintln!("Parse errors:");
            for err in &doc.parse_errors {
                eprintln!("  - {}", err.message);
            }
        }

        assert_eq!(doc.text, SAMPLE_STORYBOOK);
        assert!(doc.ast.is_some(), "AST should be parsed");
    }

    #[test]
    fn test_document_with_errors() {
        let invalid = "character { invalid syntax }";
        let doc = Document::new(invalid.to_string());

        assert!(doc.ast.is_none(), "Invalid syntax should not produce AST");
        assert!(!doc.parse_errors.is_empty(), "Should have parse errors");
    }

    #[test]
    fn test_document_update() {
        let mut doc = Document::new("character Alice {}".to_string());

        doc.update("character Bob {}".to_string());

        assert_eq!(doc.text, "character Bob {}");
        assert!(doc.name_table.resolve_name("Bob").is_some());
        assert!(doc.name_table.resolve_name("Alice").is_none());
    }

    #[test]
    fn test_symbol_extraction() {
        let doc = Document::new(SAMPLE_STORYBOOK.to_string());

        assert!(doc.name_table.resolve_name("Alice").is_some());
        assert!(doc.name_table.resolve_name("Bob").is_some());
        assert!(doc.name_table.resolve_name("Child").is_some());
        assert!(doc.name_table.resolve_name("Growing").is_some());
        assert!(doc.name_table.resolve_name("DailyRoutine").is_some());
        assert!(doc.name_table.resolve_name("Human").is_some());
        assert!(doc.name_table.resolve_name("Friendship").is_some());
    }

    #[test]
    fn test_symbol_kinds() {
        use crate::resolve::names::DeclKind;

        let doc = Document::new(SAMPLE_STORYBOOK.to_string());

        let alice = doc.name_table.resolve_name("Alice").unwrap();
        assert_eq!(alice.kind, DeclKind::Character);

        let child = doc.name_table.resolve_name("Child").unwrap();
        assert_eq!(child.kind, DeclKind::Template);

        let growing = doc.name_table.resolve_name("Growing").unwrap();
        assert_eq!(growing.kind, DeclKind::LifeArc);
    }

    #[test]
    fn test_word_at_offset() {
        let doc = Document::new("character Alice {}".to_string());

        // Test finding "character" keyword
        let word = doc.word_at_offset(5);
        assert_eq!(word, Some("character".to_string()));

        // Test finding "Alice" identifier
        let word = doc.word_at_offset(12);
        assert_eq!(word, Some("Alice".to_string()));

        // Test whitespace returns None
        let word = doc.word_at_offset(9);
        assert_eq!(word, None);
    }
}

#[cfg(test)]
mod position_tests {
    use crate::position::PositionTracker;

    #[test]
    fn test_position_tracking_single_line() {
        let mut tracker = PositionTracker::new("hello world");

        assert_eq!(tracker.offset_to_position(0), (0, 0));
        assert_eq!(tracker.offset_to_position(6), (0, 6));
        assert_eq!(tracker.offset_to_position(11), (0, 11));
    }

    #[test]
    fn test_position_tracking_multiline() {
        let mut tracker = PositionTracker::new("line 1\nline 2\nline 3");

        // Start of first line
        assert_eq!(tracker.offset_to_position(0), (0, 0));

        // Start of second line (after \n at offset 6)
        assert_eq!(tracker.offset_to_position(7), (1, 0));

        // Start of third line (after \n at offset 13)
        assert_eq!(tracker.offset_to_position(14), (2, 0));

        // Middle of second line
        assert_eq!(tracker.offset_to_position(10), (1, 3));
    }

    #[test]
    fn test_line_count() {
        let tracker = PositionTracker::new("line 1\nline 2\nline 3");
        assert_eq!(tracker.line_count(), 3);
    }

    #[test]
    fn test_line_offset() {
        let tracker = PositionTracker::new("line 1\nline 2\nline 3");

        assert_eq!(tracker.line_offset(0), Some(0));
        assert_eq!(tracker.line_offset(1), Some(7));
        assert_eq!(tracker.line_offset(2), Some(14));
        assert_eq!(tracker.line_offset(3), None);
    }
}

#[cfg(test)]
mod hover_tests {
    use super::*;

    #[test]
    fn test_hover_keywords() {
        // Test character keyword
        let hover = hover::get_hover_info("character Alice {}", 0, 5);
        assert!(hover.is_some());
        let hover = hover.unwrap();
        if let HoverContents::Markup(content) = hover.contents {
            assert!(content.value.contains("character"));
            assert!(content.value.contains("Defines a character entity"));
        }

        // Test template keyword
        let hover = hover::get_hover_info("template Child {}", 0, 2);
        assert!(hover.is_some());

        // Test life_arc keyword
        let hover = hover::get_hover_info("life_arc Growing {}", 0, 5);
        assert!(hover.is_some());
    }

    #[test]
    fn test_hover_non_keyword() {
        let hover = hover::get_hover_info("character Alice {}", 0, 12);
        assert!(hover.is_none());
    }

    #[test]
    fn test_hover_invalid_position() {
        let hover = hover::get_hover_info("character Alice {}", 0, 100);
        assert!(hover.is_none());
    }
}

#[cfg(test)]
mod completion_tests {
    use super::*;

    #[test]
    fn test_keyword_completions() {
        let doc = Document::new(SAMPLE_STORYBOOK.to_string());
        let params = CompletionParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier {
                    uri: Url::parse("file:///test.sb").unwrap(),
                },
                position: Position {
                    line: 0,
                    character: 0,
                },
            },
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
            context: None,
        };

        let result = completion::get_completions(&doc, &params);
        assert!(result.is_some());

        if let Some(CompletionResponse::Array(items)) = result {
            // Should have keyword completions
            assert!(items.iter().any(|item| item.label == "character"));
            assert!(items.iter().any(|item| item.label == "template"));
            assert!(items.iter().any(|item| item.label == "life_arc"));

            // Should have entity completions from document
            assert!(items.iter().any(|item| item.label == "Alice"));
            assert!(items.iter().any(|item| item.label == "Bob"));
        }
    }

    #[test]
    fn test_completion_includes_snippets() {
        let doc = Document::new("".to_string());
        let params = CompletionParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier {
                    uri: Url::parse("file:///test.sb").unwrap(),
                },
                position: Position::default(),
            },
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
            context: None,
        };

        let result = completion::get_completions(&doc, &params);

        if let Some(CompletionResponse::Array(items)) = result {
            // Check that character completion has a snippet
            let character_item = items.iter().find(|item| item.label == "character");
            assert!(character_item.is_some());
            let character_item = character_item.unwrap();
            assert!(character_item.insert_text.is_some());
            assert_eq!(
                character_item.insert_text_format,
                Some(InsertTextFormat::SNIPPET)
            );
        }
    }
}

#[cfg(test)]
mod formatting_tests {
    use super::*;

    #[test]
    fn test_basic_formatting() {
        let doc = Document::new("character Alice{age:7}".to_string());
        let options = FormattingOptions {
            tab_size: 4,
            insert_spaces: true,
            ..Default::default()
        };

        let result = formatting::format_document(&doc, &options);
        assert!(result.is_some());

        let edits = result.unwrap();
        assert_eq!(edits.len(), 1);

        let formatted = &edits[0].new_text;
        eprintln!("Formatted output:\n{}", formatted);
        assert!(formatted.contains("character Alice {") || formatted.contains("character Alice{"));
        assert!(formatted.contains("age: 7") || formatted.contains("age:7"));
    }

    #[test]
    fn test_formatting_indentation() {
        let doc = Document::new("character Alice {\nage: 7\n}".to_string());
        let options = FormattingOptions {
            tab_size: 4,
            insert_spaces: true,
            ..Default::default()
        };

        let result = formatting::format_document(&doc, &options);
        assert!(result.is_some());

        let formatted = &result.unwrap()[0].new_text;
        // Check that age is indented with 4 spaces
        assert!(formatted.contains("    age: 7"));
    }

    #[test]
    fn test_formatting_preserves_prose() {
        let doc = Document::new(
            "character Alice {\n---backstory\nSome  irregular   spacing\n---\n}".to_string(),
        );
        let options = FormattingOptions::default();

        let result = formatting::format_document(&doc, &options);
        let formatted = &result.unwrap()[0].new_text;

        // Prose content should be preserved exactly
        assert!(formatted.contains("Some  irregular   spacing"));
    }

    #[test]
    fn test_no_formatting_needed() {
        let already_formatted = "character Alice {\n    age: 7\n}\n";
        let doc = Document::new(already_formatted.to_string());
        let options = FormattingOptions::default();

        let result = formatting::format_document(&doc, &options);
        // Should return None if no changes needed
        assert!(result.is_none());
    }
}

#[cfg(test)]
mod symbols_tests {
    use super::*;

    #[test]
    fn test_extract_symbols_from_ast() {
        let doc = Document::new(SAMPLE_STORYBOOK.to_string());
        let ast = doc.ast.as_ref().unwrap();
        let mut positions = doc.positions.clone();

        let symbols = symbols::extract_symbols_from_ast(ast, &mut positions);

        // Should have top-level declarations
        assert!(symbols.iter().any(|s| s.name == "Alice"));
        assert!(symbols.iter().any(|s| s.name == "Child"));
        assert!(symbols.iter().any(|s| s.name == "Bob"));
        assert!(symbols.iter().any(|s| s.name == "Growing"));
        assert!(symbols.iter().any(|s| s.name == "DailyRoutine"));
        assert!(symbols.iter().any(|s| s.name == "Human"));
        assert!(symbols.iter().any(|s| s.name == "Friendship"));
    }

    #[test]
    fn test_symbol_hierarchy() {
        let doc = Document::new(SAMPLE_STORYBOOK.to_string());
        let ast = doc.ast.as_ref().unwrap();
        let mut positions = doc.positions.clone();

        let symbols = symbols::extract_symbols_from_ast(ast, &mut positions);

        // Alice should have children (fields)
        let alice = symbols.iter().find(|s| s.name == "Alice").unwrap();
        assert!(alice.children.is_some());
        let children = alice.children.as_ref().unwrap();
        assert!(children.iter().any(|c| c.name == "age"));
        assert!(children.iter().any(|c| c.name == "backstory"));
    }

    #[test]
    fn test_symbol_kinds() {
        let doc = Document::new(SAMPLE_STORYBOOK.to_string());
        let ast = doc.ast.as_ref().unwrap();
        let mut positions = doc.positions.clone();

        let symbols = symbols::extract_symbols_from_ast(ast, &mut positions);

        let alice = symbols.iter().find(|s| s.name == "Alice").unwrap();
        assert_eq!(alice.kind, SymbolKind::CLASS);

        let child = symbols.iter().find(|s| s.name == "Child").unwrap();
        assert_eq!(child.kind, SymbolKind::INTERFACE);
    }

    #[test]
    fn test_life_arc_states() {
        let doc = Document::new(SAMPLE_STORYBOOK.to_string());
        let ast = doc.ast.as_ref().unwrap();
        let mut positions = doc.positions.clone();

        let symbols = symbols::extract_symbols_from_ast(ast, &mut positions);

        let growing = symbols.iter().find(|s| s.name == "Growing").unwrap();
        assert!(growing.children.is_some());
        let states = growing.children.as_ref().unwrap();
        assert!(states.iter().any(|s| s.name == "child"));
        assert!(states.iter().any(|s| s.name == "teen"));
        assert!(states.iter().any(|s| s.name == "adult"));
    }
}

#[cfg(test)]
mod definition_tests {
    use super::*;

    #[test]
    fn test_goto_definition_character() {
        let mut doc = Document::new(SAMPLE_STORYBOOK.to_string());

        // Find position of "Alice" in "character Alice"
        let alice_offset = doc.text.find("character Alice").unwrap() + "character ".len();
        let (line, col) = doc.positions.offset_to_position(alice_offset);

        let params = GotoDefinitionParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier {
                    uri: Url::parse("file:///test.sb").unwrap(),
                },
                position: Position {
                    line: line as u32,
                    character: col as u32,
                },
            },
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
        };

        let uri = Url::parse("file:///test.sb").unwrap();
        let result = definition::get_definition(&doc, &params, &uri);

        assert!(result.is_some());
    }

    #[test]
    fn test_goto_definition_not_found() {
        let doc = Document::new("character Alice {}".to_string());

        let params = GotoDefinitionParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier {
                    uri: Url::parse("file:///test.sb").unwrap(),
                },
                position: Position {
                    line: 0,
                    character: 0, // On "character" keyword, not a symbol
                },
            },
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
        };

        let uri = Url::parse("file:///test.sb").unwrap();
        let result = definition::get_definition(&doc, &params, &uri);

        assert!(result.is_none());
    }
}

#[cfg(test)]
mod references_tests {
    use super::*;

    #[test]
    fn test_find_references() {
        let source = "character Alice {}\ncharacter Bob { friend: Alice }";
        let mut doc = Document::new(source.to_string());

        // Find position of first "Alice"
        let alice_offset = source.find("Alice").unwrap();
        let (line, col) = doc.positions.offset_to_position(alice_offset);

        let params = ReferenceParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier {
                    uri: Url::parse("file:///test.sb").unwrap(),
                },
                position: Position {
                    line: line as u32,
                    character: col as u32,
                },
            },
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
            context: ReferenceContext {
                include_declaration: true,
            },
        };

        let uri = Url::parse("file:///test.sb").unwrap();
        let result = references::find_references(&doc, &params, &uri);

        assert!(result.is_some());
        let locations = result.unwrap();
        // Should find both occurrences of "Alice"
        assert_eq!(locations.len(), 2);
    }

    #[test]
    fn test_find_references_word_boundaries() {
        let source = "character Alice {}\ncharacter Alicia {}";
        let mut doc = Document::new(source.to_string());

        let alice_offset = source.find("Alice").unwrap();
        let (line, col) = doc.positions.offset_to_position(alice_offset);

        let params = ReferenceParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier {
                    uri: Url::parse("file:///test.sb").unwrap(),
                },
                position: Position {
                    line: line as u32,
                    character: col as u32,
                },
            },
            work_done_progress_params: WorkDoneProgressParams::default(),
            partial_result_params: PartialResultParams::default(),
            context: ReferenceContext {
                include_declaration: true,
            },
        };

        let uri = Url::parse("file:///test.sb").unwrap();
        let result = references::find_references(&doc, &params, &uri);

        let locations = result.unwrap();
        // Should only find "Alice", not "Alicia"
        assert_eq!(locations.len(), 1);
    }
}

#[cfg(test)]
mod integration_tests {
    use super::*;

    #[test]
    fn test_full_workflow() {
        // Create a document
        let mut doc = Document::new(SAMPLE_STORYBOOK.to_string());

        // Verify parsing worked
        assert!(doc.ast.is_some());
        assert!(doc.parse_errors.is_empty());

        // Verify symbols were extracted
        assert!(doc.name_table.all_entries().count() > 0);

        // Test updating document
        doc.update("character NewChar {}".to_string());
        assert!(doc.name_table.resolve_name("NewChar").is_some());

        // Verify new AST was parsed
        assert!(doc.ast.is_some());
    }

    #[test]
    fn test_error_recovery() {
        let invalid = "character { invalid }";
        let doc = Document::new(invalid.to_string());

        // Should handle errors gracefully
        assert!(doc.ast.is_none());
        assert!(!doc.parse_errors.is_empty());

        // Symbols should be empty for invalid document
        assert_eq!(doc.name_table.all_entries().count(), 0);
    }
}
