//! Tests for context-aware completion

#[cfg(test)]
mod tests {
    use tower_lsp::lsp_types::{
        CompletionParams,
        Position,
        TextDocumentIdentifier,
        TextDocumentPositionParams,
        Url,
    };

    use crate::lsp::{
        completion,
        document::Document,
    };

    fn make_params(line: u32, character: u32) -> CompletionParams {
        CompletionParams {
            text_document_position: TextDocumentPositionParams {
                text_document: TextDocumentIdentifier {
                    uri: Url::parse("file:///test.sb").unwrap(),
                },
                position: Position { line, character },
            },
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
            context: None,
        }
    }

    #[test]
    fn test_top_level_completions() {
        let doc = Document::new("".to_string());
        let params = make_params(0, 0);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Should have top-level keywords
                    assert!(items.iter().any(|item| item.label == "character"));
                    assert!(items.iter().any(|item| item.label == "template"));
                    assert!(items.iter().any(|item| item.label == "behavior"));
                    // Should NOT have behavior-only keywords
                    assert!(
                        !items.iter().any(|item| item.label == "?"),
                        "? should not appear at top level"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "choose"),
                        "choose should not appear at top level"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "then"),
                        "then should not appear at top level"
                    );
                    // Should NOT have field-only keywords
                    assert!(
                        !items.iter().any(|item| item.label == "age"),
                        "age should not appear at top level"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "bond"),
                        "bond should not appear at top level"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_field_block_completions() {
        let source = "character Alice {\n    \n}";
        let doc = Document::new(source.to_string());
        // Position inside the character block (line 1, after spaces)
        let params = make_params(1, 4);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Should have field-related keywords
                    assert!(items.iter().any(|item| item.label == "from"));
                    assert!(items.iter().any(|item| item.label == "age"));
                    assert!(items.iter().any(|item| item.label == "bond"));
                    // Should NOT have top-level declaration keywords
                    assert!(
                        !items.iter().any(|item| item.label == "character"),
                        "character should not appear inside character block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "behavior"),
                        "behavior should not appear inside character block"
                    );
                    // Should NOT have behavior-only keywords
                    assert!(
                        !items.iter().any(|item| item.label == "?"),
                        "? should not appear inside character block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "choose"),
                        "choose should not appear inside character block"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_completions_include_templates() {
        // Test that templates show up in completions
        let source = "template Child { age: number }\n";
        let doc = Document::new(source.to_string());
        let params = make_params(1, 0);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Should include Child template in completions
                    assert!(
                        items.iter().any(|item| item.label == "Child"),
                        "Should have Child template in completions"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_behavior_completions() {
        let source = "behavior Test {\n    \n}";
        let doc = Document::new(source.to_string());
        // Position inside behavior block
        let params = make_params(1, 4);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Should have symbolic behavior tree keywords
                    assert!(items.iter().any(|item| item.label == "?"));
                    assert!(items.iter().any(|item| item.label == ">"));
                    assert!(items.iter().any(|item| item.label == "*"));
                    // Should have word-based behavior tree keywords
                    assert!(items.iter().any(|item| item.label == "choose"));
                    assert!(items.iter().any(|item| item.label == "then"));
                    assert!(items.iter().any(|item| item.label == "repeat"));
                    assert!(items.iter().any(|item| item.label == "if"));
                    assert!(items.iter().any(|item| item.label == "when"));
                    // Should NOT have top-level declaration keywords
                    assert!(
                        !items.iter().any(|item| item.label == "institution"),
                        "institution should not appear inside behavior block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "species"),
                        "species should not appear inside behavior block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "character"),
                        "character should not appear inside behavior block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "location"),
                        "location should not appear inside behavior block"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_life_arc_completions() {
        let source = "life_arc Growing {\n    \n}";
        let doc = Document::new(source.to_string());
        // Position inside life arc block
        let params = make_params(1, 4);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Should have life arc keywords
                    assert!(items.iter().any(|item| item.label == "state"));
                    assert!(items.iter().any(|item| item.label == "on"));
                    // Should NOT have top-level declaration keywords
                    assert!(
                        !items.iter().any(|item| item.label == "institution"),
                        "institution should not appear inside life_arc block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "behavior"),
                        "behavior should not appear inside life_arc block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "character"),
                        "character should not appear inside life_arc block"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_behavior_no_toplevel_keywords() {
        // Cursor inside nested `then` block — context should remain InBehavior
        let source = "behavior Test {\n    then {\n        \n    }\n}";
        let doc = Document::new(source.to_string());
        // Position inside nested then block (line 2, after spaces)
        let params = make_params(2, 8);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Behavior keywords should still be present at any nesting depth
                    assert!(
                        items.iter().any(|item| item.label == "?"),
                        "? should appear in nested behavior block"
                    );
                    assert!(
                        items.iter().any(|item| item.label == "choose"),
                        "choose should appear in nested behavior block"
                    );
                    // Top-level keywords must not appear
                    assert!(
                        !items.iter().any(|item| item.label == "institution"),
                        "institution must not appear in nested behavior block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "species"),
                        "species must not appear in nested behavior block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "character"),
                        "character must not appear in nested behavior block"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_relationship_completions() {
        let source = "relationship Friends {\n    \n}";
        let doc = Document::new(source.to_string());
        // Position inside relationship block
        let params = make_params(1, 4);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Should have relationship keywords
                    assert!(items.iter().any(|item| item.label == "as"));
                    assert!(items.iter().any(|item| item.label == "self"));
                    assert!(items.iter().any(|item| item.label == "other"));
                    // Should NOT have top-level declaration keywords
                    assert!(
                        !items.iter().any(|item| item.label == "institution"),
                        "institution should not appear inside relationship block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "behavior"),
                        "behavior should not appear inside relationship block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "character"),
                        "character keyword should not appear inside relationship block"
                    );
                    // Should NOT have behavior-only keywords
                    assert!(
                        !items.iter().any(|item| item.label == "?"),
                        "? should not appear inside relationship block"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_type_suggestions_in_completions() {
        // More complete example with proper syntax
        let source = r#"template Child { age: number }
species Human {}
character Alice: Child {}
character Bob {}"#;
        let doc = Document::new(source.to_string());

        // Just check that templates and species are in completions
        let params = make_params(0, 0);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Should have Child and Human in completions
                    assert!(items.iter().any(|item| item.label == "Child"));
                    assert!(items.iter().any(|item| item.label == "Human"));
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_behavior_reference_in_symbols() {
        // Check that behaviors are in symbol table and show up in completions
        let source = "behavior WalkAround { patrol }\nbehavior Main { idle }";
        let doc = Document::new(source.to_string());

        let params = make_params(0, 0);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Behaviors should be in completions
                    assert!(
                        items.iter().any(|item| item.label.contains("WalkAround")),
                        "Should have WalkAround in completions"
                    );
                    assert!(
                        items.iter().any(|item| item.label.contains("Main")),
                        "Should have Main in completions"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_snippet_format_in_completions() {
        let doc = Document::new("".to_string());
        let params = make_params(0, 0);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Check that snippets have proper format
                    let char_item = items.iter().find(|item| item.label == "character");
                    assert!(char_item.is_some());

                    if let Some(item) = char_item {
                        assert!(item.insert_text.is_some());
                        assert!(item.insert_text_format.is_some());
                        // Should contain snippet placeholders
                        assert!(item.insert_text.as_ref().unwrap().contains("${"));
                    }
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_type_system_completions() {
        let doc = Document::new("".to_string());
        let params = make_params(0, 0);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Should have type system keywords
                    assert!(
                        items.iter().any(|item| item.label == "concept"),
                        "Should have concept keyword"
                    );
                    assert!(
                        items.iter().any(|item| item.label == "sub_concept"),
                        "Should have sub_concept keyword"
                    );
                    assert!(
                        items.iter().any(|item| item.label == "definition"),
                        "Should have definition keyword"
                    );

                    // Check snippets have correct format
                    let concept_item = items.iter().find(|item| item.label == "concept");
                    assert!(concept_item.is_some());
                    if let Some(item) = concept_item {
                        let snippet = item.insert_text.as_ref().unwrap();
                        assert!(
                            snippet.contains(";"),
                            "concept snippet should end with semicolon"
                        );
                    }

                    let sub_item = items.iter().find(|item| item.label == "sub_concept");
                    assert!(sub_item.is_some());
                    if let Some(item) = sub_item {
                        let snippet = item.insert_text.as_ref().unwrap();
                        assert!(
                            snippet.contains("."),
                            "sub_concept snippet should contain dot notation"
                        );
                    }
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_no_completions_after_declaration_keyword() {
        // After typing "behavior " the user is naming a new entity — no completions
        let source = "behavior ";
        let doc = Document::new(source.to_string());
        let params = make_params(0, 9); // right after "behavior "

        let result = completion::get_completions(&doc, &params);
        assert!(
            result.is_none(),
            "Should suppress completions when typing a name after a declaration keyword"
        );
    }

    #[test]
    fn test_no_completions_while_typing_name() {
        // User is typing "behavior Fo" — in the middle of naming
        let source = "behavior Fo";
        let doc = Document::new(source.to_string());
        let params = make_params(0, 11); // right after "Fo"

        let result = completion::get_completions(&doc, &params);
        assert!(
            result.is_none(),
            "Should suppress completions when in the middle of typing a declaration name"
        );
    }

    #[test]
    fn test_schedule_completions_no_toplevel() {
        let source = "schedule Daily {\n    \n}";
        let doc = Document::new(source.to_string());
        let params = make_params(1, 4);

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Should NOT have top-level declaration keywords
                    assert!(
                        !items.iter().any(|item| item.label == "institution"),
                        "institution should not appear inside schedule block"
                    );
                    assert!(
                        !items.iter().any(|item| item.label == "behavior"),
                        "behavior should not appear inside schedule block"
                    );
                    // Should NOT have behavior-only keywords
                    assert!(
                        !items.iter().any(|item| item.label == "?"),
                        "? should not appear inside schedule block"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }

    #[test]
    fn test_no_duplicate_completions() {
        let source = "character Alice {}\ncharacter Alice {}"; // Duplicate name
        let doc = Document::new(source.to_string());
        let params = make_params(0, 0);

        // Duplicate definitions cause NameTable::from_file() to fail,
        // resulting in an empty name table and no completions.
        // This is correct - duplicates should be caught as validation errors.
        assert!(
            !doc.resolve_errors.is_empty(),
            "Should have validation error for duplicate"
        );

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

        if let Some(response) = result {
            match response {
                | tower_lsp::lsp_types::CompletionResponse::Array(items) => {
                    // Count how many times "Alice" appears
                    let alice_count = items.iter().filter(|item| item.label == "Alice").count();
                    assert_eq!(
                        alice_count, 0,
                        "Should have no completions when there are duplicate definitions"
                    );
                },
                | _ => panic!("Expected array response"),
            }
        }
    }
}
