use pytest_language_server::FixtureDefinition;
use std::path::PathBuf;
use std::sync::Arc;
use tower_lsp::lsp_types::*;

#[test]
fn test_hover_content_with_leading_newline() {
    // Create a mock fixture definition with docstring
    let definition = FixtureDefinition {
        name: "my_fixture".to_string(),
        file_path: PathBuf::from("/tmp/test/conftest.py"),
        line: 4,
        docstring: Some("This is a test fixture.\n\nIt does something useful.".to_string()),
        return_type: None,
    };

    // Build hover content (same logic as hover method)
    let mut content = String::new();

    // Add "from" line with relative path (using just filename for test)
    let relative_path = definition
        .file_path
        .file_name()
        .and_then(|f| f.to_str())
        .unwrap_or("unknown");
    content.push_str(&format!("**from** `{}`\n", relative_path));

    // Add code block with fixture signature
    content.push_str(&format!(
        "```python\n@pytest.fixture\ndef {}(...):\n```",
        definition.name
    ));

    // Add docstring if present
    if let Some(ref docstring) = definition.docstring {
        content.push_str("\n\n---\n\n");
        content.push_str(docstring);
    }

    // Verify the structure
    let lines: Vec<&str> = content.lines().collect();

    // The structure should be:
    // 0: **from** `conftest.py`
    // 1: ```python
    // 2: @pytest.fixture
    // 3: def my_fixture(...):
    // 4: ```
    // 5: (empty line from \n\n---\n)
    // 6: ---
    // 7: (empty line)
    // 8+: docstring content

    assert!(
        lines[0].starts_with("**from**"),
        "Line 0 should start with 'From', got: '{}'",
        lines[0]
    );
    assert_eq!(lines[1], "```python");
    assert_eq!(lines[2], "@pytest.fixture");
    assert!(lines[3].starts_with("def my_fixture"));
    assert_eq!(lines[4], "```");
}

#[test]
fn test_hover_content_structure_without_docstring() {
    // Create a mock fixture definition without docstring
    let definition = FixtureDefinition {
        name: "simple_fixture".to_string(),
        file_path: PathBuf::from("/tmp/test/conftest.py"),
        line: 4,
        docstring: None,
        return_type: None,
    };

    // Build hover content
    let mut content = String::new();

    // Add "from" line with relative path (using just filename for test)
    let relative_path = definition
        .file_path
        .file_name()
        .and_then(|f| f.to_str())
        .unwrap_or("unknown");
    content.push_str(&format!("**from** `{}`\n", relative_path));

    // Add code block with fixture signature
    content.push_str(&format!(
        "```python\n@pytest.fixture\ndef {}(...):\n```",
        definition.name
    ));

    // For a fixture without docstring, the content should end with the code block
    let lines: Vec<&str> = content.lines().collect();

    assert_eq!(lines.len(), 5); // from line (1 line) + code block (4 lines)
    assert!(lines[0].starts_with("**from**"));
    assert_eq!(lines[1], "```python");
    assert_eq!(lines[4], "```");
}

#[test]
fn test_references_from_parent_definition() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Parent conftest
    let parent_content = r#"
import pytest

@pytest.fixture
def cli_runner():
    return "parent"
"#;
    let parent_conftest = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(parent_conftest.clone(), parent_content);

    // Child conftest with override
    let child_content = r#"
import pytest

@pytest.fixture
def cli_runner(cli_runner):
    return cli_runner
"#;
    let child_conftest = PathBuf::from("/tmp/project/tests/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

    // Test file using child fixture
    let test_content = r#"
def test_one(cli_runner):
    pass

def test_two(cli_runner):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/tests/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Get parent definition by clicking on the child's parameter (which references parent)
    // In child conftest, line 5 has "def cli_runner(cli_runner):"
    // Line 5 (1-indexed) = line 4 (0-indexed), char 19 is in the parameter "cli_runner"
    let parent_def = db.find_fixture_definition(&child_conftest, 4, 19);
    assert!(
        parent_def.is_some(),
        "Child parameter should resolve to parent definition"
    );

    // Find references for parent - should include child's parameter, not test usages
    let refs = db.find_references_for_definition(&parent_def.unwrap());

    assert!(
        refs.iter().any(|r| r.file_path == child_conftest),
        "Parent references should include child fixture parameter"
    );

    assert!(
        refs.iter().all(|r| r.file_path != test_path),
        "Parent references should NOT include test file usages (they use child)"
    );
}

#[test]
fn test_references_from_child_definition() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Parent conftest
    let parent_content = r#"
import pytest

@pytest.fixture
def cli_runner():
    return "parent"
"#;
    let parent_conftest = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(parent_conftest.clone(), parent_content);

    // Child conftest with override
    let child_content = r#"
import pytest

@pytest.fixture
def cli_runner(cli_runner):
    return cli_runner
"#;
    let child_conftest = PathBuf::from("/tmp/project/tests/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

    // Test file using child fixture
    let test_content = r#"
def test_one(cli_runner):
    pass

def test_two(cli_runner):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/tests/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Get child definition by clicking on a test usage
    // Line 2 (1-indexed) = line 1 (0-indexed), char 13 is in "cli_runner" parameter
    let child_def = db.find_fixture_definition(&test_path, 1, 13);
    assert!(
        child_def.is_some(),
        "Test usage should resolve to child definition"
    );

    // Find references for child - should include test usages
    let refs = db.find_references_for_definition(&child_def.unwrap());

    let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();

    assert_eq!(
        test_refs.len(),
        2,
        "Child references should include both test usages"
    );
}

#[test]
fn test_references_from_usage_in_test() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Parent conftest
    let parent_content = r#"
import pytest

@pytest.fixture
def cli_runner():
    return "parent"
"#;
    let parent_conftest = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(parent_conftest.clone(), parent_content);

    // Child conftest with override
    let child_content = r#"
import pytest

@pytest.fixture
def cli_runner(cli_runner):
    return cli_runner
"#;
    let child_conftest = PathBuf::from("/tmp/project/tests/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

    // Test file using child fixture
    let test_content = r#"
def test_one(cli_runner):
    pass

def test_two(cli_runner):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/tests/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Simulate clicking on cli_runner in test_one (line 2, 1-indexed)
    let resolved_def = db.find_fixture_definition(&test_path, 1, 13); // 0-indexed LSP

    assert!(resolved_def.is_some(), "Should resolve usage to definition");

    let def = resolved_def.unwrap();
    assert_eq!(
        def.file_path, child_conftest,
        "Usage should resolve to child definition, not parent"
    );

    // Get references for the resolved definition
    let refs = db.find_references_for_definition(&def);

    // Should include both test usages
    let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();

    assert_eq!(
        test_refs.len(),
        2,
        "References should include both test usages"
    );

    // Verify that the current usage (line 2 where we clicked) IS included
    let current_usage = refs
        .iter()
        .find(|r| r.file_path == test_path && r.line == 2);
    assert!(
        current_usage.is_some(),
        "References should include the current usage (line 2) where cursor is positioned"
    );

    // Verify the other usage is also included
    let other_usage = refs
        .iter()
        .find(|r| r.file_path == test_path && r.line == 5);
    assert!(
        other_usage.is_some(),
        "References should include the other usage (line 5)"
    );
}

#[test]
fn test_references_three_level_hierarchy() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Grandparent
    let grandparent_content = r#"
import pytest

@pytest.fixture
def db():
    return "root"
"#;
    let grandparent_conftest = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(grandparent_conftest.clone(), grandparent_content);

    // Parent overrides
    let parent_content = r#"
import pytest

@pytest.fixture
def db(db):
    return f"parent_{db}"
"#;
    let parent_conftest = PathBuf::from("/tmp/project/api/conftest.py");
    db.analyze_file(parent_conftest.clone(), parent_content);

    // Child overrides again
    let child_content = r#"
import pytest

@pytest.fixture
def db(db):
    return f"child_{db}"
"#;
    let child_conftest = PathBuf::from("/tmp/project/api/v1/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

    // Test at child level
    let test_content = r#"
def test_db(db):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/api/v1/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Get definitions by clicking on parameters that reference them
    // Parent conftest: "def db(db):" - parameter 'db' starts at position 7
    let grandparent_def = db
        .find_fixture_definition(&parent_conftest, 4, 7)
        .expect("Parent parameter should resolve to grandparent");
    // Child conftest: "def db(db):" - parameter 'db' starts at position 7
    let parent_def = db
        .find_fixture_definition(&child_conftest, 4, 7)
        .expect("Child parameter should resolve to parent");
    // Test: "def test_db(db):" - parameter 'db' starts at position 12
    let child_def = db
        .find_fixture_definition(&test_path, 1, 12)
        .expect("Test parameter should resolve to child");

    // Grandparent references should only include parent parameter
    let gp_refs = db.find_references_for_definition(&grandparent_def);
    assert!(
        gp_refs.iter().any(|r| r.file_path == parent_conftest),
        "Grandparent should have parent parameter"
    );
    assert!(
        gp_refs.iter().all(|r| r.file_path != child_conftest),
        "Grandparent should NOT have child references"
    );
    assert!(
        gp_refs.iter().all(|r| r.file_path != test_path),
        "Grandparent should NOT have test references"
    );

    // Parent references should only include child parameter
    let parent_refs = db.find_references_for_definition(&parent_def);
    assert!(
        parent_refs.iter().any(|r| r.file_path == child_conftest),
        "Parent should have child parameter"
    );
    assert!(
        parent_refs.iter().all(|r| r.file_path != test_path),
        "Parent should NOT have test references"
    );

    // Child references should include test usage
    let child_refs = db.find_references_for_definition(&child_def);
    assert!(
        child_refs.iter().any(|r| r.file_path == test_path),
        "Child should have test reference"
    );
}

#[test]
fn test_references_no_duplicate_definition() {
    // Test that when a fixture definition line also has a usage (self-referencing),
    // we don't list the definition twice in the results
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Parent conftest
    let parent_content = r#"
import pytest

@pytest.fixture
def cli_runner():
    return "parent"
"#;
    let parent_conftest = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(parent_conftest.clone(), parent_content);

    // Child conftest with self-referencing override
    let child_content = r#"
import pytest

@pytest.fixture
def cli_runner(cli_runner):
    return cli_runner
"#;
    let child_conftest = PathBuf::from("/tmp/project/tests/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

    // Test file
    let test_content = r#"
def test_one(cli_runner):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/tests/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Click on the child's parameter (which references parent)
    let parent_def = db
        .find_fixture_definition(&child_conftest, 4, 19)
        .expect("Should find parent definition from child parameter");

    // Get references for parent
    let refs = db.find_references_for_definition(&parent_def);

    // The child conftest line 5 should appear exactly once in references
    // (it's both a reference and a definition line, but should only appear once)
    let child_line_refs: Vec<_> = refs
        .iter()
        .filter(|r| r.file_path == child_conftest && r.line == 5)
        .collect();

    assert_eq!(
        child_line_refs.len(),
        1,
        "Child fixture line should appear exactly once in references (not duplicated)"
    );
}

#[test]
fn test_comprehensive_fixture_hierarchy_with_cursor_positions() {
    // This test validates all cursor position scenarios with fixture hierarchy
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Root conftest with parent fixtures
    let root_content = r#"
import pytest

@pytest.fixture
def cli_runner():
    return "parent"

@pytest.fixture
def other_fixture(cli_runner):
    return f"uses_{cli_runner}"
"#;
    let root_conftest = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(root_conftest.clone(), root_content);

    // Child conftest with override
    let child_content = r#"
import pytest

@pytest.fixture
def cli_runner(cli_runner):
    return cli_runner
"#;
    let child_conftest = PathBuf::from("/tmp/project/tests/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

    // Test file in child directory
    let test_content = r#"
def test_one(cli_runner):
    pass

def test_two(cli_runner):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/tests/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    println!("\n=== SCENARIO 1: Clicking on PARENT via another fixture that uses it ===");
    // Click on other_fixture's parameter to get parent definition
    let parent_def = db.find_fixture_definition(&root_conftest, 8, 20);
    println!(
        "Parent def: {:?}",
        parent_def.as_ref().map(|d| (&d.file_path, d.line))
    );

    if let Some(parent_def) = parent_def {
        let refs = db.find_references_for_definition(&parent_def);
        println!("Parent references count: {}", refs.len());
        for r in &refs {
            println!("  {:?}:{}", r.file_path, r.line);
        }

        // Parent should have:
        // 1. other_fixture parameter (line 9 in root conftest)
        // 2. Child fixture parameter (line 5 in child conftest)
        // NOT: test_one or test_two (they use child)

        let root_refs: Vec<_> = refs
            .iter()
            .filter(|r| r.file_path == root_conftest)
            .collect();
        let child_refs: Vec<_> = refs
            .iter()
            .filter(|r| r.file_path == child_conftest)
            .collect();
        let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();

        assert!(
            !root_refs.is_empty(),
            "Parent should have reference from other_fixture"
        );
        assert!(
            !child_refs.is_empty(),
            "Parent should have reference from child fixture"
        );
        assert!(
            test_refs.is_empty(),
            "Parent should NOT have test references (they use child)"
        );
    }

    println!("\n=== SCENARIO 2: Clicking on CHILD fixture via test usage ===");
    let child_def = db.find_fixture_definition(&test_path, 1, 13);
    println!(
        "Child def: {:?}",
        child_def.as_ref().map(|d| (&d.file_path, d.line))
    );

    if let Some(child_def) = child_def {
        let refs = db.find_references_for_definition(&child_def);
        println!("Child references count: {}", refs.len());
        for r in &refs {
            println!("  {:?}:{}", r.file_path, r.line);
        }

        // Child should have:
        // 1. test_one (line 2 in test file)
        // 2. test_two (line 5 in test file)
        // NOT: other_fixture (uses parent)

        let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();
        let root_refs: Vec<_> = refs
            .iter()
            .filter(|r| r.file_path == root_conftest)
            .collect();

        assert_eq!(test_refs.len(), 2, "Child should have 2 test references");
        assert!(
            root_refs.is_empty(),
            "Child should NOT have root conftest references"
        );
    }

    println!("\n=== SCENARIO 3: Clicking on CHILD fixture parameter (resolves to parent) ===");
    let parent_via_child_param = db.find_fixture_definition(&child_conftest, 4, 19);
    println!(
        "Parent via child param: {:?}",
        parent_via_child_param
            .as_ref()
            .map(|d| (&d.file_path, d.line))
    );

    if let Some(parent_def) = parent_via_child_param {
        assert_eq!(
            parent_def.file_path, root_conftest,
            "Child parameter should resolve to parent"
        );

        let refs = db.find_references_for_definition(&parent_def);

        // Should be same as SCENARIO 1
        let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();
        assert!(
            test_refs.is_empty(),
            "Parent (via child param) should NOT have test references"
        );
    }
}

#[test]
fn test_references_clicking_on_definition_line() {
    // Test that clicking on a fixture definition itself (not parameter, not usage)
    // correctly identifies which definition and returns appropriate references
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Parent conftest
    let parent_content = r#"
import pytest

@pytest.fixture
def cli_runner():
    return "parent"
"#;
    let parent_conftest = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(parent_conftest.clone(), parent_content);

    // Child conftest
    let child_content = r#"
import pytest

@pytest.fixture
def cli_runner(cli_runner):
    return cli_runner
"#;
    let child_conftest = PathBuf::from("/tmp/project/tests/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

    // Test file
    let test_content = r#"
def test_one(cli_runner):
    pass

def test_two(cli_runner):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/tests/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    println!("\n=== TEST: Clicking on child fixture definition (function name 'cli_runner') ===");
    // Line 5 (1-indexed) = line 4 (0-indexed), clicking on "def cli_runner" at char 4
    let fixture_name = db.find_fixture_at_position(&child_conftest, 4, 4);
    println!("Fixture name at position: {:?}", fixture_name);
    assert_eq!(fixture_name, Some("cli_runner".to_string()));

    // Get the definition at this line
    let child_def = db.get_definition_at_line(&child_conftest, 5, "cli_runner");
    println!(
        "Definition at line: {:?}",
        child_def.as_ref().map(|d| (&d.file_path, d.line))
    );
    assert!(
        child_def.is_some(),
        "Should find child definition at line 5"
    );

    if let Some(child_def) = child_def {
        let refs = db.find_references_for_definition(&child_def);
        println!("Child definition references count: {}", refs.len());
        for r in &refs {
            println!("  {:?}:{}", r.file_path, r.line);
        }

        // Child definition should have only test file usages, not parent conftest
        let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();
        let parent_refs: Vec<_> = refs
            .iter()
            .filter(|r| r.file_path == parent_conftest)
            .collect();

        assert_eq!(
            test_refs.len(),
            2,
            "Child definition should have 2 test references"
        );
        assert!(
            parent_refs.is_empty(),
            "Child definition should NOT have parent references"
        );
    }

    println!("\n=== TEST: Clicking on parent fixture definition (function name 'cli_runner') ===");
    let fixture_name = db.find_fixture_at_position(&parent_conftest, 4, 4);
    println!("Fixture name at position: {:?}", fixture_name);

    let parent_def = db.get_definition_at_line(&parent_conftest, 5, "cli_runner");
    println!(
        "Definition at line: {:?}",
        parent_def.as_ref().map(|d| (&d.file_path, d.line))
    );
    assert!(
        parent_def.is_some(),
        "Should find parent definition at line 5"
    );

    if let Some(parent_def) = parent_def {
        let refs = db.find_references_for_definition(&parent_def);
        println!("Parent definition references count: {}", refs.len());
        for r in &refs {
            println!("  {:?}:{}", r.file_path, r.line);
        }

        // Parent should have child's parameter, but NOT test file usages
        let child_refs: Vec<_> = refs
            .iter()
            .filter(|r| r.file_path == child_conftest)
            .collect();
        let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();

        assert!(
            !child_refs.is_empty(),
            "Parent should have child fixture parameter reference"
        );
        assert!(
            test_refs.is_empty(),
            "Parent should NOT have test file references"
        );
    }
}

#[test]
fn test_fixture_override_in_test_file_not_conftest() {
    // This reproduces the strawberry test_codegen.py scenario:
    // A test file that defines a fixture overriding a parent from conftest
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Parent in conftest
    let conftest_content = r#"
import pytest

@pytest.fixture
def cli_runner():
    return "parent"
"#;
    let conftest_path = PathBuf::from("/tmp/project/tests/cli/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    // Test file with fixture override AND tests using it
    let test_content = r#"
import pytest

@pytest.fixture
def cli_runner(cli_runner):
    return cli_runner

def test_one(cli_runner):
    pass

def test_two(cli_runner):
    pass

def test_three(cli_runner):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/tests/cli/test_codegen.py");
    db.analyze_file(test_path.clone(), test_content);

    println!(
        "\n=== SCENARIO 1: Click on child fixture definition (function name) in test file ==="
    );
    // Line 5 (1-indexed) = line 4 (0-indexed), "def cli_runner"
    let fixture_name = db.find_fixture_at_position(&test_path, 4, 4);
    println!("Fixture name: {:?}", fixture_name);
    assert_eq!(fixture_name, Some("cli_runner".to_string()));

    let child_def = db.get_definition_at_line(&test_path, 5, "cli_runner");
    println!(
        "Child def: {:?}",
        child_def.as_ref().map(|d| (&d.file_path, d.line))
    );
    assert!(
        child_def.is_some(),
        "Should find child definition in test file"
    );

    if let Some(child_def) = child_def {
        let refs = db.find_references_for_definition(&child_def);
        println!("Child references count: {}", refs.len());
        for r in &refs {
            println!("  {:?}:{}", r.file_path, r.line);
        }

        // Should only have references from the SAME FILE (test_one, test_two, test_three)
        // Should NOT have references from other files
        let same_file_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();
        let other_file_refs: Vec<_> = refs.iter().filter(|r| r.file_path != test_path).collect();

        assert_eq!(
            same_file_refs.len(),
            3,
            "Child should have 3 references in same file"
        );
        assert!(
            other_file_refs.is_empty(),
            "Child should NOT have references from other files"
        );
    }

    println!(
        "\n=== SCENARIO 2: Click on child fixture parameter (points to parent) in test file ==="
    );
    // Line 5, char 19 is the parameter "cli_runner"
    let parent_def = db.find_fixture_definition(&test_path, 4, 19);
    println!(
        "Parent def via child param: {:?}",
        parent_def.as_ref().map(|d| (&d.file_path, d.line))
    );

    if let Some(parent_def) = parent_def {
        assert_eq!(
            parent_def.file_path, conftest_path,
            "Parameter should resolve to parent in conftest"
        );

        let refs = db.find_references_for_definition(&parent_def);
        println!("Parent references count: {}", refs.len());
        for r in &refs {
            println!("  {:?}:{}", r.file_path, r.line);
        }

        // Parent should have:
        // 1. Child fixture parameter (line 5 in test file)
        // NOT: test_one, test_two, test_three (they use child, not parent)
        let test_file_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();

        // Should only have the child fixture's parameter (line 5), not the test usages
        assert_eq!(
            test_file_refs.len(),
            1,
            "Parent should have 1 reference from test file (child parameter only)"
        );
        assert_eq!(
            test_file_refs[0].line, 5,
            "Parent reference should be on line 5 (child fixture parameter)"
        );
    }

    println!("\n=== SCENARIO 3: Click on usage in test function ===");
    // Line 8 (1-indexed) = line 7 (0-indexed), test_one's cli_runner parameter
    let resolved = db.find_fixture_definition(&test_path, 7, 17);
    println!(
        "Resolved from test: {:?}",
        resolved.as_ref().map(|d| (&d.file_path, d.line))
    );

    if let Some(def) = resolved {
        assert_eq!(
            def.file_path, test_path,
            "Test usage should resolve to child in same file"
        );
        assert_eq!(def.line, 5, "Should resolve to child fixture at line 5");
    }
}

#[test]
fn test_references_include_current_position() {
    // LSP Spec requirement: textDocument/references should include the current position
    // where the cursor is, whether it's a usage or a definition
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def cli_runner():
    return "runner"
"#;
    let conftest_path = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    let test_content = r#"
def test_one(cli_runner):
    pass

def test_two(cli_runner):
    pass

def test_three(cli_runner):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    println!("\n=== TEST: Click on usage at test_one (line 2) ===");
    // Line 2 (1-indexed), clicking on cli_runner parameter
    let fixture_name = db.find_fixture_at_position(&test_path, 1, 13);
    assert_eq!(fixture_name, Some("cli_runner".to_string()));

    let resolved_def = db.find_fixture_definition(&test_path, 1, 13);
    assert!(
        resolved_def.is_some(),
        "Should resolve to conftest definition"
    );

    let def = resolved_def.unwrap();
    let refs = db.find_references_for_definition(&def);

    println!("References found: {}", refs.len());
    for r in &refs {
        println!(
            "  {:?}:{} (chars {}-{})",
            r.file_path.file_name(),
            r.line,
            r.start_char,
            r.end_char
        );
    }

    // CRITICAL: References should include ALL usages, including the current one
    assert_eq!(refs.len(), 3, "Should have 3 references (all test usages)");

    // Verify line 2 (where we clicked) IS included
    let line2_ref = refs
        .iter()
        .find(|r| r.file_path == test_path && r.line == 2);
    assert!(
        line2_ref.is_some(),
        "References MUST include current position (line 2)"
    );

    // Verify other lines are also included
    let line5_ref = refs
        .iter()
        .find(|r| r.file_path == test_path && r.line == 5);
    assert!(line5_ref.is_some(), "References should include line 5");

    let line8_ref = refs
        .iter()
        .find(|r| r.file_path == test_path && r.line == 8);
    assert!(line8_ref.is_some(), "References should include line 8");

    println!("\n=== TEST: Click on usage at test_two (line 5) ===");
    let resolved_def = db.find_fixture_definition(&test_path, 4, 13);
    assert!(resolved_def.is_some());

    let def = resolved_def.unwrap();
    let refs = db.find_references_for_definition(&def);

    // Should still have all 3 references
    assert_eq!(refs.len(), 3, "Should have 3 references");

    // Current position (line 5) MUST be included
    let line5_ref = refs
        .iter()
        .find(|r| r.file_path == test_path && r.line == 5);
    assert!(
        line5_ref.is_some(),
        "References MUST include current position (line 5)"
    );

    // Simulate LSP handler logic: verify no references would be incorrectly skipped
    // (only skip if reference is on same line as definition)
    for r in &refs {
        if r.file_path == def.file_path && r.line == def.line {
            println!(
                "  Would skip (same as definition): {:?}:{}",
                r.file_path.file_name(),
                r.line
            );
        } else {
            println!(
                "  Would include: {:?}:{} (chars {}-{})",
                r.file_path.file_name(),
                r.line,
                r.start_char,
                r.end_char
            );
        }
    }

    // In this scenario, no references should be skipped (definition is in conftest, usages in test file)
    let would_be_skipped = refs
        .iter()
        .filter(|r| r.file_path == def.file_path && r.line == def.line)
        .count();
    assert_eq!(
        would_be_skipped, 0,
        "No references should be skipped in this scenario"
    );

    println!("\n=== TEST: Click on definition (line 5 in conftest) ===");
    // When clicking on the definition itself, references should include all usages
    let fixture_name = db.find_fixture_at_position(&conftest_path, 4, 4);
    assert_eq!(fixture_name, Some("cli_runner".to_string()));

    // This should return None (we're on definition, not usage)
    let resolved = db.find_fixture_definition(&conftest_path, 4, 4);
    assert!(
        resolved.is_none(),
        "Clicking on definition name should return None"
    );

    // Get definition at this line
    let def = db.get_definition_at_line(&conftest_path, 5, "cli_runner");
    assert!(def.is_some());

    let def = def.unwrap();
    let refs = db.find_references_for_definition(&def);

    // Should have all 3 test usages
    assert_eq!(refs.len(), 3, "Definition should have 3 usage references");

    println!("\nAll LSP spec requirements verified ✓");
}

#[test]
fn test_references_multiline_function_signature() {
    // Test that references work correctly with multiline function signatures
    // This simulates the strawberry test_codegen.py scenario
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def cli_runner():
    return "runner"
"#;
    let conftest_path = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    // Multiline function signature (like strawberry line 87-89)
    let test_content = r#"
def test_codegen(
    cli_app: Typer, cli_runner: CliRunner, query_file_path: Path
):
    pass

def test_another(cli_runner):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/test_codegen.py");
    db.analyze_file(test_path.clone(), test_content);

    println!("\n=== TEST: Click on cli_runner in function signature (line 3, char 23) ===");
    // Line 3 (1-indexed): "    cli_app: Typer, cli_runner: CliRunner, query_file_path: Path"
    // Character position 23 should be in "cli_runner" (starts at ~20)

    let fixture_name = db.find_fixture_at_position(&test_path, 2, 23); // 0-indexed for LSP
    println!("Fixture at position: {:?}", fixture_name);
    assert_eq!(
        fixture_name,
        Some("cli_runner".to_string()),
        "Should find cli_runner at this position"
    );

    let resolved_def = db.find_fixture_definition(&test_path, 2, 23);
    assert!(
        resolved_def.is_some(),
        "Should resolve to conftest definition"
    );

    let def = resolved_def.unwrap();
    println!("Resolved to: {:?}:{}", def.file_path.file_name(), def.line);

    let refs = db.find_references_for_definition(&def);
    println!("\nReferences found: {}", refs.len());
    for r in &refs {
        println!(
            "  {:?}:{} (chars {}-{})",
            r.file_path.file_name(),
            r.line,
            r.start_char,
            r.end_char
        );
    }

    // Should have 2 references: line 3 (signature) and line 7 (test_another)
    assert_eq!(
        refs.len(),
        2,
        "Should have 2 references (both function signatures)"
    );

    // CRITICAL: Line 3 (where we clicked) MUST be included
    let line3_ref = refs
        .iter()
        .find(|r| r.file_path == test_path && r.line == 3);
    assert!(
        line3_ref.is_some(),
        "References MUST include current position (line 3 in signature)"
    );

    // Also verify line 7 (test_another) is included
    let line7_ref = refs
        .iter()
        .find(|r| r.file_path == test_path && r.line == 7);
    assert!(
        line7_ref.is_some(),
        "References should include test_another parameter (line 7)"
    );

    println!("\nMultiline signature test passed ✓");
}

#[tokio::test]
async fn test_code_action_for_undeclared_fixture() {
    // Test that code actions are generated for undeclared fixtures
    use pytest_language_server::FixtureDatabase;

    let db = Arc::new(FixtureDatabase::new());

    let conftest_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42
"#;
    let conftest_path = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    let test_content = r#"
def test_undeclared():
    result = my_fixture + 1
    assert result == 43
"#;
    let test_path = PathBuf::from("/tmp/project/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Get undeclared fixtures
    let undeclared = db.get_undeclared_fixtures(&test_path);
    println!("\nUndeclared fixtures: {:?}", undeclared);
    assert_eq!(undeclared.len(), 1, "Should have 1 undeclared fixture");

    let fixture = &undeclared[0];
    assert_eq!(fixture.name, "my_fixture");
    assert_eq!(fixture.line, 3); // 1-indexed
    assert_eq!(fixture.function_name, "test_undeclared");
    assert_eq!(fixture.function_line, 2); // 1-indexed

    // Simulate creating a diagnostic
    let diagnostic = Diagnostic {
        range: Range {
            start: Position {
                line: (fixture.line - 1) as u32, // 0-indexed for LSP
                character: fixture.start_char as u32,
            },
            end: Position {
                line: (fixture.line - 1) as u32,
                character: fixture.end_char as u32,
            },
        },
        severity: Some(DiagnosticSeverity::WARNING),
        code: Some(NumberOrString::String("undeclared-fixture".to_string())),
        source: Some("pytest-lsp".to_string()),
        message: format!(
            "Fixture '{}' is used but not declared as a parameter",
            fixture.name
        ),
        code_description: None,
        related_information: None,
        tags: None,
        data: None,
    };

    println!("Created diagnostic: {:?}", diagnostic);

    // Now test that the Backend would create a code action
    // We can't easily test the actual LSP handler without a full LSP setup,
    // but we can verify the data structures are correct
    assert_eq!(
        diagnostic.code,
        Some(NumberOrString::String("undeclared-fixture".to_string()))
    );
    assert_eq!(diagnostic.range.start.line, 2); // Line 3 in 1-indexed is line 2 in 0-indexed

    println!("\nCode action test passed ✓");
}

// ============================================================================
// HIGH PRIORITY TESTS: LSP Protocol Edge Cases
// ============================================================================

#[test]
fn test_position_in_string_literal() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    let test_content = r#"
def test_something(my_fixture):
    # Fixture name in string literal - should NOT trigger goto-definition
    text = "my_fixture"
    assert my_fixture == 42
"#;
    let test_path = PathBuf::from("/tmp/test/test_string.py");
    db.analyze_file(test_path.clone(), test_content);

    // Try to find definition at position inside string literal "my_fixture"
    // Line 3 (0-indexed), character 12 is inside the string
    let definition = db.find_fixture_definition(&test_path, 3, 12);

    // Should NOT find definition because cursor is in a string literal
    // Note: Current implementation may not distinguish string literals from identifiers
    if definition.is_some() {
        println!("LIMITATION: String literals not distinguished from identifiers");
        // This is a known limitation - the current implementation doesn't
        // have context about whether a position is in a string or comment
    } else {
        // Correctly ignores string literals
    }
}

#[test]
fn test_position_in_comment() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    let test_content = r#"
def test_something(my_fixture):
    # my_fixture is used here - cursor should not trigger
    assert my_fixture == 42
"#;
    let test_path = PathBuf::from("/tmp/test/test_comment.py");
    db.analyze_file(test_path.clone(), test_content);

    // Try to find definition at position inside comment
    // Line 2 (0-indexed), character 8 is inside "# my_fixture"
    let definition = db.find_fixture_definition(&test_path, 2, 8);

    // Should NOT find definition in comment
    // Note: Current implementation doesn't track comments, so this depends on usage tracking
    if definition.is_some() {
        println!("LIMITATION: Comments not distinguished from code");
    } else {
        // Correctly ignores comments
    }
}

#[test]
fn test_empty_file() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let empty_content = "";
    let test_path = PathBuf::from("/tmp/test/test_empty.py");
    db.analyze_file(test_path.clone(), empty_content);

    // Should not crash on empty file
    let definition = db.find_fixture_definition(&test_path, 0, 0);
    assert!(definition.is_none(), "Empty file should return None");

    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert!(
        undeclared.is_empty(),
        "Empty file should have no undeclared fixtures"
    );
}

#[test]
fn test_position_out_of_bounds() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let test_content = r#"
def test_something():
    assert True
"#;
    let test_path = PathBuf::from("/tmp/test/test_bounds.py");
    db.analyze_file(test_path.clone(), test_content);

    // Try position beyond last line
    let definition = db.find_fixture_definition(&test_path, 999, 0);
    assert!(
        definition.is_none(),
        "Out of bounds line should return None"
    );

    // Try position beyond last character on valid line
    let definition2 = db.find_fixture_definition(&test_path, 1, 9999);
    assert!(
        definition2.is_none(),
        "Out of bounds character should return None"
    );
}

#[test]
fn test_whitespace_only_file() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let whitespace_content = "   \n\n\t\t\n   \n";
    let test_path = PathBuf::from("/tmp/test/test_whitespace.py");
    db.analyze_file(test_path.clone(), whitespace_content);

    // Should handle whitespace-only file gracefully
    let definition = db.find_fixture_definition(&test_path, 1, 2);
    assert!(definition.is_none(), "Whitespace file should return None");

    // Should not detect any fixtures
    assert!(
        !db.definitions
            .iter()
            .any(|entry| { entry.value().iter().any(|def| def.file_path == test_path) }),
        "Whitespace file should not have fixtures"
    );
}

#[test]
fn test_malformed_python_syntax() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Python file with syntax error
    let malformed_content = r#"
import pytest

@pytest.fixture
def incomplete_fixture(
    # Missing closing parenthesis and function body
"#;
    let test_path = PathBuf::from("/tmp/test/test_malformed.py");
    db.analyze_file(test_path.clone(), malformed_content);

    // Should not crash on syntax error
    // Fixture detection may or may not work depending on how parser handles errors
    println!("Malformed file handled without crash");

    // Just verify it doesn't panic
    let _ = db.get_undeclared_fixtures(&test_path);
    // Malformed file handled gracefully
}

#[test]
fn test_multi_byte_utf8_characters() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "测试"
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    let test_content = r#"
def test_unicode(my_fixture):
    # Comment with emoji 🔥 and Chinese 测试
    result = my_fixture
    assert result == "测试"
"#;
    let test_path = PathBuf::from("/tmp/test/test_unicode.py");
    db.analyze_file(test_path.clone(), test_content);

    // Verify usages were detected despite unicode in file
    let usages = db.usages.get(&test_path);
    assert!(
        usages.is_some(),
        "Should detect usages in file with unicode"
    );

    // Verify fixture can be found
    let definition = db.find_fixture_definition(&test_path, 1, 17);
    assert!(definition.is_some(), "Should find fixture in unicode file");
}

#[test]
fn test_very_long_line() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def fixture_with_very_long_name_that_exceeds_normal_expectations():
    return 42
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    let test_content = r#"
def test_long(fixture_with_very_long_name_that_exceeds_normal_expectations):
    result = fixture_with_very_long_name_that_exceeds_normal_expectations
    assert result == 42
"#;
    let test_path = PathBuf::from("/tmp/test/test_long.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should handle very long fixture names
    assert!(db
        .definitions
        .contains_key("fixture_with_very_long_name_that_exceeds_normal_expectations"));

    let usages = db.usages.get(&test_path);
    assert!(usages.is_some(), "Should detect long fixture names");
}

// ============================================================================
// HIGH PRIORITY TESTS: Error Handling
// ============================================================================

#[test]
fn test_invalid_utf8_content() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Invalid UTF-8 byte sequences
    // Rust strings must be valid UTF-8, so we can't actually create invalid UTF-8 in a string literal
    // This test documents that the file reading layer should handle this

    // Instead, test with valid but unusual UTF-8
    let unusual_content = "import pytest\n\n@pytest.fixture\ndef \u{FEFF}bom_fixture():  # BOM character\n    return 42";
    let test_path = PathBuf::from("/tmp/test/test_utf8.py");
    db.analyze_file(test_path.clone(), unusual_content);

    // Should handle without crashing
    println!("UTF-8 with unusual characters handled gracefully");
    // No crash on unusual UTF-8
}

#[test]
fn test_incomplete_function_definition() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let incomplete_content = r#"
import pytest

@pytest.fixture
def incomplete_fixture(
"#;
    let test_path = PathBuf::from("/tmp/test/test_incomplete.py");
    db.analyze_file(test_path.clone(), incomplete_content);

    // Should not crash, but won't detect incomplete fixture
    // The parser will fail, and we should handle that gracefully
    println!("Incomplete function definition handled without panic");
    // Graceful handling of syntax error
}

#[test]
fn test_truncated_file() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let truncated_content = r#"
import pytest

@pytest.fixture
def truncated_fixture():
    return "
"#;
    let test_path = PathBuf::from("/tmp/test/test_truncated.py");
    db.analyze_file(test_path.clone(), truncated_content);

    // Should handle truncated string literal without crash
    println!("Truncated file handled gracefully");
    // No crash on truncated file
}

#[test]
fn test_mixed_line_endings() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Mix of \n (Unix) and \r\n (Windows) line endings
    let mixed_content =
        "import pytest\r\n\n@pytest.fixture\r\ndef my_fixture():\n    return 42\r\n";

    let test_path = PathBuf::from("/tmp/test/test_mixed.py");
    db.analyze_file(test_path.clone(), mixed_content);

    // Should detect fixture despite mixed line endings
    assert!(
        db.definitions.contains_key("my_fixture"),
        "Should detect fixtures with mixed line endings"
    );
}

#[test]
fn test_file_with_only_comments() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let comment_only = r#"
# This is a comment
# Another comment
# TODO: implement tests
"#;
    let test_path = PathBuf::from("/tmp/test/test_comments.py");
    db.analyze_file(test_path.clone(), comment_only);

    // Should not crash, no fixtures detected
    assert!(
        !db.definitions
            .iter()
            .any(|entry| { entry.value().iter().any(|def| def.file_path == test_path) }),
        "Comment-only file should have no fixtures"
    );
}

#[test]
fn test_deeply_nested_indentation() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let nested_content = r#"
import pytest

@pytest.fixture
def deeply_nested():
    class A:
        class B:
            class C:
                class D:
                    def inner():
                        def more_inner():
                            return 42
    return A()
"#;
    let test_path = PathBuf::from("/tmp/test/test_nested.py");
    db.analyze_file(test_path.clone(), nested_content);

    // Should detect the fixture definition despite deep nesting
    assert!(
        db.definitions.contains_key("deeply_nested"),
        "Should handle deeply nested structures"
    );
}

#[test]
fn test_tabs_and_spaces_mixed() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Python typically rejects mixed tabs and spaces, but parser should handle it
    let mixed_indentation = "import pytest\n\n@pytest.fixture\ndef my_fixture():\n\treturn 42  # tab\n    # space indentation";

    let test_path = PathBuf::from("/tmp/test/test_tabs.py");
    db.analyze_file(test_path.clone(), mixed_indentation);

    // Should detect fixture or handle parse error gracefully
    if db.definitions.contains_key("my_fixture") {
        // Fixture detected despite mixed indentation
    } else {
        println!("Parser rejected mixed tabs/spaces (expected)");
        // Graceful handling of indentation error
    }
}

#[test]
fn test_non_ascii_fixture_name() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    // Python 3 allows non-ASCII identifiers
    let non_ascii_content = r#"
import pytest

@pytest.fixture
def测试_fixture():
    return "test"

@pytest.fixture
def фикстура():
    return "fixture"
"#;
    let test_path = PathBuf::from("/tmp/test/test_non_ascii.py");
    db.analyze_file(test_path.clone(), non_ascii_content);

    // Should handle non-ASCII fixture names
    if db.definitions.contains_key("测试_fixture") {
        // Non-ASCII fixture names supported
        assert!(db.definitions.contains_key("фикстура"));
    } else {
        println!("LIMITATION: Non-ASCII identifiers not fully supported");
        // Test documents non-ASCII handling
    }
}

// MARK: - Renamed Fixtures Tests (name= parameter)

#[test]
fn test_goto_definition_renamed_fixture() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let conftest = r#"
import pytest

@pytest.fixture(name="db_conn")
def internal_database_connection():
    return "connection"
"#;
    let conftest_path = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest);

    let test_content = r#"
def test_uses_renamed(db_conn):
    assert db_conn == "connection"
"#;
    let test_path = PathBuf::from("/tmp/project/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Click on db_conn in test - should find definition
    let fixture_name = db.find_fixture_at_position(&test_path, 1, 22);
    assert_eq!(fixture_name, Some("db_conn".to_string()));

    let definition = db.find_fixture_definition(&test_path, 1, 22);
    assert!(
        definition.is_some(),
        "Should find renamed fixture definition"
    );

    let def = definition.unwrap();
    assert_eq!(def.name, "db_conn");
    assert_eq!(def.file_path, conftest_path);
    assert_eq!(def.line, 5); // Line where function def is (1-indexed)
}

#[test]
fn test_find_references_renamed_fixture() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let conftest = r#"
import pytest

@pytest.fixture(name="client")
def create_test_client():
    return "test_client"
"#;
    let conftest_path = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest);

    let test_content = r#"
def test_one(client):
    pass

def test_two(client):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Get definition and find references
    let definition = db.find_fixture_definition(&test_path, 1, 14);
    assert!(definition.is_some());

    let refs = db.find_references_for_definition(&definition.unwrap());
    assert_eq!(refs.len(), 2, "Should find 2 references to 'client'");

    // Both should reference "client" not "create_test_client"
    assert!(refs.iter().all(|r| r.name == "client"));
}

#[test]
fn test_renamed_fixture_with_dependency() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(name="db")
def database_fixture():
    return "database"

@pytest.fixture(name="user")
def user_fixture(db):
    return {"db": db}

def test_example(user, db):
    pass
"#;
    let file_path = PathBuf::from("/tmp/project/test_file.py");
    db.analyze_file(file_path.clone(), content);

    // Verify both renamed fixtures are registered correctly
    assert!(db.definitions.contains_key("db"));
    assert!(db.definitions.contains_key("user"));
    assert!(!db.definitions.contains_key("database_fixture"));
    assert!(!db.definitions.contains_key("user_fixture"));

    // Verify usages: user_fixture uses db, test uses both
    let usages = db.usages.get(&file_path).unwrap();
    let db_usages: Vec<_> = usages.iter().filter(|u| u.name == "db").collect();
    let user_usages: Vec<_> = usages.iter().filter(|u| u.name == "user").collect();

    assert_eq!(
        db_usages.len(),
        2,
        "db should be used twice (in user_fixture and test)"
    );
    assert_eq!(user_usages.len(), 1, "user should be used once (in test)");
}

#[test]
fn test_normal_fixture_no_regression() {
    // Ensure fixtures without name= still work correctly
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let conftest = r#"
import pytest

@pytest.fixture
def normal_fixture():
    return "normal"

@pytest.fixture(scope="session")
def session_fixture():
    return "session"

@pytest.fixture(autouse=True)
def autouse_fixture():
    return "autouse"
"#;
    let conftest_path = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest);

    let test_content = r#"
def test_example(normal_fixture, session_fixture):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // All fixtures should be registered by function name
    assert!(db.definitions.contains_key("normal_fixture"));
    assert!(db.definitions.contains_key("session_fixture"));
    assert!(db.definitions.contains_key("autouse_fixture"));

    // Goto definition should work
    let def = db.find_fixture_definition(&test_path, 1, 18);
    assert!(def.is_some());
    assert_eq!(def.unwrap().name, "normal_fixture");

    // References should work
    let def = db.find_fixture_definition(&test_path, 1, 18).unwrap();
    let refs = db.find_references_for_definition(&def);
    assert_eq!(refs.len(), 1);
}

#[test]
fn test_mixed_renamed_and_normal_fixtures() {
    use pytest_language_server::FixtureDatabase;

    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(name="renamed")
def internal_name():
    return 1

@pytest.fixture
def normal():
    return 2

def test_mixed(renamed, normal):
    pass
"#;
    let file_path = PathBuf::from("/tmp/project/test_file.py");
    db.analyze_file(file_path.clone(), content);

    // Renamed fixture uses alias
    assert!(db.definitions.contains_key("renamed"));
    assert!(!db.definitions.contains_key("internal_name"));

    // Normal fixture uses function name
    assert!(db.definitions.contains_key("normal"));

    // Both should be findable via goto definition
    let renamed_def = db.find_fixture_definition(&file_path, 11, 15);
    let normal_def = db.find_fixture_definition(&file_path, 11, 24);

    assert!(renamed_def.is_some());
    assert!(normal_def.is_some());
    assert_eq!(renamed_def.unwrap().name, "renamed");
    assert_eq!(normal_def.unwrap().name, "normal");
}
