//! Unit tests for the FixtureDatabase.
//!
//! All tests have a 30-second timeout to prevent hangs from blocking CI.

use ntest::timeout;
use pytest_language_server::FixtureDatabase;
use std::path::PathBuf;

#[test]
#[timeout(30000)]
fn test_fixture_definition_detection() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42

@fixture
def another_fixture():
    return "hello"
"#;

    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    // Check that fixtures were detected
    assert!(db.definitions.contains_key("my_fixture"));
    assert!(db.definitions.contains_key("another_fixture"));

    // Check fixture details
    let my_fixture_defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(my_fixture_defs.len(), 1);
    assert_eq!(my_fixture_defs[0].name, "my_fixture");
    assert_eq!(my_fixture_defs[0].file_path, conftest_path);
}

#[test]
#[timeout(30000)]
fn test_fixture_usage_detection() {
    let db = FixtureDatabase::new();

    let test_content = r#"
def test_something(my_fixture, another_fixture):
    assert my_fixture == 42
    assert another_fixture == "hello"

def test_other(my_fixture):
    assert my_fixture > 0
"#;

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

    // Check that usages were detected
    assert!(db.usages.contains_key(&test_path));

    let usages = db.usages.get(&test_path).unwrap();
    // Should have usages from the first test function (we only track one function per file currently)
    assert!(usages.iter().any(|u| u.name == "my_fixture"));
    assert!(usages.iter().any(|u| u.name == "another_fixture"));
}

#[test]
#[timeout(30000)]
fn test_go_to_definition() {
    let db = FixtureDatabase::new();

    // Set up conftest.py with a fixture
    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.clone(), conftest_content);

    // Set up a test file that uses the fixture
    let test_content = r#"
def test_something(my_fixture):
    assert my_fixture == 42
"#;

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

    // Try to find the definition from the test file
    // The usage is on line 2 (1-indexed) - that's where the function parameter is
    // In 0-indexed LSP coordinates, that's line 1
    // Character position 19 is where 'my_fixture' starts
    let definition = db.find_fixture_definition(&test_path, 1, 19);

    assert!(definition.is_some(), "Definition should be found");
    let def = definition.unwrap();
    assert_eq!(def.name, "my_fixture");
    assert_eq!(def.file_path, conftest_path);
}

#[test]
#[timeout(30000)]
fn test_fixture_decorator_variations() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest
from pytest import fixture

@pytest.fixture
def fixture1():
    pass

@pytest.fixture()
def fixture2():
    pass

@fixture
def fixture3():
    pass

@fixture()
def fixture4():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    // Check all variations were detected
    assert!(db.definitions.contains_key("fixture1"));
    assert!(db.definitions.contains_key("fixture2"));
    assert!(db.definitions.contains_key("fixture3"));
    assert!(db.definitions.contains_key("fixture4"));
}

#[test]
#[timeout(30000)]
fn test_fixture_in_test_file() {
    let db = FixtureDatabase::new();

    // Test file with fixture defined in the same file
    let test_content = r#"
import pytest

@pytest.fixture
def local_fixture():
    return 42

def test_something(local_fixture):
    assert local_fixture == 42
"#;

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

    // Check that fixture was detected even though it's not in conftest.py
    assert!(db.definitions.contains_key("local_fixture"));

    let local_fixture_defs = db.definitions.get("local_fixture").unwrap();
    assert_eq!(local_fixture_defs.len(), 1);
    assert_eq!(local_fixture_defs[0].name, "local_fixture");
    assert_eq!(local_fixture_defs[0].file_path, test_path);

    // Check that usage was detected
    assert!(db.usages.contains_key(&test_path));
    let usages = db.usages.get(&test_path).unwrap();
    assert!(usages.iter().any(|u| u.name == "local_fixture"));

    // Test go-to-definition for fixture in same file
    let usage_line = usages
        .iter()
        .find(|u| u.name == "local_fixture")
        .map(|u| u.line)
        .unwrap();

    // Character position 19 is where 'local_fixture' starts in "def test_something(local_fixture):"
    let definition = db.find_fixture_definition(&test_path, (usage_line - 1) as u32, 19);
    assert!(
        definition.is_some(),
        "Should find definition for fixture in same file. Line: {}, char: 19",
        usage_line
    );
    let def = definition.unwrap();
    assert_eq!(def.name, "local_fixture");
    assert_eq!(def.file_path, test_path);
}

#[test]
#[timeout(30000)]
fn test_async_test_functions() {
    let db = FixtureDatabase::new();

    // Test file with async test function
    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42

async def test_async_function(my_fixture):
    assert my_fixture == 42

def test_sync_function(my_fixture):
    assert my_fixture == 42
"#;

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

    // Check that fixture was detected
    assert!(db.definitions.contains_key("my_fixture"));

    // Check that both async and sync test functions have their usages detected
    assert!(db.usages.contains_key(&test_path));
    let usages = db.usages.get(&test_path).unwrap();

    // Should have 2 usages (one from async, one from sync)
    let fixture_usages: Vec<_> = usages.iter().filter(|u| u.name == "my_fixture").collect();
    assert_eq!(
        fixture_usages.len(),
        2,
        "Should detect fixture usage in both async and sync tests"
    );
}

#[test]
#[timeout(30000)]
fn test_extract_word_at_position() {
    let db = FixtureDatabase::new();

    // Test basic word extraction
    let line = "def test_something(my_fixture):";

    // Cursor on 'm' of 'my_fixture' (position 19)
    assert_eq!(
        db.extract_word_at_position(line, 19),
        Some("my_fixture".to_string())
    );

    // Cursor on 'y' of 'my_fixture' (position 20)
    assert_eq!(
        db.extract_word_at_position(line, 20),
        Some("my_fixture".to_string())
    );

    // Cursor on last 'e' of 'my_fixture' (position 28)
    assert_eq!(
        db.extract_word_at_position(line, 28),
        Some("my_fixture".to_string())
    );

    // Cursor on 'd' of 'def' (position 0)
    assert_eq!(
        db.extract_word_at_position(line, 0),
        Some("def".to_string())
    );

    // Cursor on space after 'def' (position 3) - should return None
    assert_eq!(db.extract_word_at_position(line, 3), None);

    // Cursor on 't' of 'test_something' (position 4)
    assert_eq!(
        db.extract_word_at_position(line, 4),
        Some("test_something".to_string())
    );

    // Cursor on opening parenthesis (position 18) - should return None
    assert_eq!(db.extract_word_at_position(line, 18), None);

    // Cursor on closing parenthesis (position 29) - should return None
    assert_eq!(db.extract_word_at_position(line, 29), None);

    // Cursor on colon (position 31) - should return None
    assert_eq!(db.extract_word_at_position(line, 31), None);
}

#[test]
#[timeout(30000)]
fn test_extract_word_at_position_fixture_definition() {
    let db = FixtureDatabase::new();

    let line = "@pytest.fixture";

    // Cursor on '@' - should return None
    assert_eq!(db.extract_word_at_position(line, 0), None);

    // Cursor on 'p' of 'pytest' (position 1)
    assert_eq!(
        db.extract_word_at_position(line, 1),
        Some("pytest".to_string())
    );

    // Cursor on '.' - should return None
    assert_eq!(db.extract_word_at_position(line, 7), None);

    // Cursor on 'f' of 'fixture' (position 8)
    assert_eq!(
        db.extract_word_at_position(line, 8),
        Some("fixture".to_string())
    );

    let line2 = "def foo(other_fixture):";

    // Cursor on 'd' of 'def'
    assert_eq!(
        db.extract_word_at_position(line2, 0),
        Some("def".to_string())
    );

    // Cursor on space after 'def' - should return None
    assert_eq!(db.extract_word_at_position(line2, 3), None);

    // Cursor on 'f' of 'foo'
    assert_eq!(
        db.extract_word_at_position(line2, 4),
        Some("foo".to_string())
    );

    // Cursor on 'o' of 'other_fixture'
    assert_eq!(
        db.extract_word_at_position(line2, 8),
        Some("other_fixture".to_string())
    );

    // Cursor on parenthesis - should return None
    assert_eq!(db.extract_word_at_position(line2, 7), None);
}

#[test]
#[timeout(30000)]
fn test_word_detection_only_on_fixtures() {
    let db = FixtureDatabase::new();

    // Set up a conftest with a fixture
    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.clone(), conftest_content);

    // Set up a test file
    let test_content = r#"
def test_something(my_fixture, regular_param):
    assert my_fixture == 42
"#;
    let test_path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Line 2 is "def test_something(my_fixture, regular_param):"
    // Character positions:
    // 0: 'd' of 'def'
    // 4: 't' of 'test_something'
    // 19: 'm' of 'my_fixture'
    // 31: 'r' of 'regular_param'

    // Cursor on 'def' - should NOT find a fixture (LSP line 1, 0-based)
    assert_eq!(db.find_fixture_definition(&test_path, 1, 0), None);

    // Cursor on 'test_something' - should NOT find a fixture
    assert_eq!(db.find_fixture_definition(&test_path, 1, 4), None);

    // Cursor on 'my_fixture' - SHOULD find the fixture
    let result = db.find_fixture_definition(&test_path, 1, 19);
    assert!(result.is_some());
    let def = result.unwrap();
    assert_eq!(def.name, "my_fixture");

    // Cursor on 'regular_param' - should NOT find a fixture (it's not a fixture)
    assert_eq!(db.find_fixture_definition(&test_path, 1, 31), None);

    // Cursor on comma or parenthesis - should NOT find a fixture
    assert_eq!(db.find_fixture_definition(&test_path, 1, 18), None); // '('
    assert_eq!(db.find_fixture_definition(&test_path, 1, 29), None); // ','
}

#[test]
#[timeout(30000)]
fn test_self_referencing_fixture() {
    let db = FixtureDatabase::new();

    // Set up a parent conftest.py with the original fixture
    let parent_conftest_content = r#"
import pytest

@pytest.fixture
def foo():
    return "parent"
"#;
    let parent_conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(parent_conftest_path.clone(), parent_conftest_content);

    // Set up a child directory conftest.py that overrides foo, referencing itself
    let child_conftest_content = r#"
import pytest

@pytest.fixture
def foo(foo):
    return foo + " child"
"#;
    let child_conftest_path = PathBuf::from("/tmp/test/subdir/conftest.py");
    db.analyze_file(child_conftest_path.clone(), child_conftest_content);

    // Now test go-to-definition on the parameter `foo` in the child fixture
    // Line 5 is "def foo(foo):" (1-indexed)
    // Character position 8 is the 'f' in the parameter name "foo"
    // LSP uses 0-indexed lines, so line 4 in LSP coordinates

    let result = db.find_fixture_definition(&child_conftest_path, 4, 8);

    assert!(
        result.is_some(),
        "Should find parent definition for self-referencing fixture"
    );
    let def = result.unwrap();
    assert_eq!(def.name, "foo");
    assert_eq!(
        def.file_path, parent_conftest_path,
        "Should resolve to parent conftest.py, not the child"
    );
    assert_eq!(def.line, 5, "Should point to line 5 of parent conftest.py");
}

#[test]
#[timeout(30000)]
fn test_fixture_overriding_same_file() {
    let db = FixtureDatabase::new();

    // A test file with multiple fixtures with the same name (unusual but valid)
    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "first"

@pytest.fixture
def my_fixture():
    return "second"

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

    // When there are multiple definitions in the same file, the later one should win
    // (Python's behavior - later definitions override earlier ones)

    // Test go-to-definition on the parameter in test_something
    // Line 12 is "def test_something(my_fixture):" (1-indexed)
    // Character position 19 is the 'm' in "my_fixture"
    // LSP uses 0-indexed lines, so line 11 in LSP coordinates

    let result = db.find_fixture_definition(&test_path, 11, 19);

    assert!(result.is_some(), "Should find fixture definition");
    let def = result.unwrap();
    assert_eq!(def.name, "my_fixture");
    assert_eq!(def.file_path, test_path);
    // The current implementation returns the first match in the same file
    // For true Python semantics, we'd want the last one, but that's a more complex change
    // For now, we just verify it finds *a* definition in the same file
}

#[test]
#[timeout(30000)]
fn test_fixture_overriding_conftest_hierarchy() {
    let db = FixtureDatabase::new();

    // Root conftest.py
    let root_conftest_content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "root"
"#;
    let root_conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(root_conftest_path.clone(), root_conftest_content);

    // Subdirectory conftest.py that overrides the fixture
    let sub_conftest_content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "subdir"
"#;
    let sub_conftest_path = PathBuf::from("/tmp/test/subdir/conftest.py");
    db.analyze_file(sub_conftest_path.clone(), sub_conftest_content);

    // Test file in subdirectory
    let test_content = r#"
def test_something(shared_fixture):
    assert shared_fixture == "subdir"
"#;
    let test_path = PathBuf::from("/tmp/test/subdir/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Go-to-definition from the test should find the closest conftest.py (subdir)
    // Line 2 is "def test_something(shared_fixture):" (1-indexed)
    // Character position 19 is the 's' in "shared_fixture"
    // LSP uses 0-indexed lines, so line 1 in LSP coordinates

    let result = db.find_fixture_definition(&test_path, 1, 19);

    assert!(result.is_some(), "Should find fixture definition");
    let def = result.unwrap();
    assert_eq!(def.name, "shared_fixture");
    assert_eq!(
        def.file_path, sub_conftest_path,
        "Should resolve to closest conftest.py"
    );

    // Now test from a file in the parent directory
    let parent_test_content = r#"
def test_parent(shared_fixture):
    assert shared_fixture == "root"
"#;
    let parent_test_path = PathBuf::from("/tmp/test/test_parent.py");
    db.analyze_file(parent_test_path.clone(), parent_test_content);

    let result = db.find_fixture_definition(&parent_test_path, 1, 16);

    assert!(result.is_some(), "Should find fixture definition");
    let def = result.unwrap();
    assert_eq!(def.name, "shared_fixture");
    assert_eq!(
        def.file_path, root_conftest_path,
        "Should resolve to root conftest.py"
    );
}

#[test]
#[timeout(30000)]
fn test_scoped_references() {
    let db = FixtureDatabase::new();

    // Set up a root conftest.py with a fixture
    let root_conftest_content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "root"
"#;
    let root_conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(root_conftest_path.clone(), root_conftest_content);

    // Set up subdirectory conftest.py that overrides the fixture
    let sub_conftest_content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "subdir"
"#;
    let sub_conftest_path = PathBuf::from("/tmp/test/subdir/conftest.py");
    db.analyze_file(sub_conftest_path.clone(), sub_conftest_content);

    // Test file in the root directory (uses root fixture)
    let root_test_content = r#"
def test_root(shared_fixture):
    assert shared_fixture == "root"
"#;
    let root_test_path = PathBuf::from("/tmp/test/test_root.py");
    db.analyze_file(root_test_path.clone(), root_test_content);

    // Test file in subdirectory (uses subdir fixture)
    let sub_test_content = r#"
def test_sub(shared_fixture):
    assert shared_fixture == "subdir"
"#;
    let sub_test_path = PathBuf::from("/tmp/test/subdir/test_sub.py");
    db.analyze_file(sub_test_path.clone(), sub_test_content);

    // Another test in subdirectory
    let sub_test2_content = r#"
def test_sub2(shared_fixture):
    assert shared_fixture == "subdir"
"#;
    let sub_test2_path = PathBuf::from("/tmp/test/subdir/test_sub2.py");
    db.analyze_file(sub_test2_path.clone(), sub_test2_content);

    // Get the root definition
    let root_definitions = db.definitions.get("shared_fixture").unwrap();
    let root_definition = root_definitions
        .iter()
        .find(|d| d.file_path == root_conftest_path)
        .unwrap();

    // Get the subdir definition
    let sub_definition = root_definitions
        .iter()
        .find(|d| d.file_path == sub_conftest_path)
        .unwrap();

    // Find references for the root definition
    let root_refs = db.find_references_for_definition(root_definition);

    // Should only include the test in the root directory
    assert_eq!(
        root_refs.len(),
        1,
        "Root definition should have 1 reference (from root test)"
    );
    assert_eq!(root_refs[0].file_path, root_test_path);

    // Find references for the subdir definition
    let sub_refs = db.find_references_for_definition(sub_definition);

    // Should include both tests in the subdirectory
    assert_eq!(
        sub_refs.len(),
        2,
        "Subdir definition should have 2 references (from subdir tests)"
    );

    let sub_ref_paths: Vec<_> = sub_refs.iter().map(|r| &r.file_path).collect();
    assert!(sub_ref_paths.contains(&&sub_test_path));
    assert!(sub_ref_paths.contains(&&sub_test2_path));

    // Verify that all references by name returns 3 total
    let all_refs = db.find_fixture_references("shared_fixture");
    assert_eq!(
        all_refs.len(),
        3,
        "Should find 3 total references across all scopes"
    );
}

#[test]
#[timeout(30000)]
fn test_multiline_parameters() {
    let db = FixtureDatabase::new();

    // Conftest with fixture
    let conftest_content = r#"
import pytest

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

    // Test file with multiline parameters
    let test_content = r#"
def test_xxx(
    foo,
):
    assert foo == 42
"#;
    let test_path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Line 3 (1-indexed) is "    foo," - the parameter line
    // In LSP coordinates, that's line 2 (0-indexed)
    // Character position 4 is the 'f' in 'foo'

    // Debug: Check what usages were recorded
    if let Some(usages) = db.usages.get(&test_path) {
        println!("Usages recorded:");
        for usage in usages.iter() {
            println!("  {} at line {} (1-indexed)", usage.name, usage.line);
        }
    } else {
        println!("No usages recorded for test file");
    }

    // The content has a leading newline, so:
    // Line 1: (empty)
    // Line 2: def test_xxx(
    // Line 3:     foo,
    // Line 4: ):
    // Line 5:     assert foo == 42

    // foo is at line 3 (1-indexed) = line 2 (0-indexed LSP)
    let result = db.find_fixture_definition(&test_path, 2, 4);

    assert!(
        result.is_some(),
        "Should find fixture definition when cursor is on parameter line"
    );
    let def = result.unwrap();
    assert_eq!(def.name, "foo");
}

#[test]
#[timeout(30000)]
fn test_find_references_from_usage() {
    let db = FixtureDatabase::new();

    // Simple fixture and usage in the same file
    let test_content = r#"
import pytest

@pytest.fixture
def foo(): ...


def test_xxx(foo):
    pass
"#;
    let test_path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Get the foo definition
    let foo_defs = db.definitions.get("foo").unwrap();
    assert_eq!(foo_defs.len(), 1, "Should have exactly one foo definition");
    let foo_def = &foo_defs[0];
    assert_eq!(foo_def.line, 5, "foo definition should be on line 5");

    // Get references for the definition
    let refs_from_def = db.find_references_for_definition(foo_def);
    println!("References from definition:");
    for r in &refs_from_def {
        println!("  {} at line {}", r.name, r.line);
    }

    assert_eq!(
        refs_from_def.len(),
        1,
        "Should find 1 usage reference (test_xxx parameter)"
    );
    assert_eq!(refs_from_def[0].line, 8, "Usage should be on line 8");

    // Now simulate what happens when user clicks on the usage (line 8, char 13 - the 'f' in 'foo')
    // This is LSP line 7 (0-indexed)
    let fixture_name = db.find_fixture_at_position(&test_path, 7, 13);
    println!(
        "\nfind_fixture_at_position(line 7, char 13): {:?}",
        fixture_name
    );

    assert_eq!(
        fixture_name,
        Some("foo".to_string()),
        "Should find fixture name at usage position"
    );

    let resolved_def = db.find_fixture_definition(&test_path, 7, 13);
    println!(
        "\nfind_fixture_definition(line 7, char 13): {:?}",
        resolved_def.as_ref().map(|d| (d.line, &d.file_path))
    );

    assert!(resolved_def.is_some(), "Should resolve usage to definition");
    assert_eq!(
        resolved_def.unwrap(),
        *foo_def,
        "Should resolve to the correct definition"
    );
}

#[test]
#[timeout(30000)]
fn test_find_references_with_ellipsis_body() {
    // This reproduces the structure from strawberry test_codegen.py
    let db = FixtureDatabase::new();

    let test_content = r#"@pytest.fixture
def foo(): ...


def test_xxx(foo):
    pass
"#;
    let test_path = PathBuf::from("/tmp/test/test_codegen.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check what line foo definition is on
    let foo_defs = db.definitions.get("foo");
    println!(
        "foo definitions: {:?}",
        foo_defs
            .as_ref()
            .map(|defs| defs.iter().map(|d| d.line).collect::<Vec<_>>())
    );

    // Check what line foo usage is on
    if let Some(usages) = db.usages.get(&test_path) {
        println!("usages:");
        for u in usages.iter() {
            println!("  {} at line {}", u.name, u.line);
        }
    }

    assert!(foo_defs.is_some(), "Should find foo definition");
    let foo_def = &foo_defs.unwrap()[0];

    // Get the usage line
    let usages = db.usages.get(&test_path).unwrap();
    let foo_usage = usages.iter().find(|u| u.name == "foo").unwrap();

    // Test from usage position (LSP coordinates are 0-indexed)
    let usage_lsp_line = (foo_usage.line - 1) as u32;
    println!("\nTesting from usage at LSP line {}", usage_lsp_line);

    let fixture_name = db.find_fixture_at_position(&test_path, usage_lsp_line, 13);
    assert_eq!(
        fixture_name,
        Some("foo".to_string()),
        "Should find foo at usage"
    );

    let def_from_usage = db.find_fixture_definition(&test_path, usage_lsp_line, 13);
    assert!(
        def_from_usage.is_some(),
        "Should resolve usage to definition"
    );
    assert_eq!(def_from_usage.unwrap(), *foo_def);
}

#[test]
#[timeout(30000)]
fn test_fixture_hierarchy_parent_references() {
    // Test that finding references from a parent fixture definition
    // includes child fixture definitions but NOT the child's usages
    let db = FixtureDatabase::new();

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

@pytest.fixture
def cli_runner():
    """Parent fixture"""
    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):
    """Child override that uses parent"""
    return cli_runner
"#;
    let child_conftest = PathBuf::from("/tmp/project/subdir/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

    // Test file in subdir using the 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/subdir/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Get parent definition
    let parent_defs = db.definitions.get("cli_runner").unwrap();
    let parent_def = parent_defs
        .iter()
        .find(|d| d.file_path == parent_conftest)
        .unwrap();

    println!(
        "\nParent definition: {:?}:{}",
        parent_def.file_path, parent_def.line
    );

    // Find references for parent definition
    let refs = db.find_references_for_definition(parent_def);

    println!("\nReferences for parent definition:");
    for r in &refs {
        println!("  {} at {:?}:{}", r.name, r.file_path, r.line);
    }

    // Parent references should include:
    // 1. The child fixture definition (line 5 in child conftest)
    // 2. The child's parameter that references the parent (line 5 in child conftest)
    // But NOT:
    // 3. test_one and test_two usages (they resolve to child, not parent)

    assert!(
        refs.len() <= 2,
        "Parent should have at most 2 references: child definition and its parameter, got {}",
        refs.len()
    );

    // Should include the child conftest
    let child_refs: Vec<_> = refs
        .iter()
        .filter(|r| r.file_path == child_conftest)
        .collect();
    assert!(
        !child_refs.is_empty(),
        "Parent references should include child fixture definition"
    );

    // Should NOT include test file usages
    let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();
    assert!(
        test_refs.is_empty(),
        "Parent references should NOT include child's test file usages"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_hierarchy_child_references() {
    // Test that finding references from a child fixture definition
    // includes usages in the same directory (that resolve to the child)
    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/subdir/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/subdir/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Get child definition
    let child_defs = db.definitions.get("cli_runner").unwrap();
    let child_def = child_defs
        .iter()
        .find(|d| d.file_path == child_conftest)
        .unwrap();

    println!(
        "\nChild definition: {:?}:{}",
        child_def.file_path, child_def.line
    );

    // Find references for child definition
    let refs = db.find_references_for_definition(child_def);

    println!("\nReferences for child definition:");
    for r in &refs {
        println!("  {} at {:?}:{}", r.name, r.file_path, r.line);
    }

    // Child references should include test_one and test_two
    assert!(
        refs.len() >= 2,
        "Child should have at least 2 references from test file, got {}",
        refs.len()
    );

    let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();
    assert_eq!(
        test_refs.len(),
        2,
        "Should have 2 references from test file"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_hierarchy_child_parameter_references() {
    // Test that finding references from a child fixture's parameter
    // (which references the parent) includes the child fixture definition
    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/subdir/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

    // When user clicks on the parameter "cli_runner" in the child definition,
    // it should resolve to the parent definition
    // Line 5 (1-indexed) = line 4 (0-indexed LSP), char 15 is in the parameter name
    let resolved_def = db.find_fixture_definition(&child_conftest, 4, 15);

    assert!(
        resolved_def.is_some(),
        "Child parameter should resolve to parent definition"
    );

    let def = resolved_def.unwrap();
    assert_eq!(
        def.file_path, parent_conftest,
        "Should resolve to parent conftest"
    );

    // Find references for parent definition
    let refs = db.find_references_for_definition(&def);

    println!("\nReferences for parent (from child parameter):");
    for r in &refs {
        println!("  {} at {:?}:{}", r.name, r.file_path, r.line);
    }

    // Should include the child fixture's parameter usage
    let child_refs: Vec<_> = refs
        .iter()
        .filter(|r| r.file_path == child_conftest)
        .collect();
    assert!(
        !child_refs.is_empty(),
        "Parent references should include child fixture parameter"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_hierarchy_usage_from_test() {
    // Test that finding references from a test function parameter
    // includes the definition it resolves to and other usages
    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/subdir/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

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

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

    assert!(
        resolved_def.is_some(),
        "Usage should resolve to child definition"
    );

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

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

    println!("\nReferences for child (from test usage):");
    for r in &refs {
        println!("  {} at {:?}:{}", r.name, r.file_path, r.line);
    }

    // Should include all three test usages
    let test_refs: Vec<_> = refs.iter().filter(|r| r.file_path == test_path).collect();
    assert_eq!(test_refs.len(), 3, "Should find all 3 usages in test file");
}

#[test]
#[timeout(30000)]
fn test_fixture_hierarchy_multiple_levels() {
    // Test a three-level hierarchy: grandparent -> parent -> child
    let db = FixtureDatabase::new();

    // Grandparent
    let grandparent_content = r#"
import pytest

@pytest.fixture
def db():
    return "grandparent_db"
"#;
    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/tests/conftest.py");
    db.analyze_file(child_conftest.clone(), child_content);

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

    // Get all definitions
    let all_defs = db.definitions.get("db").unwrap();
    assert_eq!(all_defs.len(), 3, "Should have 3 definitions");

    let grandparent_def = all_defs
        .iter()
        .find(|d| d.file_path == grandparent_conftest)
        .unwrap();
    let parent_def = all_defs
        .iter()
        .find(|d| d.file_path == parent_conftest)
        .unwrap();
    let child_def = all_defs
        .iter()
        .find(|d| d.file_path == child_conftest)
        .unwrap();

    // Test from test file - should resolve to child
    let resolved = db.find_fixture_definition(&test_path, 1, 12);
    assert_eq!(
        resolved.as_ref(),
        Some(child_def),
        "Test should use child definition"
    );

    // Child's references should include test file
    let child_refs = db.find_references_for_definition(child_def);
    let test_refs: Vec<_> = child_refs
        .iter()
        .filter(|r| r.file_path == test_path)
        .collect();
    assert!(
        !test_refs.is_empty(),
        "Child should have test file references"
    );

    // Parent's references should include child's parameter, but not test file
    let parent_refs = db.find_references_for_definition(parent_def);
    let child_param_refs: Vec<_> = parent_refs
        .iter()
        .filter(|r| r.file_path == child_conftest)
        .collect();
    let test_refs_in_parent: Vec<_> = parent_refs
        .iter()
        .filter(|r| r.file_path == test_path)
        .collect();

    assert!(
        !child_param_refs.is_empty(),
        "Parent should have child parameter reference"
    );
    assert!(
        test_refs_in_parent.is_empty(),
        "Parent should NOT have test file references"
    );

    // Grandparent's references should include parent's parameter, but not child's stuff
    let grandparent_refs = db.find_references_for_definition(grandparent_def);
    let parent_param_refs: Vec<_> = grandparent_refs
        .iter()
        .filter(|r| r.file_path == parent_conftest)
        .collect();
    let child_refs_in_gp: Vec<_> = grandparent_refs
        .iter()
        .filter(|r| r.file_path == child_conftest)
        .collect();

    assert!(
        !parent_param_refs.is_empty(),
        "Grandparent should have parent parameter reference"
    );
    assert!(
        child_refs_in_gp.is_empty(),
        "Grandparent should NOT have child references"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_hierarchy_same_file_override() {
    // Test that a fixture can be overridden in the same file
    // (less common but valid pytest pattern)
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def base():
    return "base"

@pytest.fixture
def base(base):
    return f"override_{base}"

def test_uses_override(base):
    pass
"#;
    let test_path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(test_path.clone(), content);

    let defs = db.definitions.get("base").unwrap();
    assert_eq!(defs.len(), 2, "Should have 2 definitions in same file");

    println!("\nDefinitions found:");
    for d in defs.iter() {
        println!("  base at line {}", d.line);
    }

    // Check usages
    if let Some(usages) = db.usages.get(&test_path) {
        println!("\nUsages found:");
        for u in usages.iter() {
            println!("  {} at line {}", u.name, u.line);
        }
    } else {
        println!("\nNo usages found!");
    }

    // The test should resolve to the second definition (override)
    // Line 12 (1-indexed) = line 11 (0-indexed LSP)
    // Character position: "def test_uses_override(base):" - 'b' is at position 23
    let resolved = db.find_fixture_definition(&test_path, 11, 23);

    println!("\nResolved: {:?}", resolved.as_ref().map(|d| d.line));

    assert!(resolved.is_some(), "Should resolve to override definition");

    // The second definition should be at line 9 (1-indexed)
    let override_def = defs.iter().find(|d| d.line == 9).unwrap();
    println!("Override def at line: {}", override_def.line);
    assert_eq!(resolved.as_ref(), Some(override_def));
}

#[test]
#[timeout(30000)]
fn test_cursor_position_on_definition_line() {
    // Debug test to understand what happens at different cursor positions
    // on a fixture definition line with a self-referencing parameter
    let db = FixtureDatabase::new();

    // Add a parent conftest with parent fixture
    let parent_content = r#"
import pytest

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

    let content = r#"
import pytest

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

    // Line 5 (1-indexed): "def cli_runner(cli_runner):"
    // Position 0: 'd' in def
    // Position 4: 'c' in cli_runner (function name)
    // Position 15: '('
    // Position 16: 'c' in cli_runner (parameter name)

    println!("\n=== Testing character positions on line 5 ===");

    // Check usages
    if let Some(usages) = db.usages.get(&test_path) {
        println!("\nUsages found:");
        for u in usages.iter() {
            println!(
                "  {} at line {}, chars {}-{}",
                u.name, u.line, u.start_char, u.end_char
            );
        }
    } else {
        println!("\nNo usages found!");
    }

    // Test clicking on function name 'cli_runner' - should be treated as definition
    let line_content = "def cli_runner(cli_runner):";
    println!("\nLine content: '{}'", line_content);

    // Position 4 = 'c' in function name cli_runner
    println!("\nPosition 4 (function name):");
    let word_at_4 = db.extract_word_at_position(line_content, 4);
    println!("  Word at cursor: {:?}", word_at_4);
    let fixture_name_at_4 = db.find_fixture_at_position(&test_path, 4, 4);
    println!("  find_fixture_at_position: {:?}", fixture_name_at_4);
    let resolved_4 = db.find_fixture_definition(&test_path, 4, 4); // Line 5 = index 4
    println!(
        "  Resolved: {:?}",
        resolved_4.as_ref().map(|d| (d.name.as_str(), d.line))
    );

    // Position 16 = 'c' in parameter name cli_runner
    println!("\nPosition 16 (parameter name):");
    let word_at_16 = db.extract_word_at_position(line_content, 16);
    println!("  Word at cursor: {:?}", word_at_16);

    // Manual check: does the usage check work?
    if let Some(usages) = db.usages.get(&test_path) {
        for usage in usages.iter() {
            println!("  Checking usage: {} at line {}", usage.name, usage.line);
            if usage.line == 5 && usage.name == "cli_runner" {
                println!("    MATCH! Usage matches our position");
            }
        }
    }

    let fixture_name_at_16 = db.find_fixture_at_position(&test_path, 4, 16);
    println!("  find_fixture_at_position: {:?}", fixture_name_at_16);
    let resolved_16 = db.find_fixture_definition(&test_path, 4, 16); // Line 5 = index 4
    println!(
        "  Resolved: {:?}",
        resolved_16.as_ref().map(|d| (d.name.as_str(), d.line))
    );

    // Expected behavior:
    // - Position 4 (function name): should resolve to CHILD (line 5) - we're ON the definition
    // - Position 16 (parameter): should resolve to PARENT (line 5 in conftest) - it's a usage

    assert_eq!(word_at_4, Some("cli_runner".to_string()));
    assert_eq!(word_at_16, Some("cli_runner".to_string()));

    // Check the actual resolution
    println!("\n=== ACTUAL vs EXPECTED ===");
    println!("Position 4 (function name):");
    println!(
        "  Actual: {:?}",
        resolved_4.as_ref().map(|d| (&d.file_path, d.line))
    );
    println!("  Expected: test file, line 5 (the child definition itself)");

    println!("\nPosition 16 (parameter):");
    println!(
        "  Actual: {:?}",
        resolved_16.as_ref().map(|d| (&d.file_path, d.line))
    );
    println!("  Expected: conftest, line 5 (the parent definition)");

    // The BUG: both return the same thing (child at line 5)
    // Position 4: returning child is CORRECT (though find_fixture_definition returns None,
    //             main.rs falls back to get_definition_at_line which is correct)
    // Position 16: returning child is WRONG - should return parent (line 5 in conftest)

    if let Some(ref def) = resolved_16 {
        assert_eq!(
            def.file_path, parent_conftest,
            "Parameter should resolve to parent definition"
        );
    } else {
        panic!("Position 16 (parameter) should resolve to parent definition");
    }
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_detection_in_test() {
    let db = FixtureDatabase::new();

    // Add a fixture definition in conftest
    let conftest_content = r#"
import pytest

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

    // Add a test that uses the fixture without declaring it
    let test_content = r#"
def test_example():
    result = my_fixture.get()
    assert result == 42
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that undeclared fixture was detected
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(undeclared.len(), 1, "Should detect one undeclared fixture");

    let fixture = &undeclared[0];
    assert_eq!(fixture.name, "my_fixture");
    assert_eq!(fixture.function_name, "test_example");
    assert_eq!(fixture.line, 3); // Line 3: "result = my_fixture.get()"
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_detection_in_fixture() {
    let db = FixtureDatabase::new();

    // Add fixture definitions in conftest
    let conftest_content = r#"
import pytest

@pytest.fixture
def base_fixture():
    return "base"

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

    // Add a fixture that uses another fixture without declaring it
    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture(base_fixture):
    data = helper_fixture.value
    return f"{base_fixture}-{data}"
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that undeclared fixture was detected
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(undeclared.len(), 1, "Should detect one undeclared fixture");

    let fixture = &undeclared[0];
    assert_eq!(fixture.name, "helper_fixture");
    assert_eq!(fixture.function_name, "my_fixture");
    assert_eq!(fixture.line, 6); // Line 6: "data = helper_fixture.value"
}

#[test]
#[timeout(30000)]
fn test_no_false_positive_for_declared_fixtures() {
    let db = FixtureDatabase::new();

    // Add a fixture definition in conftest
    let conftest_content = r#"
import pytest

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

    // Add a test that properly declares the fixture as a parameter
    let test_content = r#"
def test_example(my_fixture):
    result = my_fixture
    assert result == 42
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that no undeclared fixtures were detected
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect any undeclared fixtures"
    );
}

#[test]
#[timeout(30000)]
fn test_no_false_positive_for_non_fixtures() {
    let db = FixtureDatabase::new();

    // Add a test that uses regular variables (not fixtures)
    let test_content = r#"
def test_example():
    my_variable = 42
    result = my_variable + 10
    assert result == 52
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that no undeclared fixtures were detected
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect any undeclared fixtures"
    );
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_not_available_in_hierarchy() {
    let db = FixtureDatabase::new();

    // Add a fixture in a different directory (not in hierarchy)
    let other_conftest = r#"
import pytest

@pytest.fixture
def other_fixture():
    return "other"
"#;
    let other_path = PathBuf::from("/other/conftest.py");
    db.analyze_file(other_path.clone(), other_conftest);

    // Add a test that uses a name that happens to match a fixture but isn't available
    let test_content = r#"
def test_example():
    result = other_fixture.value
    assert result == "other"
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should not detect undeclared fixture because it's not in the hierarchy
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect fixtures not in hierarchy"
    );
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_async_test() {
    let db = FixtureDatabase::new();

    // Add fixture in same file
    let content = r#"
import pytest

@pytest.fixture
def http_client():
    return "MockClient"

async def test_with_undeclared():
    response = await http_client.query("test")
    assert response == "test"
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), content);

    // Check that undeclared fixture was detected
    let undeclared = db.get_undeclared_fixtures(&test_path);

    println!("Found {} undeclared fixtures", undeclared.len());
    for u in &undeclared {
        println!("  - {} at line {} in {}", u.name, u.line, u.function_name);
    }

    assert_eq!(undeclared.len(), 1, "Should detect one undeclared fixture");
    assert_eq!(undeclared[0].name, "http_client");
    assert_eq!(undeclared[0].function_name, "test_with_undeclared");
    assert_eq!(undeclared[0].line, 9);
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_assert_statement() {
    let db = FixtureDatabase::new();

    // Add fixture in conftest
    let conftest_content = r#"
import pytest

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

    // Test file that uses fixture in assert without declaring it
    let test_content = r#"
def test_assertion():
    result = calculate_value()
    assert result == expected_value
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that undeclared fixture was detected in assert
    let undeclared = db.get_undeclared_fixtures(&test_path);

    assert_eq!(
        undeclared.len(),
        1,
        "Should detect one undeclared fixture in assert"
    );
    assert_eq!(undeclared[0].name, "expected_value");
    assert_eq!(undeclared[0].function_name, "test_assertion");
}

#[test]
#[timeout(30000)]
fn test_no_false_positive_for_local_variable() {
    // Problem 2: Should not warn if a local variable shadows a fixture name
    let db = FixtureDatabase::new();

    // Add fixture in conftest
    let conftest_content = r#"
import pytest

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

    // Test file that has a local variable with the same name
    let test_content = r#"
def test_with_local_variable():
    foo = "local variable"
    result = foo.upper()
    assert result == "LOCAL VARIABLE"
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should NOT detect undeclared fixture because foo is a local variable
    let undeclared = db.get_undeclared_fixtures(&test_path);

    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect undeclared fixture when name is a local variable"
    );
}

#[test]
#[timeout(30000)]
fn test_no_false_positive_for_imported_name() {
    // Problem 2: Should not warn if an imported name shadows a fixture name
    let db = FixtureDatabase::new();

    // Add fixture in conftest
    let conftest_content = r#"
import pytest

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

    // Test file that imports a name
    let test_content = r#"
from mymodule import foo

def test_with_import():
    result = foo.something()
    assert result == "value"
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should NOT detect undeclared fixture because foo is imported
    let undeclared = db.get_undeclared_fixtures(&test_path);

    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect undeclared fixture when name is imported"
    );
}

#[test]
#[timeout(30000)]
fn test_warn_for_fixture_used_directly() {
    // Problem 2: SHOULD warn if trying to use a fixture defined in the same file
    // This is an error because fixtures must be accessed through parameters
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def foo():
    return "fixture"

def test_using_fixture_directly():
    # This is an error - fixtures must be declared as parameters
    result = foo.something()
    assert result == "value"
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // SHOULD detect undeclared fixture even though foo is defined in same file
    let undeclared = db.get_undeclared_fixtures(&test_path);

    assert_eq!(
        undeclared.len(),
        1,
        "Should detect fixture used directly without parameter declaration"
    );
    assert_eq!(undeclared[0].name, "foo");
    assert_eq!(undeclared[0].function_name, "test_using_fixture_directly");
}

#[test]
#[timeout(30000)]
fn test_no_false_positive_for_module_level_assignment() {
    // Should not warn if name is assigned at module level (not a fixture)
    let db = FixtureDatabase::new();

    // Add fixture in conftest
    let conftest_content = r#"
import pytest

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

    // Test file that has a module-level assignment
    let test_content = r#"
# Module-level assignment
foo = SomeClass()

def test_with_module_var():
    result = foo.method()
    assert result == "value"
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should NOT detect undeclared fixture because foo is assigned at module level
    let undeclared = db.get_undeclared_fixtures(&test_path);

    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect undeclared fixture when name is assigned at module level"
    );
}

#[test]
#[timeout(30000)]
fn test_no_false_positive_for_function_definition() {
    // Should not warn if name is a regular function (not a fixture)
    let db = FixtureDatabase::new();

    // Add fixture in conftest
    let conftest_content = r#"
import pytest

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

    // Test file that has a regular function with the same name
    let test_content = r#"
def foo():
    return "not a fixture"

def test_with_function():
    result = foo()
    assert result == "not a fixture"
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should NOT detect undeclared fixture because foo is a regular function
    let undeclared = db.get_undeclared_fixtures(&test_path);

    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect undeclared fixture when name is a regular function"
    );
}

#[test]
#[timeout(30000)]
fn test_no_false_positive_for_class_definition() {
    // Should not warn if name is a class
    let db = FixtureDatabase::new();

    // Add fixture in conftest
    let conftest_content = r#"
import pytest

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

    // Test file that has a class with the same name
    let test_content = r#"
class MyClass:
    pass

def test_with_class():
    obj = MyClass()
    assert obj is not None
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should NOT detect undeclared fixture because MyClass is a class
    let undeclared = db.get_undeclared_fixtures(&test_path);

    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect undeclared fixture when name is a class"
    );
}

#[test]
#[timeout(30000)]
fn test_line_aware_local_variable_scope() {
    // Test that local variables are only considered "in scope" AFTER they're assigned
    let db = FixtureDatabase::new();

    // Conftest with http_client fixture
    let conftest_content = r#"
import pytest

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

    // Test file that uses http_client before and after a local assignment
    let test_content = r#"async def test_example():
    # Line 1: http_client should be flagged (not yet assigned)
    result = await http_client.get("/api")
    # Line 3: Now we assign http_client locally
    http_client = "local"
    # Line 5: http_client should NOT be flagged (local var now)
    result2 = await http_client.get("/api2")
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check for undeclared fixtures
    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Should only detect http_client on line 3 (usage before assignment)
    // NOT on line 7 (after assignment on line 5)
    assert_eq!(
        undeclared.len(),
        1,
        "Should detect http_client only before local assignment"
    );
    assert_eq!(undeclared[0].name, "http_client");
    // Line numbers: 1=def, 2=comment, 3=result (first usage), 4=comment, 5=assignment, 6=comment, 7=result2
    assert_eq!(
        undeclared[0].line, 3,
        "Should flag usage on line 3 (before assignment on line 5)"
    );
}

#[test]
#[timeout(30000)]
fn test_same_line_assignment_and_usage() {
    // Test that usage on the same line as assignment refers to the fixture
    let db = FixtureDatabase::new();

    let conftest_content = r#"import pytest

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

    let test_content = r#"async def test_example():
    # This references the fixture on the RHS, then assigns to local var
    http_client = await http_client.get("/api")
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Should detect http_client on RHS (line 3) because assignment hasn't happened yet
    assert_eq!(undeclared.len(), 1);
    assert_eq!(undeclared[0].name, "http_client");
    assert_eq!(undeclared[0].line, 3);
}

#[test]
#[timeout(30000)]
fn test_no_false_positive_for_later_assignment() {
    // This is the actual bug we fixed - make sure local assignment later in function
    // doesn't prevent detection of undeclared fixture usage BEFORE the assignment
    let db = FixtureDatabase::new();

    let conftest_content = r#"import pytest

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

    // This was the original issue: http_client used on line 2, but assigned on line 4
    // Old code would see the assignment and not flag line 2
    let test_content = r#"async def test_example():
    result = await http_client.get("/api")  # Should be flagged
    # Now assign locally
    http_client = "local"
    # This should NOT be flagged because variable is now assigned
    result2 = http_client
"#;
    let test_path = PathBuf::from("/tmp/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Should only detect one undeclared usage (line 2)
    assert_eq!(
        undeclared.len(),
        1,
        "Should detect exactly one undeclared fixture"
    );
    assert_eq!(undeclared[0].name, "http_client");
    assert_eq!(
        undeclared[0].line, 2,
        "Should flag usage on line 2 before assignment on line 4"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_resolution_priority_deterministic() {
    // Test that fixture resolution is deterministic and follows priority rules
    // This test ensures we don't randomly pick a definition from DashMap iteration
    let db = FixtureDatabase::new();

    // Create multiple conftest.py files with the same fixture name in different locations
    // Scenario: /tmp/project/app/tests/test_foo.py should resolve to closest conftest

    // Root conftest
    let root_content = r#"
import pytest

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

    // Unrelated conftest (different branch of directory tree)
    let unrelated_content = r#"
import pytest

@pytest.fixture
def db():
    return "unrelated_db"
"#;
    let unrelated_conftest = PathBuf::from("/tmp/other/conftest.py");
    db.analyze_file(unrelated_conftest.clone(), unrelated_content);

    // App-level conftest
    let app_content = r#"
import pytest

@pytest.fixture
def db():
    return "app_db"
"#;
    let app_conftest = PathBuf::from("/tmp/project/app/conftest.py");
    db.analyze_file(app_conftest.clone(), app_content);

    // Tests-level conftest (closest)
    let tests_content = r#"
import pytest

@pytest.fixture
def db():
    return "tests_db"
"#;
    let tests_conftest = PathBuf::from("/tmp/project/app/tests/conftest.py");
    db.analyze_file(tests_conftest.clone(), tests_content);

    // Test file
    let test_content = r#"
def test_database(db):
    assert db is not None
"#;
    let test_path = PathBuf::from("/tmp/project/app/tests/test_foo.py");
    db.analyze_file(test_path.clone(), test_content);

    // Run the resolution multiple times to ensure it's deterministic
    for iteration in 0..10 {
        let result = db.find_fixture_definition(&test_path, 1, 18); // Line 2, column 18 = "db" parameter

        assert!(
            result.is_some(),
            "Iteration {}: Should find a fixture definition",
            iteration
        );

        let def = result.unwrap();
        assert_eq!(
            def.name, "db",
            "Iteration {}: Should find 'db' fixture",
            iteration
        );

        // Should ALWAYS resolve to the closest conftest.py (tests_conftest)
        assert_eq!(
            def.file_path, tests_conftest,
            "Iteration {}: Should consistently resolve to closest conftest.py at {:?}, but got {:?}",
            iteration,
            tests_conftest,
            def.file_path
        );
    }
}

#[test]
#[timeout(30000)]
fn test_fixture_resolution_prefers_parent_over_unrelated() {
    // Test that when no fixture is in same file or conftest hierarchy,
    // we prefer third-party fixtures (site-packages) over random unrelated conftest files
    let db = FixtureDatabase::new();

    // Unrelated conftest in different directory tree
    let unrelated_content = r#"
import pytest

@pytest.fixture
def custom_fixture():
    return "unrelated"
"#;
    let unrelated_conftest = PathBuf::from("/tmp/other_project/conftest.py");
    db.analyze_file(unrelated_conftest.clone(), unrelated_content);

    // Third-party fixture (mock in site-packages)
    let third_party_content = r#"
import pytest

@pytest.fixture
def custom_fixture():
    return "third_party"
"#;
    let third_party_path =
        PathBuf::from("/tmp/.venv/lib/python3.11/site-packages/pytest_custom/plugin.py");
    db.analyze_file(third_party_path.clone(), third_party_content);

    // Test file in a different project
    let test_content = r#"
def test_custom(custom_fixture):
    assert custom_fixture is not None
"#;
    let test_path = PathBuf::from("/tmp/my_project/test_foo.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should prefer third-party fixture over unrelated conftest
    let result = db.find_fixture_definition(&test_path, 1, 16);
    assert!(result.is_some());
    let def = result.unwrap();

    // Should be the third-party fixture (site-packages)
    assert_eq!(
        def.file_path, third_party_path,
        "Should prefer third-party fixture from site-packages over unrelated conftest.py"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_resolution_hierarchy_over_third_party() {
    // Test that fixtures in the conftest hierarchy are preferred over third-party
    let db = FixtureDatabase::new();

    // Third-party fixture
    let third_party_content = r#"
import pytest

@pytest.fixture
def mocker():
    return "third_party_mocker"
"#;
    let third_party_path =
        PathBuf::from("/tmp/project/.venv/lib/python3.11/site-packages/pytest_mock/plugin.py");
    db.analyze_file(third_party_path.clone(), third_party_content);

    // Local conftest.py that overrides mocker
    let local_content = r#"
import pytest

@pytest.fixture
def mocker():
    return "local_mocker"
"#;
    let local_conftest = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(local_conftest.clone(), local_content);

    // Test file
    let test_content = r#"
def test_mocking(mocker):
    assert mocker is not None
"#;
    let test_path = PathBuf::from("/tmp/project/test_foo.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should prefer local conftest over third-party
    let result = db.find_fixture_definition(&test_path, 1, 17);
    assert!(result.is_some());
    let def = result.unwrap();

    assert_eq!(
        def.file_path, local_conftest,
        "Should prefer local conftest.py fixture over third-party fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_resolution_with_relative_paths() {
    // Test that fixture resolution works even when paths are stored with different representations
    // This simulates the case where analyze_file is called with relative paths vs absolute paths
    let db = FixtureDatabase::new();

    // Conftest with absolute path
    let conftest_content = r#"
import pytest

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

    // Test file also with absolute path
    let test_content = r#"
def test_example(shared):
    assert shared == "conftest"
"#;
    let test_abs = PathBuf::from("/tmp/project/tests/test_foo.py");
    db.analyze_file(test_abs.clone(), test_content);

    // Should find the fixture from conftest
    let result = db.find_fixture_definition(&test_abs, 1, 17);
    assert!(result.is_some(), "Should find fixture with absolute paths");
    let def = result.unwrap();
    assert_eq!(def.file_path, conftest_abs, "Should resolve to conftest.py");
}

#[test]
#[timeout(30000)]
fn test_fixture_resolution_deep_hierarchy() {
    // Test resolution in a deep directory hierarchy to ensure path traversal works correctly
    let db = FixtureDatabase::new();

    // Root level fixture
    let root_content = r#"
import pytest

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

    // Level 1
    let level1_content = r#"
import pytest

@pytest.fixture
def db():
    return "level1"
"#;
    let level1_conftest = PathBuf::from("/tmp/project/src/conftest.py");
    db.analyze_file(level1_conftest.clone(), level1_content);

    // Level 2
    let level2_content = r#"
import pytest

@pytest.fixture
def db():
    return "level2"
"#;
    let level2_conftest = PathBuf::from("/tmp/project/src/app/conftest.py");
    db.analyze_file(level2_conftest.clone(), level2_content);

    // Level 3 - deepest
    let level3_content = r#"
import pytest

@pytest.fixture
def db():
    return "level3"
"#;
    let level3_conftest = PathBuf::from("/tmp/project/src/app/tests/conftest.py");
    db.analyze_file(level3_conftest.clone(), level3_content);

    // Test at level 3 - should use level 3 fixture
    let test_l3_content = r#"
def test_db(db):
    assert db == "level3"
"#;
    let test_l3 = PathBuf::from("/tmp/project/src/app/tests/test_foo.py");
    db.analyze_file(test_l3.clone(), test_l3_content);

    let result_l3 = db.find_fixture_definition(&test_l3, 1, 12);
    assert!(result_l3.is_some());
    assert_eq!(
        result_l3.unwrap().file_path,
        level3_conftest,
        "Test at level 3 should use level 3 fixture"
    );

    // Test at level 2 - should use level 2 fixture
    let test_l2_content = r#"
def test_db(db):
    assert db == "level2"
"#;
    let test_l2 = PathBuf::from("/tmp/project/src/app/test_bar.py");
    db.analyze_file(test_l2.clone(), test_l2_content);

    let result_l2 = db.find_fixture_definition(&test_l2, 1, 12);
    assert!(result_l2.is_some());
    assert_eq!(
        result_l2.unwrap().file_path,
        level2_conftest,
        "Test at level 2 should use level 2 fixture"
    );

    // Test at level 1 - should use level 1 fixture
    let test_l1_content = r#"
def test_db(db):
    assert db == "level1"
"#;
    let test_l1 = PathBuf::from("/tmp/project/src/test_baz.py");
    db.analyze_file(test_l1.clone(), test_l1_content);

    let result_l1 = db.find_fixture_definition(&test_l1, 1, 12);
    assert!(result_l1.is_some());
    assert_eq!(
        result_l1.unwrap().file_path,
        level1_conftest,
        "Test at level 1 should use level 1 fixture"
    );

    // Test at root - should use root fixture
    let test_root_content = r#"
def test_db(db):
    assert db == "root"
"#;
    let test_root = PathBuf::from("/tmp/project/test_root.py");
    db.analyze_file(test_root.clone(), test_root_content);

    let result_root = db.find_fixture_definition(&test_root, 1, 12);
    assert!(result_root.is_some());
    assert_eq!(
        result_root.unwrap().file_path,
        root_conftest,
        "Test at root should use root fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_resolution_sibling_directories() {
    // Test that fixtures in sibling directories don't leak into each other
    let db = FixtureDatabase::new();

    // Root conftest
    let root_content = r#"
import pytest

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

    // Module A with its own fixture
    let module_a_content = r#"
import pytest

@pytest.fixture
def module_specific():
    return "module_a"
"#;
    let module_a_conftest = PathBuf::from("/tmp/project/module_a/conftest.py");
    db.analyze_file(module_a_conftest.clone(), module_a_content);

    // Module B with its own fixture (same name!)
    let module_b_content = r#"
import pytest

@pytest.fixture
def module_specific():
    return "module_b"
"#;
    let module_b_conftest = PathBuf::from("/tmp/project/module_b/conftest.py");
    db.analyze_file(module_b_conftest.clone(), module_b_content);

    // Test in module A - should use module A's fixture
    let test_a_content = r#"
def test_a(module_specific, shared):
    assert module_specific == "module_a"
    assert shared == "root"
"#;
    let test_a = PathBuf::from("/tmp/project/module_a/test_a.py");
    db.analyze_file(test_a.clone(), test_a_content);

    let result_a = db.find_fixture_definition(&test_a, 1, 11);
    assert!(result_a.is_some());
    assert_eq!(
        result_a.unwrap().file_path,
        module_a_conftest,
        "Test in module_a should use module_a's fixture"
    );

    // Test in module B - should use module B's fixture
    let test_b_content = r#"
def test_b(module_specific, shared):
    assert module_specific == "module_b"
    assert shared == "root"
"#;
    let test_b = PathBuf::from("/tmp/project/module_b/test_b.py");
    db.analyze_file(test_b.clone(), test_b_content);

    let result_b = db.find_fixture_definition(&test_b, 1, 11);
    assert!(result_b.is_some());
    assert_eq!(
        result_b.unwrap().file_path,
        module_b_conftest,
        "Test in module_b should use module_b's fixture"
    );

    // Both should be able to access shared root fixture
    // "shared" starts at column 29 (after "module_specific, ")
    let result_a_shared = db.find_fixture_definition(&test_a, 1, 29);
    assert!(result_a_shared.is_some());
    assert_eq!(
        result_a_shared.unwrap().file_path,
        root_conftest,
        "Test in module_a should access root's shared fixture"
    );

    let result_b_shared = db.find_fixture_definition(&test_b, 1, 29);
    assert!(result_b_shared.is_some());
    assert_eq!(
        result_b_shared.unwrap().file_path,
        root_conftest,
        "Test in module_b should access root's shared fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_resolution_multiple_unrelated_branches_is_deterministic() {
    // Issue #23 fix: When a fixture is defined in multiple unrelated branches,
    // and a test file is NOT in any of their hierarchies, the fixture should NOT
    // be accessible (returns None, not a random choice).
    let db = FixtureDatabase::new();

    // Three unrelated project branches - each has their own conftest.py
    let branch_a_content = r#"
import pytest

@pytest.fixture
def common_fixture():
    return "branch_a"
"#;
    let branch_a_conftest = PathBuf::from("/tmp/projects/project_a/conftest.py");
    db.analyze_file(branch_a_conftest.clone(), branch_a_content);

    let branch_b_content = r#"
import pytest

@pytest.fixture
def common_fixture():
    return "branch_b"
"#;
    let branch_b_conftest = PathBuf::from("/tmp/projects/project_b/conftest.py");
    db.analyze_file(branch_b_conftest.clone(), branch_b_content);

    let branch_c_content = r#"
import pytest

@pytest.fixture
def common_fixture():
    return "branch_c"
"#;
    let branch_c_conftest = PathBuf::from("/tmp/projects/project_c/conftest.py");
    db.analyze_file(branch_c_conftest.clone(), branch_c_content);

    // Test in yet another unrelated location - NOT in any project's hierarchy
    let test_content = r#"
def test_something(common_fixture):
    assert common_fixture is not None
"#;
    let test_path = PathBuf::from("/tmp/unrelated/test_foo.py");
    db.analyze_file(test_path.clone(), test_content);

    // The fixture is NOT accessible from this location because:
    // 1. It's not in the same file
    // 2. None of the conftest.py files are in parent directories of test_path
    // 3. It's not in site-packages
    let result = db.find_fixture_definition(&test_path, 1, 19);
    assert!(
        result.is_none(),
        "Fixture should NOT be found - test file is not in any conftest hierarchy"
    );

    // However, a test WITHIN project_a should find project_a's fixture
    let test_in_a_content = r#"
def test_in_project_a(common_fixture):
    pass
"#;
    let test_in_a_path = PathBuf::from("/tmp/projects/project_a/test_example.py");
    db.analyze_file(test_in_a_path.clone(), test_in_a_content);

    let result_in_a = db.find_fixture_definition(&test_in_a_path, 1, 22);
    assert!(
        result_in_a.is_some(),
        "Fixture should be found in project_a"
    );
    assert_eq!(
        result_in_a.unwrap().file_path,
        branch_a_conftest,
        "Should resolve to project_a's conftest.py"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_resolution_conftest_at_various_depths() {
    // Test that conftest.py files at different depths are correctly prioritized
    let db = FixtureDatabase::new();

    // Deep conftest
    let deep_content = r#"
import pytest

@pytest.fixture
def fixture_a():
    return "deep"

@pytest.fixture
def fixture_b():
    return "deep"
"#;
    let deep_conftest = PathBuf::from("/tmp/project/src/module/tests/integration/conftest.py");
    db.analyze_file(deep_conftest.clone(), deep_content);

    // Mid-level conftest - overrides fixture_a but not fixture_b
    let mid_content = r#"
import pytest

@pytest.fixture
def fixture_a():
    return "mid"
"#;
    let mid_conftest = PathBuf::from("/tmp/project/src/module/conftest.py");
    db.analyze_file(mid_conftest.clone(), mid_content);

    // Root conftest - defines fixture_c
    let root_content = r#"
import pytest

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

    // Test in deep directory
    let test_content = r#"
def test_all(fixture_a, fixture_b, fixture_c):
    assert fixture_a == "deep"
    assert fixture_b == "deep"
    assert fixture_c == "root"
"#;
    let test_path = PathBuf::from("/tmp/project/src/module/tests/integration/test_foo.py");
    db.analyze_file(test_path.clone(), test_content);

    // fixture_a: should resolve to deep (closest)
    let result_a = db.find_fixture_definition(&test_path, 1, 13);
    assert!(result_a.is_some());
    assert_eq!(
        result_a.unwrap().file_path,
        deep_conftest,
        "fixture_a should resolve to closest conftest (deep)"
    );

    // fixture_b: should resolve to deep (only defined there)
    let result_b = db.find_fixture_definition(&test_path, 1, 24);
    assert!(result_b.is_some());
    assert_eq!(
        result_b.unwrap().file_path,
        deep_conftest,
        "fixture_b should resolve to deep conftest"
    );

    // fixture_c: should resolve to root (only defined there)
    let result_c = db.find_fixture_definition(&test_path, 1, 35);
    assert!(result_c.is_some());
    assert_eq!(
        result_c.unwrap().file_path,
        root_conftest,
        "fixture_c should resolve to root conftest"
    );

    // Test in mid-level directory (one level up)
    let test_mid_content = r#"
def test_mid(fixture_a, fixture_c):
    assert fixture_a == "mid"
    assert fixture_c == "root"
"#;
    let test_mid_path = PathBuf::from("/tmp/project/src/module/test_bar.py");
    db.analyze_file(test_mid_path.clone(), test_mid_content);

    // fixture_a from mid-level: should resolve to mid conftest
    let result_a_mid = db.find_fixture_definition(&test_mid_path, 1, 13);
    assert!(result_a_mid.is_some());
    assert_eq!(
        result_a_mid.unwrap().file_path,
        mid_conftest,
        "fixture_a from mid-level test should resolve to mid conftest"
    );
}

#[test]
#[timeout(30000)]
fn test_get_available_fixtures_same_file() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def fixture_a():
    return "a"

@pytest.fixture
def fixture_b():
    return "b"

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

    let available = db.get_available_fixtures(&test_path);

    assert_eq!(available.len(), 2, "Should find 2 fixtures in same file");

    let names: Vec<_> = available.iter().map(|f| f.name.as_str()).collect();
    assert!(names.contains(&"fixture_a"));
    assert!(names.contains(&"fixture_b"));
}

#[test]
#[timeout(30000)]
fn test_get_available_fixtures_conftest_hierarchy() {
    let db = FixtureDatabase::new();

    // Root conftest
    let root_conftest = r#"
import pytest

@pytest.fixture
def root_fixture():
    return "root"
"#;
    let root_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(root_path.clone(), root_conftest);

    // Subdir conftest
    let sub_conftest = r#"
import pytest

@pytest.fixture
def sub_fixture():
    return "sub"
"#;
    let sub_path = PathBuf::from("/tmp/test/subdir/conftest.py");
    db.analyze_file(sub_path.clone(), sub_conftest);

    // Test file in subdir
    let test_content = r#"
def test_something():
    pass
"#;
    let test_path = PathBuf::from("/tmp/test/subdir/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    let available = db.get_available_fixtures(&test_path);

    assert_eq!(
        available.len(),
        2,
        "Should find fixtures from both conftest files"
    );

    let names: Vec<_> = available.iter().map(|f| f.name.as_str()).collect();
    assert!(names.contains(&"root_fixture"));
    assert!(names.contains(&"sub_fixture"));
}

#[test]
#[timeout(30000)]
fn test_get_available_fixtures_no_duplicates() {
    let db = FixtureDatabase::new();

    // Root conftest
    let root_conftest = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "root"
"#;
    let root_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(root_path.clone(), root_conftest);

    // Subdir conftest with same fixture name
    let sub_conftest = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "sub"
"#;
    let sub_path = PathBuf::from("/tmp/test/subdir/conftest.py");
    db.analyze_file(sub_path.clone(), sub_conftest);

    // Test file in subdir
    let test_content = r#"
def test_something():
    pass
"#;
    let test_path = PathBuf::from("/tmp/test/subdir/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    let available = db.get_available_fixtures(&test_path);

    // Should only find one "shared_fixture" (the closest one)
    let shared_count = available
        .iter()
        .filter(|f| f.name == "shared_fixture")
        .count();
    assert_eq!(shared_count, 1, "Should only include shared_fixture once");

    // The one included should be from the subdir (closest)
    let shared_fixture = available
        .iter()
        .find(|f| f.name == "shared_fixture")
        .unwrap();
    assert_eq!(shared_fixture.file_path, sub_path);
}

#[test]
#[timeout(30000)]
fn test_is_inside_function_in_test() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

def test_example(fixture_a, fixture_b):
    result = fixture_a + fixture_b
    assert result == "ab"
"#;
    let test_path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Test on the function definition line (line 4, 0-indexed line 3)
    let result = db.is_inside_function(&test_path, 3, 10);
    assert!(result.is_some());

    let (func_name, is_fixture, params) = result.unwrap();
    assert_eq!(func_name, "test_example");
    assert!(!is_fixture);
    assert_eq!(params, vec!["fixture_a", "fixture_b"]);

    // Test inside the function body (line 5, 0-indexed line 4)
    let result = db.is_inside_function(&test_path, 4, 10);
    assert!(result.is_some());

    let (func_name, is_fixture, _) = result.unwrap();
    assert_eq!(func_name, "test_example");
    assert!(!is_fixture);
}

#[test]
#[timeout(30000)]
fn test_is_inside_function_in_fixture() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture(other_fixture):
    return other_fixture + "_modified"
"#;
    let test_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(test_path.clone(), test_content);

    // Test on the function definition line (line 5, 0-indexed line 4)
    let result = db.is_inside_function(&test_path, 4, 10);
    assert!(result.is_some());

    let (func_name, is_fixture, params) = result.unwrap();
    assert_eq!(func_name, "my_fixture");
    assert!(is_fixture);
    assert_eq!(params, vec!["other_fixture"]);

    // Test inside the function body (line 6, 0-indexed line 5)
    let result = db.is_inside_function(&test_path, 5, 10);
    assert!(result.is_some());

    let (func_name, is_fixture, _) = result.unwrap();
    assert_eq!(func_name, "my_fixture");
    assert!(is_fixture);
}

#[test]
#[timeout(30000)]
fn test_is_inside_function_outside() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "value"

def test_example(my_fixture):
    assert my_fixture == "value"

# This is a comment outside any function
"#;
    let test_path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Test on the import line (line 1, 0-indexed line 0)
    let result = db.is_inside_function(&test_path, 0, 0);
    assert!(
        result.is_none(),
        "Should not be inside a function on import line"
    );

    // Test on the comment line (line 10, 0-indexed line 9)
    let result = db.is_inside_function(&test_path, 9, 0);
    assert!(
        result.is_none(),
        "Should not be inside a function on comment line"
    );
}

#[test]
#[timeout(30000)]
fn test_is_inside_function_non_test() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

def helper_function():
    return "helper"

def test_example():
    result = helper_function()
    assert result == "helper"
"#;
    let test_path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Test inside helper_function (not a test or fixture)
    let result = db.is_inside_function(&test_path, 3, 10);
    assert!(
        result.is_none(),
        "Should not return non-test, non-fixture functions"
    );

    // Test inside test_example (is a test)
    let result = db.is_inside_function(&test_path, 6, 10);
    assert!(result.is_some(), "Should return test functions");

    let (func_name, is_fixture, _) = result.unwrap();
    assert_eq!(func_name, "test_example");
    assert!(!is_fixture);
}

#[test]
#[timeout(30000)]
fn test_is_inside_async_function() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
async def async_fixture():
    return "async_value"

async def test_async_example(async_fixture):
    assert async_fixture == "async_value"
"#;
    let test_path = PathBuf::from("/tmp/test/test_async.py");
    db.analyze_file(test_path.clone(), test_content);

    // Test inside async fixture (line 5, 0-indexed line 4)
    let result = db.is_inside_function(&test_path, 4, 10);
    assert!(result.is_some());

    let (func_name, is_fixture, _) = result.unwrap();
    assert_eq!(func_name, "async_fixture");
    assert!(is_fixture);

    // Test inside async test (line 8, 0-indexed line 7)
    let result = db.is_inside_function(&test_path, 7, 10);
    assert!(result.is_some());

    let (func_name, is_fixture, params) = result.unwrap();
    assert_eq!(func_name, "test_async_example");
    assert!(!is_fixture);
    assert_eq!(params, vec!["async_fixture"]);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_simple_return_type() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def string_fixture() -> str:
    return "hello"
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    let fixtures = db.definitions.get("string_fixture").unwrap();
    assert_eq!(fixtures.len(), 1);
    assert_eq!(fixtures[0].return_type, Some("str".to_string()));
}

#[test]
#[timeout(30000)]
fn test_fixture_with_generator_return_type() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from typing import Generator

@pytest.fixture
def generator_fixture() -> Generator[str, None, None]:
    yield "value"
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    let fixtures = db.definitions.get("generator_fixture").unwrap();
    assert_eq!(fixtures.len(), 1);
    // Should extract the yielded type (str) from Generator[str, None, None]
    assert_eq!(fixtures[0].return_type, Some("str".to_string()));
}

#[test]
#[timeout(30000)]
fn test_fixture_with_iterator_return_type() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from typing import Iterator

@pytest.fixture
def iterator_fixture() -> Iterator[int]:
    yield 42
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    let fixtures = db.definitions.get("iterator_fixture").unwrap();
    assert_eq!(fixtures.len(), 1);
    // Should extract the yielded type (int) from Iterator[int]
    assert_eq!(fixtures[0].return_type, Some("int".to_string()));
}

#[test]
#[timeout(30000)]
fn test_fixture_without_return_type() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def no_type_fixture():
    return 123
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    let fixtures = db.definitions.get("no_type_fixture").unwrap();
    assert_eq!(fixtures.len(), 1);
    assert_eq!(fixtures[0].return_type, None);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_union_return_type() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def union_fixture() -> str | int:
    return "string"
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    let fixtures = db.definitions.get("union_fixture").unwrap();
    assert_eq!(fixtures.len(), 1);
    assert_eq!(fixtures[0].return_type, Some("str | int".to_string()));
}

// ============================================================================
// HIGH PRIORITY TESTS: Real-world pytest patterns
// ============================================================================

#[test]
#[timeout(30000)]
fn test_parametrized_fixture_detection() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(params=[1, 2, 3])
def number_fixture(request):
    return request.param

@pytest.fixture(params=["a", "b"])
def letter_fixture(request):
    return request.param
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect parametrized fixtures
    assert!(db.definitions.contains_key("number_fixture"));
    assert!(db.definitions.contains_key("letter_fixture"));

    let number_defs = db.definitions.get("number_fixture").unwrap();
    assert_eq!(number_defs.len(), 1);
    assert_eq!(number_defs[0].name, "number_fixture");
}

#[test]
#[timeout(30000)]
fn test_parametrized_fixture_usage() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture(params=[1, 2, 3])
def number_fixture(request):
    return request.param
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    let test_content = r#"
def test_with_parametrized(number_fixture):
    assert number_fixture > 0
"#;
    let test_path = PathBuf::from("/tmp/test/test_param.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should find definition for parametrized fixture
    // Line 1 (0-indexed), character position 27 is where 'number_fixture' starts in parameter
    let definition = db.find_fixture_definition(&test_path, 1, 27);
    assert!(
        definition.is_some(),
        "Should find parametrized fixture definition"
    );
    let def = definition.unwrap();
    assert_eq!(def.name, "number_fixture");
    assert_eq!(def.file_path, conftest_path);
}

#[test]
#[timeout(30000)]
fn test_parametrized_fixture_with_ids() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(params=[1, 2, 3], ids=["one", "two", "three"])
def number_with_ids(request):
    return request.param

@pytest.fixture(params=["x", "y"], ids=lambda x: f"letter_{x}")
def letter_with_ids(request):
    return request.param

@pytest.fixture(
    params=[{"a": 1}, {"b": 2}],
    ids=["dict_a", "dict_b"],
    scope="module"
)
def complex_params(request):
    return request.param
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect all parametrized fixtures with ids
    assert!(
        db.definitions.contains_key("number_with_ids"),
        "Should detect fixture with list ids"
    );
    assert!(
        db.definitions.contains_key("letter_with_ids"),
        "Should detect fixture with lambda ids"
    );
    assert!(
        db.definitions.contains_key("complex_params"),
        "Should detect multi-line parametrized fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_factory_fixture_pattern() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def user_factory():
    def _create_user(name, email):
        return {"name": name, "email": email}
    return _create_user

@pytest.fixture
def database_factory(db_connection):
    def _create_database(name):
        return db_connection.create(name)
    return _create_database
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect factory fixtures
    assert!(db.definitions.contains_key("user_factory"));
    assert!(db.definitions.contains_key("database_factory"));

    let user_factory = db.definitions.get("user_factory").unwrap();
    assert_eq!(user_factory.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_autouse_fixture_detection() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(autouse=True)
def auto_fixture():
    print("Running automatically")
    yield
    print("Cleanup")

@pytest.fixture(scope="function", autouse=True)
def another_auto():
    return 42
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect autouse fixtures
    assert!(db.definitions.contains_key("auto_fixture"));
    assert!(db.definitions.contains_key("another_auto"));

    // Verify autouse field is set correctly
    let auto_fixture = &db.definitions.get("auto_fixture").unwrap()[0];
    assert!(
        auto_fixture.autouse,
        "auto_fixture should have autouse=true"
    );

    let another_auto = &db.definitions.get("another_auto").unwrap()[0];
    assert!(
        another_auto.autouse,
        "another_auto should have autouse=true"
    );
}

#[test]
#[timeout(30000)]
fn test_autouse_fixture_not_flagged_as_undeclared() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

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

    let test_content = r#"
def test_something():
    # auto_setup runs automatically, not declared in parameters
    # Using it in body should NOT be flagged since it's autouse
    result = auto_setup
    assert result == "setup"
"#;
    let test_path = PathBuf::from("/tmp/test/test_autouse.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Note: Current implementation may flag this, which is a limitation
    // This test documents expected behavior for future enhancement
    // For now, autouse fixtures are treated like any other fixture
    // and WILL be flagged if used in function body without parameter declaration
    assert!(
        undeclared.iter().any(|u| u.name == "auto_setup"),
        "Current implementation flags autouse fixtures - this is a known limitation"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_with_scope_session() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

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

@pytest.fixture(scope="module")
def module_fixture():
    return "module data"

@pytest.fixture(scope="class")
def class_fixture():
    return "class data"
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixtures with different scopes
    assert!(db.definitions.contains_key("session_fixture"));
    assert!(db.definitions.contains_key("module_fixture"));
    assert!(db.definitions.contains_key("class_fixture"));
}

#[test]
#[timeout(30000)]
fn test_pytest_asyncio_fixture() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
import pytest_asyncio

@pytest_asyncio.fixture
async def async_fixture():
    return "async data"

@pytest.fixture
async def regular_async_fixture():
    return "also async"
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // @pytest_asyncio.fixture is now supported
    assert!(
        db.definitions.contains_key("async_fixture"),
        "pytest_asyncio.fixture should be detected"
    );

    // Regular async fixtures with @pytest.fixture are also detected
    assert!(db.definitions.contains_key("regular_async_fixture"));
}

#[test]
#[timeout(30000)]
fn test_fixture_name_aliasing() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(name="custom_name")
def internal_fixture_name():
    return "aliased"

@pytest.fixture(name="db")
def database_connection():
    return "connection"
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixtures by their alias name (from name= parameter)
    assert!(db.definitions.contains_key("custom_name"));
    assert!(db.definitions.contains_key("db"));

    // The internal function names should NOT be registered as fixtures
    assert!(!db.definitions.contains_key("internal_fixture_name"));
    assert!(!db.definitions.contains_key("database_connection"));
}

#[test]
#[timeout(30000)]
fn test_renamed_fixture_usage_detection() {
    // Test case from https://github.com/bellini666/pytest-language-server/issues/18
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(name="new")
def old() -> int:
    return 1

def test_example(new: int):
    assert new == 1
"#;
    let file_path = PathBuf::from("/tmp/test/test_renamed.py");
    db.analyze_file(file_path.clone(), content);

    // The fixture should be registered under "new", not "old"
    assert!(db.definitions.contains_key("new"));
    assert!(!db.definitions.contains_key("old"));

    // The usage in test_example should reference "new"
    let usages = db.usages.get(&file_path).unwrap();
    assert!(usages.iter().any(|u| u.name == "new"));

    // The fixture should be found and marked as used
    let new_defs = db.definitions.get("new").unwrap();
    assert_eq!(new_defs.len(), 1);
    assert_eq!(new_defs[0].file_path, file_path);
}

#[test]
#[timeout(30000)]
fn test_class_based_test_methods_use_fixtures() {
    // Test case from https://github.com/bellini666/pytest-language-server/issues/19
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture() -> int:
    return 1

class TestInClass:
    def test_in_class(self, my_fixture: int):
        assert my_fixture == 1

    def test_another(self, my_fixture: int):
        assert my_fixture == 1
"#;
    let file_path = PathBuf::from("/tmp/test/test_class.py");
    db.analyze_file(file_path.clone(), content);

    // The fixture should be detected
    assert!(db.definitions.contains_key("my_fixture"));

    // The test methods inside the class should register fixture usages
    let usages = db.usages.get(&file_path).unwrap();
    let my_fixture_usages: Vec<_> = usages.iter().filter(|u| u.name == "my_fixture").collect();

    assert_eq!(
        my_fixture_usages.len(),
        2,
        "Should have 2 usages of my_fixture from test methods in class"
    );
}

#[test]
#[timeout(30000)]
fn test_nested_class_test_methods() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def outer_fixture():
    return "outer"

class TestOuter:
    def test_outer(self, outer_fixture):
        pass

    class TestNested:
        def test_nested(self, outer_fixture):
            pass
"#;
    let file_path = PathBuf::from("/tmp/test/test_nested.py");
    db.analyze_file(file_path.clone(), content);

    // Both outer and nested test methods should find the fixture
    let usages = db.usages.get(&file_path).unwrap();
    let fixture_usages: Vec<_> = usages
        .iter()
        .filter(|u| u.name == "outer_fixture")
        .collect();

    assert_eq!(
        fixture_usages.len(),
        2,
        "Should have 2 usages from both outer and nested test classes"
    );
}

#[test]
#[timeout(30000)]
fn test_deeply_nested_classes() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "shared"

class TestLevel1:
    def test_level1(self, shared_fixture):
        pass

    class TestLevel2:
        def test_level2(self, shared_fixture):
            pass

        class TestLevel3:
            def test_level3(self, shared_fixture):
                pass
"#;
    let file_path = PathBuf::from("/tmp/test/test_deep_nested.py");
    db.analyze_file(file_path.clone(), content);

    // All test methods at all nesting levels should find the fixture
    let usages = db.usages.get(&file_path).unwrap();
    let fixture_usages: Vec<_> = usages
        .iter()
        .filter(|u| u.name == "shared_fixture")
        .collect();

    assert_eq!(
        fixture_usages.len(),
        3,
        "Should have 3 usages from all nesting levels"
    );
}

#[test]
#[timeout(30000)]
fn test_nested_class_with_usefixtures() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def setup_fixture():
    return "setup"

@pytest.fixture
def nested_setup():
    return "nested"

@pytest.mark.usefixtures("setup_fixture")
class TestOuter:
    def test_outer(self):
        pass

    @pytest.mark.usefixtures("nested_setup")
    class TestNested:
        def test_nested(self):
            pass
"#;
    let file_path = PathBuf::from("/tmp/test/test_nested_usefixtures.py");
    db.analyze_file(file_path.clone(), content);

    let usages = db.usages.get(&file_path).unwrap();

    // Both usefixtures decorators should be detected
    assert!(
        usages.iter().any(|u| u.name == "setup_fixture"),
        "setup_fixture from outer class usefixtures should be detected"
    );
    assert!(
        usages.iter().any(|u| u.name == "nested_setup"),
        "nested_setup from nested class usefixtures should be detected"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_in_nested_class() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

class TestOuter:
    @pytest.fixture
    def outer_class_fixture(self):
        return "outer"

    def test_uses_outer(self, outer_class_fixture):
        pass

    class TestNested:
        @pytest.fixture
        def nested_class_fixture(self):
            return "nested"

        def test_uses_nested(self, nested_class_fixture):
            pass

        def test_uses_both(self, outer_class_fixture, nested_class_fixture):
            pass
"#;
    let file_path = PathBuf::from("/tmp/test/test_fixture_in_nested.py");
    db.analyze_file(file_path.clone(), content);

    // Both class-level fixtures should be detected
    assert!(
        db.definitions.contains_key("outer_class_fixture"),
        "Fixture in outer class should be detected"
    );
    assert!(
        db.definitions.contains_key("nested_class_fixture"),
        "Fixture in nested class should be detected"
    );

    let usages = db.usages.get(&file_path).unwrap();

    // Check usages
    let outer_usages: Vec<_> = usages
        .iter()
        .filter(|u| u.name == "outer_class_fixture")
        .collect();
    assert_eq!(
        outer_usages.len(),
        2,
        "outer_class_fixture should be used twice"
    );

    let nested_usages: Vec<_> = usages
        .iter()
        .filter(|u| u.name == "nested_class_fixture")
        .collect();
    assert_eq!(
        nested_usages.len(),
        2,
        "nested_class_fixture should be used twice"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_defined_in_class() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

class TestWithFixture:
    @pytest.fixture
    def class_fixture(self):
        return "class_value"

    def test_uses_class_fixture(self, class_fixture):
        assert class_fixture == "class_value"
"#;
    let file_path = PathBuf::from("/tmp/test/test_class_fixture.py");
    db.analyze_file(file_path.clone(), content);

    // Fixture defined inside class should be detected
    assert!(
        db.definitions.contains_key("class_fixture"),
        "Class-defined fixture should be detected"
    );

    // Test method should register usage
    let usages = db.usages.get(&file_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "class_fixture"),
        "Usage of class fixture should be detected"
    );
}

// =============================================================================
// request builtin fixture tests
// =============================================================================

#[test]
#[timeout(30000)]
fn test_request_fixture_definition_registered_after_venv_scan() {
    // After scanning a fake _pytest directory (simulating a venv), the
    // synthetic `request` definition must be present with the correct type.
    use tempfile::tempdir;

    let temp = tempdir().unwrap();
    // scan_venv_fixtures looks for .venv/lib/python*/site-packages/
    let venv = temp.path().join(".venv");
    let site_packages = venv.join("lib").join("python3.11").join("site-packages");
    let pytest_internal = site_packages.join("_pytest");
    std::fs::create_dir_all(&pytest_internal).unwrap();

    // Write a minimal fixtures.py so the scanner has a real path to point at.
    std::fs::write(
        pytest_internal.join("fixtures.py"),
        b"# pytest internal fixtures\n",
    )
    .unwrap();

    let db = FixtureDatabase::new();
    db.scan_workspace(temp.path());

    let defs = db.definitions.get("request");
    assert!(
        defs.is_some(),
        "'request' fixture definition must be registered after venv scan"
    );

    let def = &defs.unwrap()[0];
    assert_eq!(def.name, "request");
    assert_eq!(
        def.return_type.as_deref(),
        Some("FixtureRequest"),
        "request fixture must have return_type = FixtureRequest"
    );
    assert!(
        def.is_third_party,
        "request fixture must be marked as third-party"
    );
    assert!(def.is_plugin, "request fixture must be marked as plugin");
}

#[test]
#[timeout(30000)]
fn test_request_fixture_return_type_import_spec() {
    // The return_type_imports for `request` must tell code actions to insert
    // `from pytest import FixtureRequest`.
    use tempfile::tempdir;

    let temp = tempdir().unwrap();
    let venv = temp.path().join(".venv");
    let site_packages = venv.join("lib").join("python3.11").join("site-packages");
    let pytest_internal = site_packages.join("_pytest");
    std::fs::create_dir_all(&pytest_internal).unwrap();
    std::fs::write(pytest_internal.join("fixtures.py"), b"").unwrap();

    let db = FixtureDatabase::new();
    db.scan_workspace(temp.path());

    let defs = db
        .definitions
        .get("request")
        .expect("request must be registered");
    let def = &defs[0];

    assert_eq!(def.return_type_imports.len(), 1);
    let spec = &def.return_type_imports[0];
    assert_eq!(spec.check_name, "FixtureRequest");
    assert_eq!(spec.import_statement, "from pytest import FixtureRequest");
}

#[test]
#[timeout(30000)]
fn test_request_fixture_has_docstring() {
    use tempfile::tempdir;

    let temp = tempdir().unwrap();
    let venv = temp.path().join(".venv");
    let site_packages = venv.join("lib").join("python3.11").join("site-packages");
    let pytest_internal = site_packages.join("_pytest");
    std::fs::create_dir_all(&pytest_internal).unwrap();
    std::fs::write(pytest_internal.join("fixtures.py"), b"").unwrap();

    let db = FixtureDatabase::new();
    db.scan_workspace(temp.path());

    let defs = db
        .definitions
        .get("request")
        .expect("request must be registered");
    let def = &defs[0];

    let doc = def.docstring.as_deref().unwrap_or("");
    assert!(
        doc.contains("FixtureRequest") || doc.contains("requesting test context"),
        "request docstring should describe its purpose, got: {:?}",
        doc
    );
    assert!(
        doc.contains("param") || doc.contains("addfinalizer") || doc.contains("docs.pytest.org"),
        "request docstring should mention key attributes or docs URL, got: {:?}",
        doc
    );
}

#[test]
#[timeout(30000)]
fn test_request_not_flagged_as_undeclared_in_test_function() {
    // `request` must never appear in the undeclared-fixtures list, even when
    // used in a test body without being declared as a parameter.
    let db = FixtureDatabase::new();

    let content = r#"
def test_uses_request_in_body():
    val = request.param
    assert val is not None
"#;
    let path = PathBuf::from("/tmp/test_req_undecl/test_req.py");
    db.analyze_file(path.clone(), content);

    let undeclared = db.get_undeclared_fixtures(&path);
    assert!(
        !undeclared.iter().any(|u| u.name == "request"),
        "request must never be reported as undeclared"
    );
}

#[test]
#[timeout(30000)]
fn test_request_not_flagged_as_undeclared_in_fixture() {
    // `request` as a fixture parameter must not be flagged as undeclared.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture(request):
    return request.param
"#;
    let path = PathBuf::from("/tmp/test_req_fixt_undecl/conftest.py");
    db.analyze_file(path.clone(), content);

    let undeclared = db.get_undeclared_fixtures(&path);
    assert!(
        !undeclared.iter().any(|u| u.name == "request"),
        "request in a fixture parameter must not be flagged as undeclared"
    );
}

#[test]
#[timeout(30000)]
fn test_request_recorded_as_usage_in_test_function() {
    // `request` used as a test function parameter must be tracked so inlay
    // hints can show its type.
    let db = FixtureDatabase::new();

    let content = r#"
def test_uses_request(request):
    assert request.node is not None
"#;
    let path = PathBuf::from("/tmp/test_req_usage_test/test_req.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).expect("usages should be tracked");
    let request_usage = usages.iter().find(|u| u.name == "request");
    assert!(
        request_usage.is_some(),
        "request parameter in a test function must be tracked as a usage"
    );
    assert!(
        request_usage.unwrap().is_parameter,
        "request in a test function parameter must have is_parameter = true"
    );
}

#[test]
#[timeout(30000)]
fn test_request_recorded_as_usage_in_fixture_function() {
    // `request` used as a fixture parameter must also be tracked so inlay
    // hints can show `: FixtureRequest` on it.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def parametrized_fixture(request):
    return request.param * 2
"#;
    let path = PathBuf::from("/tmp/test_req_usage_fix/conftest.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).expect("usages should be tracked");
    let request_usage = usages.iter().find(|u| u.name == "request");
    assert!(
        request_usage.is_some(),
        "request parameter in a fixture function must be tracked as a usage"
    );
    assert!(
        request_usage.unwrap().is_parameter,
        "request in a fixture function parameter must have is_parameter = true"
    );
}

#[test]
#[timeout(30000)]
fn test_request_not_added_as_fixture_dependency() {
    // Even though `request` is now tracked as a usage, it must NOT appear in
    // the fixture's dependency list — it is a special pytest injection, not a
    // regular fixture dependency.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture(request, tmp_path):
    return request.param
"#;
    let path = PathBuf::from("/tmp/test_req_dep/conftest.py");
    db.analyze_file(path.clone(), content);

    let defs = db
        .definitions
        .get("my_fixture")
        .expect("fixture must be detected");
    let def = &defs[0];

    assert!(
        !def.dependencies.contains(&"request".to_string()),
        "request must not appear in fixture dependencies, got: {:?}",
        def.dependencies
    );
    // tmp_path should still be a dependency
    assert!(
        def.dependencies.contains(&"tmp_path".to_string()),
        "tmp_path should be listed as a dependency"
    );
}

#[test]
#[timeout(30000)]
fn test_request_completion_available() {
    // `request` must appear in the list of available fixtures for a test file
    // once scan_workspace discovers the venv's `_pytest` package.
    use tempfile::tempdir;

    let temp = tempdir().unwrap();
    let venv = temp.path().join(".venv");
    let site_packages = venv.join("lib").join("python3.11").join("site-packages");
    let pytest_internal = site_packages.join("_pytest");
    std::fs::create_dir_all(&pytest_internal).unwrap();
    std::fs::write(pytest_internal.join("fixtures.py"), b"").unwrap();

    // Write a minimal test file so there's a file path to query against.
    let test_dir = temp.path().join("tests");
    std::fs::create_dir_all(&test_dir).unwrap();
    let test_path = test_dir.join("test_req.py");
    std::fs::write(&test_path, b"def test_something(request): pass").unwrap();

    let db = FixtureDatabase::new();
    db.scan_workspace(temp.path());

    let available = db.get_available_fixtures(&test_path);
    let request_def = available.iter().find(|f| f.name == "request");
    assert!(
        request_def.is_some(),
        "request must appear in available fixtures after venv scan"
    );
    assert_eq!(
        request_def.unwrap().return_type.as_deref(),
        Some("FixtureRequest")
    );
}

#[test]
#[timeout(30000)]
fn test_request_not_in_cycle_detection() {
    // `request` must not participate in cycle detection (it has no dependencies).
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def a(request):
    return request.param

@pytest.fixture
def b(a):
    return a + 1
"#;
    let path = PathBuf::from("/tmp/test_req_cycle/conftest.py");
    db.analyze_file(path.clone(), content);

    let cycles = db.detect_fixture_cycles();
    assert!(
        cycles.is_empty(),
        "There should be no cycles; request must not cause false cycle detection"
    );
}

#[test]
#[timeout(30000)]
fn test_pytest_django_builtin_fixtures() {
    let db = FixtureDatabase::new();

    // Simulate pytest-django fixtures in site-packages
    let django_plugin_content = r#"
import pytest

@pytest.fixture
def db():
    """Provide django database access"""
    return "db_connection"

@pytest.fixture
def client():
    """Provide django test client"""
    return "test_client"

@pytest.fixture
def admin_client():
    """Provide django admin client"""
    return "admin_client"
"#;
    let plugin_path =
        PathBuf::from("/tmp/.venv/lib/python3.11/site-packages/pytest_django/fixtures.py");
    db.analyze_file(plugin_path.clone(), django_plugin_content);

    let test_content = r#"
def test_with_django_fixtures(db, client, admin_client):
    assert db is not None
    assert client is not None
"#;
    let test_path = PathBuf::from("/tmp/test/test_django.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should detect django fixtures from third-party plugin
    assert!(db.definitions.contains_key("db"));
    assert!(db.definitions.contains_key("client"));
    assert!(db.definitions.contains_key("admin_client"));

    // Verify usages were detected
    assert!(
        db.usages.contains_key(&test_path),
        "Test file should have fixture usages"
    );
    let usages = db.usages.get(&test_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "db"),
        "Should detect 'db' fixture usage"
    );
    assert!(
        usages.iter().any(|u| u.name == "client"),
        "Should detect 'client' fixture usage"
    );

    // Should find definition using third-party fixture resolution
    // Line 1 (0-indexed), character 31 is where 'db' starts in the parameter list
    let db_def = db.find_fixture_definition(&test_path, 1, 31);
    assert!(db_def.is_some(), "Should find third-party fixture 'db'");
    assert_eq!(db_def.unwrap().name, "db");
}

#[test]
#[timeout(30000)]
fn test_pytest_mock_advanced_patterns() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_service():
    return Mock()

@pytest.fixture
def patched_function(mocker):
    return mocker.patch('module.function')
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    // Should detect fixtures that use mocker
    assert!(db.definitions.contains_key("mock_service"));
    assert!(db.definitions.contains_key("patched_function"));

    // patched_function uses mocker as dependency
    let patched = db.definitions.get("patched_function").unwrap();
    assert_eq!(patched.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_mixed_sync_async_fixture_dependencies() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def sync_fixture():
    return "sync"

@pytest.fixture
async def async_fixture(sync_fixture):
    return f"async_{sync_fixture}"

@pytest.fixture
async def another_async(async_fixture):
    return f"another_{await async_fixture}"
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect mixed sync/async fixtures
    assert!(db.definitions.contains_key("sync_fixture"));
    assert!(db.definitions.contains_key("async_fixture"));
    assert!(db.definitions.contains_key("another_async"));

    // Check that async_fixture depends on sync_fixture
    let async_usages = db.usages.get(&file_path).unwrap();
    assert!(async_usages.iter().any(|u| u.name == "sync_fixture"));
}

#[test]
#[timeout(30000)]
fn test_yield_fixture_with_exception_handling() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def resource_with_cleanup():
    resource = acquire_resource()
    try:
        yield resource
    except Exception as e:
        handle_error(e)
    finally:
        cleanup_resource(resource)

@pytest.fixture
def complex_fixture():
    setup()
    try:
        yield "value"
    finally:
        teardown()
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect yield fixtures with exception handling
    assert!(db.definitions.contains_key("resource_with_cleanup"));
    assert!(db.definitions.contains_key("complex_fixture"));
}

#[test]
#[timeout(30000)]
fn test_yield_fixture_basic() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def simple_yield_fixture():
    """A simple yield fixture with setup and teardown."""
    # Setup
    connection = create_connection()
    yield connection
    # Teardown
    connection.close()

@pytest.fixture
def yield_with_value():
    yield 42

@pytest.fixture
def yield_none():
    yield
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // All yield fixtures should be detected
    assert!(
        db.definitions.contains_key("simple_yield_fixture"),
        "Simple yield fixture should be detected"
    );
    assert!(
        db.definitions.contains_key("yield_with_value"),
        "Yield with value should be detected"
    );
    assert!(
        db.definitions.contains_key("yield_none"),
        "Yield None should be detected"
    );

    // Check docstring extraction works for yield fixtures
    let simple = db.definitions.get("simple_yield_fixture").unwrap();
    assert!(
        simple[0].docstring.is_some(),
        "Docstring should be extracted from yield fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_yield_fixture_usage_in_test() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def db_session():
    session = create_session()
    yield session
    session.rollback()
    session.close()
"#;

    let test_content = r#"
def test_with_db(db_session):
    db_session.query("SELECT 1")
"#;

    let conftest_path = PathBuf::from("/tmp/test_yield/conftest.py");
    let test_path = PathBuf::from("/tmp/test_yield/test_db.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Yield fixture should be found via go-to-definition
    let definition = db.find_fixture_definition(&test_path, 1, 18);
    assert!(definition.is_some(), "Should find yield fixture definition");
    let def = definition.unwrap();
    assert_eq!(def.name, "db_session");
    assert_eq!(def.file_path, conftest_path);
}

#[test]
#[timeout(30000)]
fn test_yield_fixture_with_context_manager() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from contextlib import contextmanager

@pytest.fixture
def managed_resource():
    with open("file.txt") as f:
        yield f

@pytest.fixture
def nested_context():
    with lock:
        with connection:
            yield connection
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    assert!(
        db.definitions.contains_key("managed_resource"),
        "Yield fixture with context manager should be detected"
    );
    assert!(
        db.definitions.contains_key("nested_context"),
        "Yield fixture with nested context should be detected"
    );
}

#[test]
#[timeout(30000)]
fn test_async_yield_fixture() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
async def async_db():
    db = await create_async_db()
    yield db
    await db.close()

@pytest.fixture
async def async_client():
    async with httpx.AsyncClient() as client:
        yield client
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    assert!(
        db.definitions.contains_key("async_db"),
        "Async yield fixture should be detected"
    );
    assert!(
        db.definitions.contains_key("async_client"),
        "Async yield fixture with context manager should be detected"
    );
}

#[test]
#[timeout(30000)]
fn test_indirect_parametrization() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def user_data(request):
    return request.param

@pytest.mark.parametrize("user_data", [
    {"name": "Alice"},
    {"name": "Bob"}
], indirect=True)
def test_user(user_data):
    assert user_data["name"] in ["Alice", "Bob"]
"#;
    let test_path = PathBuf::from("/tmp/test/test_indirect.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should detect fixture used with indirect parametrization
    assert!(db.definitions.contains_key("user_data"));

    let usages = db.usages.get(&test_path).unwrap();
    assert!(usages.iter().any(|u| u.name == "user_data"));
}

// ============================================================================
// HIGH PRIORITY TESTS: Undeclared fixture detection gaps
// ============================================================================

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_walrus_operator() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return [1, 2, 3, 4, 5]
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    let test_content = r#"
def test_walrus():
    # Using walrus operator with fixture name
    if (data := my_fixture):
        assert len(data) > 0
"#;
    let test_path = PathBuf::from("/tmp/test/test_walrus.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Note: Current implementation may not detect walrus operator assignments
    // This test documents the limitation
    if undeclared.is_empty() {
        // Known limitation: walrus operator (named expressions) not handled
        println!("LIMITATION: Walrus operator assignments not detected as local variables");
    } else {
        // If detected, it should flag my_fixture as undeclared
        assert!(undeclared.iter().any(|u| u.name == "my_fixture"));
    }
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_list_comprehension() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def items():
    return [1, 2, 3]

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

    let test_content = r#"
def test_comprehension():
    # Using fixture in list comprehension iterable - should be flagged
    result = [x * 2 for x in items]
    assert len(result) == 3

    # Using fixture in comprehension expression - should be flagged
    result2 = [multiplier * x for x in [1, 2, 3]]
    assert result2 == [2, 4, 6]
"#;
    let test_path = PathBuf::from("/tmp/test/test_comprehension.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Note: Current implementation does not track comprehension loop variables
    // as local variables, so this is a KNOWN LIMITATION
    println!(
        "Undeclared fixtures detected: {:?}",
        undeclared.iter().map(|u| &u.name).collect::<Vec<_>>()
    );

    // This test documents that comprehensions are partially detected
    // but comprehension loop variables are not tracked as locals
    if undeclared.iter().any(|u| u.name == "items") {
        // Good: fixture in iterable is detected
        // Test passes
    } else {
        // Known limitation: comprehension analysis is incomplete
        println!("LIMITATION: List comprehension variables not fully analyzed");
    }
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_dict_comprehension() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def data_dict():
    return {"a": 1, "b": 2}
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    let test_content = r#"
def test_dict_comp():
    # Using fixture in dict comprehension
    result = {k: v * 2 for k, v in data_dict.items()}
    assert result["a"] == 2
"#;
    let test_path = PathBuf::from("/tmp/test/test_dict_comp.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Note: Current implementation does not detect fixtures in dict comprehensions
    // This is a KNOWN LIMITATION
    if undeclared.iter().any(|u| u.name == "data_dict") {
        // Dict comprehension fixture detection working
    } else {
        println!("LIMITATION: Dict comprehension fixture detection not implemented");
        // Test documents known limitation
    }
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_generator_expression() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def numbers():
    return [1, 2, 3, 4, 5]
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    let test_content = r#"
def test_generator():
    # Using fixture in generator expression
    gen = (x * 2 for x in numbers)
    result = list(gen)
    assert len(result) == 5
"#;
    let test_path = PathBuf::from("/tmp/test/test_generator.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Note: Generator expressions are similar to list comprehensions
    // Current implementation does not detect these - KNOWN LIMITATION
    if undeclared.iter().any(|u| u.name == "numbers") {
        // Generator expression fixture detection working
    } else {
        println!("LIMITATION: Generator expression fixture detection not implemented");
        // Test documents known limitation
    }
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_f_string() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

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

    let test_content = r#"
def test_f_string():
    # Using fixture in f-string interpolation
    message = f"Hello {user_name}"
    assert "Alice" in message
"#;
    let test_path = PathBuf::from("/tmp/test/test_f_string.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Note: Current rustpython-parser may not expose f-string internals
    // This test documents expected behavior
    if undeclared.iter().any(|u| u.name == "user_name") {
        // Good: f-string variables are detected
        // F-string fixture detection working
    } else {
        println!("LIMITATION: F-string interpolation not analyzed for fixture references");
    }
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_lambda() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

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

    let test_content = r#"
def test_lambda():
    # Using fixture in lambda body
    func = lambda x: x * multiplier
    result = func(5)
    assert result == 15
"#;
    let test_path = PathBuf::from("/tmp/test/test_lambda.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Note: Lambda expressions are currently not analyzed for fixture usage
    // This is a KNOWN LIMITATION
    if undeclared.iter().any(|u| u.name == "multiplier") {
        // Lambda fixture detection working
    } else {
        println!("LIMITATION: Lambda expressions not analyzed for fixture references");
        // Test documents known limitation
    }
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_nested_function() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def config():
    return {"key": "value"}
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    let test_content = r#"
def test_nested():
    def inner_function():
        # Using fixture from outer scope
        return config["key"]

    result = inner_function()
    assert result == "value"
"#;
    let test_path = PathBuf::from("/tmp/test/test_nested.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Note: Nested functions are a complex case
    // Current implementation scans the test function body but may not
    // traverse into nested function definitions
    if undeclared.iter().any(|u| u.name == "config") {
        // Nested function fixture detection working
    } else {
        println!("LIMITATION: Nested functions not analyzed for fixture references");
    }
}

#[test]
#[timeout(30000)]
fn test_undeclared_fixture_in_decorator_argument() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

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

    let test_content = r#"
import pytest

def timeout_decorator(seconds):
    def decorator(func):
        return func
    return decorator

@timeout_decorator(timeout_value)
def test_with_timeout():
    assert True
"#;
    let test_path = PathBuf::from("/tmp/test/test_decorator.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Decorator arguments are typically not scanned
    // This test documents the limitation
    if undeclared.iter().any(|u| u.name == "timeout_value") {
        // Decorator argument fixture detection working
    } else {
        println!("LIMITATION: Decorator arguments not analyzed for fixture references");
    }
}

#[test]
#[timeout(30000)]
fn test_local_variable_shadowing_fixture() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

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

    let test_content = r#"
def test_shadowing():
    # Local variable shadows fixture name
    data = "local_data"
    assert data == "local_data"

    # This should NOT be flagged as undeclared
    result = data.upper()
    assert result == "LOCAL_DATA"
"#;
    let test_path = PathBuf::from("/tmp/test/test_shadow.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Should NOT flag 'data' as undeclared because it's assigned locally
    assert!(
        !undeclared.iter().any(|u| u.name == "data"),
        "Local variable should shadow fixture name - should not be flagged"
    );
}

#[test]
#[timeout(30000)]
fn test_comprehension_variable_shadowing_fixture() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

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

    let test_content = r#"
def test_comp_shadow():
    # Comprehension variable 'x' shadows fixture 'x'
    result = [x * 2 for x in [1, 2, 3]]
    assert result == [2, 4, 6]
"#;
    let test_path = PathBuf::from("/tmp/test/test_comp_shadow.py");
    db.analyze_file(test_path.clone(), test_content);

    let undeclared = db.get_undeclared_fixtures(&test_path);

    // Note: Comprehension variables are not currently tracked as local vars
    // This is a known limitation
    if undeclared.iter().any(|u| u.name == "x") {
        println!("LIMITATION: Comprehension variables not tracked - false positive for 'x'");
    } else {
        // Comprehension variable correctly handled
    }
}

// ============================================================================
// MEDIUM PRIORITY TESTS: Fixture detection advanced cases
// ============================================================================

#[test]
#[timeout(30000)]
fn test_decorator_with_multiple_arguments() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(scope="session", autouse=True, name="custom")
def complex_fixture():
    return 42

@pytest.fixture(scope="module", params=[1, 2, 3])
def parametrized_scoped():
    return "data"
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixtures with multiple decorator arguments
    // When name= is present, use the alias; otherwise use function name
    assert!(db.definitions.contains_key("custom")); // has name="custom"
    assert!(!db.definitions.contains_key("complex_fixture")); // function name not registered
    assert!(db.definitions.contains_key("parametrized_scoped")); // no name=, uses function name
}

#[test]
#[timeout(30000)]
fn test_parameter_with_type_hints() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from typing import List, Dict

@pytest.fixture
def typed_fixture(param: str, count: int) -> Dict[str, int]:
    return {param: count}

@pytest.fixture
def complex_types(data: List[str]) -> List[Dict[str, int]]:
    return [{"item": len(d)} for d in data]
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixtures with typed parameters
    assert!(db.definitions.contains_key("typed_fixture"));
    assert!(db.definitions.contains_key("complex_types"));

    // Check that parameter type hints are handled correctly
    let typed_usages = db.usages.get(&file_path).unwrap();
    assert!(typed_usages.iter().any(|u| u.name == "param"));
    assert!(typed_usages.iter().any(|u| u.name == "count"));
}

#[test]
#[timeout(30000)]
fn test_default_parameter_values() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def fixture_with_defaults(value="default", count=0):
    return value * count

@pytest.fixture
def optional_param(data=None):
    return data or []
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixtures with default parameter values
    assert!(db.definitions.contains_key("fixture_with_defaults"));
    assert!(db.definitions.contains_key("optional_param"));
}

#[test]
#[timeout(30000)]
fn test_variadic_parameters() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def fixture_with_args(*args):
    return args

@pytest.fixture
def fixture_with_kwargs(**kwargs):
    return kwargs

@pytest.fixture
def fixture_with_both(base, *args, **kwargs):
    return (base, args, kwargs)
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixtures with *args and **kwargs
    assert!(db.definitions.contains_key("fixture_with_args"));
    assert!(db.definitions.contains_key("fixture_with_kwargs"));
    assert!(db.definitions.contains_key("fixture_with_both"));

    // Check that 'base' is detected as a dependency, but not *args or **kwargs
    let usages = db.usages.get(&file_path).unwrap();
    assert!(usages.iter().any(|u| u.name == "base"));
    assert!(!usages.iter().any(|u| u.name == "args"));
    assert!(!usages.iter().any(|u| u.name == "kwargs"));
}

#[test]
#[timeout(30000)]
fn test_variadic_with_fixture_dependencies() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def base_fixture():
    return "base"

@pytest.fixture
def config_fixture():
    return {"key": "value"}

@pytest.fixture
def combined_fixture(base_fixture, config_fixture, *args, **kwargs):
    """Fixture that depends on other fixtures and also accepts variadic args."""
    return {
        "base": base_fixture,
        "config": config_fixture,
        "extra_args": args,
        "extra_kwargs": kwargs,
    }
"#;

    let conftest_path = PathBuf::from("/tmp/test_variadic/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    // All fixtures should be detected
    assert!(db.definitions.contains_key("base_fixture"));
    assert!(db.definitions.contains_key("config_fixture"));
    assert!(db.definitions.contains_key("combined_fixture"));

    // Fixture dependencies should be tracked
    let usages = db.usages.get(&conftest_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "base_fixture"),
        "base_fixture should be tracked as dependency"
    );
    assert!(
        usages.iter().any(|u| u.name == "config_fixture"),
        "config_fixture should be tracked as dependency"
    );
}

#[test]
#[timeout(30000)]
fn test_variadic_in_test_function() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42

def test_with_variadic(my_fixture, *args, **kwargs):
    # Note: This is unusual but valid Python
    assert my_fixture == 42
"#;

    let test_path = PathBuf::from("/tmp/test_variadic/test_func.py");
    db.analyze_file(test_path.clone(), test_content);

    // Fixture should be detected
    assert!(db.definitions.contains_key("my_fixture"));

    // Usage should be tracked
    let usages = db.usages.get(&test_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "my_fixture"),
        "my_fixture should be tracked as usage in test"
    );

    // *args and **kwargs should NOT be tracked as fixture usages
    assert!(
        !usages.iter().any(|u| u.name == "args"),
        "args should not be tracked as fixture"
    );
    assert!(
        !usages.iter().any(|u| u.name == "kwargs"),
        "kwargs should not be tracked as fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_keyword_only_with_variadic() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def dep_fixture():
    return "dep"

@pytest.fixture
def complex_fixture(*args, kwonly_dep: str, **kwargs):
    # kwonly_dep is a keyword-only parameter that could be a fixture
    return kwonly_dep
"#;

    let file_path = PathBuf::from("/tmp/test_variadic/conftest.py");
    db.analyze_file(file_path.clone(), content);

    assert!(db.definitions.contains_key("dep_fixture"));
    assert!(db.definitions.contains_key("complex_fixture"));

    // kwonly_dep should be tracked as a potential fixture dependency
    let usages = db.usages.get(&file_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "kwonly_dep"),
        "Keyword-only parameter should be tracked as potential fixture dependency"
    );
}

#[test]
#[timeout(30000)]
fn test_class_based_fixtures() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

class TestClass:
    @pytest.fixture
    def class_fixture(self):
        return "class_value"

    def test_method(self, class_fixture):
        assert class_fixture == "class_value"
"#;
    let file_path = PathBuf::from("/tmp/test/test_class.py");
    db.analyze_file(file_path.clone(), content);

    // Note: Class-based fixtures may not be fully supported
    // This test documents the current behavior
    if db.definitions.contains_key("class_fixture") {
        // Class-based fixtures detected
    } else {
        println!("LIMITATION: Class-based fixtures not detected");
    }
}

#[test]
#[timeout(30000)]
fn test_classmethod_and_staticmethod_fixtures() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

class TestClass:
    @classmethod
    @pytest.fixture
    def class_method_fixture(cls):
        return "classmethod"

    @staticmethod
    @pytest.fixture
    def static_method_fixture():
        return "staticmethod"
"#;
    let file_path = PathBuf::from("/tmp/test/test_methods.py");
    db.analyze_file(file_path.clone(), content);

    // These are unusual patterns - document behavior
    if db.definitions.contains_key("class_method_fixture") {
        println!("Class method fixtures detected");
    }
    if db.definitions.contains_key("static_method_fixture") {
        println!("Static method fixtures detected");
    }
}

#[test]
#[timeout(30000)]
fn test_unicode_fixture_names() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def 測試_fixture():
    """Chinese/Japanese test fixture"""
    return "test"

@pytest.fixture
def фикстура():
    """Russian fixture"""
    return "fixture"

@pytest.fixture
def fixture_émoji():
    """French accent fixture"""
    return "emoji"

@pytest.fixture
def données_utilisateur():
    """French: user data"""
    return {"name": "Jean"}

@pytest.fixture
def δεδομένα_χρήστη():
    """Greek: user data"""
    return {"name": "Γιώργος"}
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Python 3 supports Unicode identifiers (PEP 3131)
    // All fixtures should be detected
    assert!(
        db.definitions.contains_key("測試_fixture"),
        "Chinese/Japanese fixture should be detected"
    );
    assert!(
        db.definitions.contains_key("фикстура"),
        "Russian fixture should be detected"
    );
    assert!(
        db.definitions.contains_key("fixture_émoji"),
        "French accent fixture should be detected"
    );
    assert!(
        db.definitions.contains_key("données_utilisateur"),
        "French fixture should be detected"
    );
    assert!(
        db.definitions.contains_key("δεδομένα_χρήστη"),
        "Greek fixture should be detected"
    );

    // Check that docstrings are correctly extracted
    let russian = db.definitions.get("фикстура").unwrap();
    assert!(
        russian[0].docstring.as_ref().unwrap().contains("Russian"),
        "Russian docstring should be extracted"
    );
}

#[test]
#[timeout(30000)]
fn test_unicode_fixture_usage_detection() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def données():
    return 42
"#;

    let test_content = r#"
def test_unicode_usage(données):
    assert données == 42
"#;

    let conftest_path = PathBuf::from("/tmp/test_unicode/conftest.py");
    let test_path = PathBuf::from("/tmp/test_unicode/test_example.py");

    db.analyze_file(conftest_path, conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Check that the Unicode fixture usage was detected
    let usages = db.usages.get(&test_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "données"),
        "Unicode fixture usage should be detected"
    );
}

#[test]
#[timeout(30000)]
fn test_unicode_fixture_goto_definition() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def données():
    return 42
"#;

    let test_content = r#"
def test_unicode(données):
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_unicode/conftest.py");
    let test_path = PathBuf::from("/tmp/test_unicode/test_example.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // The fixture "données" starts at character position 17 on line 2 (1-indexed)
    // In 0-indexed LSP coords: line 1, character 17
    // "def test_unicode(données):"
    //  0         1         2
    //  0123456789012345678901234
    //                  ^--- position 17

    let definition = db.find_fixture_definition(&test_path, 1, 17);

    assert!(
        definition.is_some(),
        "Definition should be found for Unicode fixture"
    );
    let def = definition.unwrap();
    assert_eq!(def.name, "données");
    assert_eq!(def.file_path, conftest_path);
}

#[test]
#[timeout(30000)]
fn test_fixture_names_with_underscores() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def _private_fixture():
    return "private"

@pytest.fixture
def __dunder_fixture__():
    return "dunder"

@pytest.fixture
def fixture__double():
    return "double"
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixtures with various underscore patterns
    assert!(db.definitions.contains_key("_private_fixture"));
    assert!(db.definitions.contains_key("__dunder_fixture__"));
    assert!(db.definitions.contains_key("fixture__double"));
}

#[test]
#[timeout(30000)]
fn test_very_long_fixture_name() {
    let db = FixtureDatabase::new();

    let long_name = "fixture_with_an_extremely_long_name_that_exceeds_typical_naming_conventions_and_tests_the_system_capacity_for_handling_lengthy_identifiers";
    let content = format!(
        r#"
import pytest

@pytest.fixture
def {}():
    return 42
"#,
        long_name
    );

    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), &content);

    // Should handle very long fixture names
    assert!(
        db.definitions.contains_key(long_name),
        "Should handle fixture names over 100 characters"
    );
}

#[test]
#[timeout(30000)]
fn test_optional_and_union_type_hints() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from typing import Optional, Union, List

@pytest.fixture
def optional_fixture(data: Optional[str]) -> Optional[int]:
    return len(data) if data else None

@pytest.fixture
def union_fixture(value: Union[str, int, List[str]]) -> Union[str, int]:
    return value
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixtures with Optional and Union types
    assert!(db.definitions.contains_key("optional_fixture"));
    assert!(db.definitions.contains_key("union_fixture"));

    // Check return type extraction
    let optional_defs = db.definitions.get("optional_fixture").unwrap();
    if let Some(ref return_type) = optional_defs[0].return_type {
        assert!(return_type.contains("Optional") || return_type.contains("int"));
    }
}

#[test]
#[timeout(30000)]
fn test_forward_reference_type_hints() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def forward_ref_fixture() -> "MyClass":
    return MyClass()

class MyClass:
    pass
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixture with forward reference
    assert!(db.definitions.contains_key("forward_ref_fixture"));

    // Check if forward reference is preserved in return type
    let defs = db.definitions.get("forward_ref_fixture").unwrap();
    if let Some(ref return_type) = defs[0].return_type {
        // Forward reference might be stored as "MyClass" or "'MyClass'"
        assert!(return_type.contains("MyClass"));
    }
}

#[test]
#[timeout(30000)]
fn test_generic_type_hints() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from typing import List, Dict, Tuple, Generic, TypeVar

T = TypeVar('T')

@pytest.fixture
def list_fixture() -> List[str]:
    return ["a", "b", "c"]

@pytest.fixture
def dict_fixture() -> Dict[str, List[int]]:
    return {"key": [1, 2, 3]}

@pytest.fixture
def tuple_fixture() -> Tuple[str, int, bool]:
    return ("text", 42, True)
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect fixtures with generic type hints
    assert!(db.definitions.contains_key("list_fixture"));
    assert!(db.definitions.contains_key("dict_fixture"));
    assert!(db.definitions.contains_key("tuple_fixture"));
}

// ============================================================================
// MEDIUM PRIORITY TESTS: Complex hierarchy scenarios
// ============================================================================

#[test]
#[timeout(30000)]
fn test_five_level_override_chain() {
    let db = FixtureDatabase::new();

    // Create 5-level deep hierarchy
    let root_conftest = r#"
import pytest

@pytest.fixture
def deep_fixture():
    return "root"
"#;
    db.analyze_file(PathBuf::from("/tmp/project/conftest.py"), root_conftest);

    let level2_conftest = r#"
import pytest

@pytest.fixture
def deep_fixture(deep_fixture):
    return f"{deep_fixture}_level2"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/project/level2/conftest.py"),
        level2_conftest,
    );

    let level3_conftest = r#"
import pytest

@pytest.fixture
def deep_fixture(deep_fixture):
    return f"{deep_fixture}_level3"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/project/level2/level3/conftest.py"),
        level3_conftest,
    );

    let level4_conftest = r#"
import pytest

@pytest.fixture
def deep_fixture(deep_fixture):
    return f"{deep_fixture}_level4"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/project/level2/level3/level4/conftest.py"),
        level4_conftest,
    );

    let level5_conftest = r#"
import pytest

@pytest.fixture
def deep_fixture(deep_fixture):
    return f"{deep_fixture}_level5"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/project/level2/level3/level4/level5/conftest.py"),
        level5_conftest,
    );

    // Test file at deepest level
    let test_content = r#"
def test_deep(deep_fixture):
    assert "level5" in deep_fixture
"#;
    let test_path = PathBuf::from("/tmp/project/level2/level3/level4/level5/test_deep.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should find the closest (level5) fixture
    let definition = db.find_fixture_definition(&test_path, 1, 15);
    assert!(definition.is_some());
    assert!(definition
        .unwrap()
        .file_path
        .ends_with("level5/conftest.py"));
}

#[test]
#[timeout(30000)]
fn test_diamond_dependency_pattern() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def base_fixture():
    return "base"

@pytest.fixture
def branch_a(base_fixture):
    return f"{base_fixture}_a"

@pytest.fixture
def branch_b(base_fixture):
    return f"{base_fixture}_b"

@pytest.fixture
def diamond(branch_a, branch_b):
    return f"{branch_a}_{branch_b}"
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    // Verify all fixtures detected
    assert!(db.definitions.contains_key("base_fixture"));
    assert!(db.definitions.contains_key("branch_a"));
    assert!(db.definitions.contains_key("branch_b"));
    assert!(db.definitions.contains_key("diamond"));

    // Verify dependencies
    let usages = db.usages.get(&conftest_path).unwrap();
    assert!(usages.iter().any(|u| u.name == "base_fixture"));
    assert!(usages.iter().any(|u| u.name == "branch_a"));
    assert!(usages.iter().any(|u| u.name == "branch_b"));
}

#[test]
#[timeout(30000)]
fn test_ten_level_directory_depth() {
    let db = FixtureDatabase::new();

    // Create fixture at root
    let root_conftest = r#"
import pytest

@pytest.fixture
def deep_search():
    return "found"
"#;
    db.analyze_file(PathBuf::from("/tmp/root/conftest.py"), root_conftest);

    // Test file 10 levels deep
    let test_content = r#"
def test_deep_search(deep_search):
    assert deep_search == "found"
"#;
    let deep_path = PathBuf::from("/tmp/root/a/b/c/d/e/f/g/h/i/j/test_deep.py");
    db.analyze_file(deep_path.clone(), test_content);

    // Should find fixture from root despite 10-level depth
    let definition = db.find_fixture_definition(&deep_path, 1, 22);
    assert!(definition.is_some(), "Should find fixture 10 levels up");
    assert_eq!(definition.unwrap().name, "deep_search");
}

#[test]
#[timeout(30000)]
fn test_fixture_chain_middle_doesnt_use_parent() {
    let db = FixtureDatabase::new();

    let root_conftest = r#"
import pytest

@pytest.fixture
def chain_fixture():
    return "root"
"#;
    db.analyze_file(PathBuf::from("/tmp/test/conftest.py"), root_conftest);

    let middle_conftest = r#"
import pytest

@pytest.fixture
def chain_fixture():
    # Middle fixture doesn't use parent - breaks chain
    return "middle_independent"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/test/subdir/conftest.py"),
        middle_conftest,
    );

    let leaf_conftest = r#"
import pytest

@pytest.fixture
def chain_fixture(chain_fixture):
    # Leaf uses parent (middle), but middle doesn't use root
    return f"{chain_fixture}_leaf"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/test/subdir/deep/conftest.py"),
        leaf_conftest,
    );

    // Test at leaf level
    let test_content = r#"
def test_chain(chain_fixture):
    assert "leaf" in chain_fixture
"#;
    let test_path = PathBuf::from("/tmp/test/subdir/deep/test_chain.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should find leaf fixture
    let definition = db.find_fixture_definition(&test_path, 1, 16);
    assert!(definition.is_some());
    let def = definition.unwrap();
    assert!(def.file_path.ends_with("deep/conftest.py"));
}

#[test]
#[timeout(30000)]
fn test_multiple_fixtures_same_name_in_file() {
    let db = FixtureDatabase::new();

    // Having multiple fixtures with same name in one file is unusual
    // but pytest allows it - last one wins
    let content = r#"
import pytest

@pytest.fixture
def duplicate_fixture():
    return "first"

@pytest.fixture
def duplicate_fixture():
    return "second"

@pytest.fixture
def duplicate_fixture():
    return "third"
"#;
    let file_path = PathBuf::from("/home/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    // Should detect all three definitions
    let defs = db.definitions.get("duplicate_fixture").unwrap();
    assert_eq!(defs.len(), 3, "Should store all duplicate definitions");

    // Verify they are on different lines
    let lines: Vec<usize> = defs.iter().map(|d| d.line).collect();
    assert_eq!(lines.len(), 3);
    // Lines should be ordered (first, second, third fixture)
    assert!(lines[0] < lines[1]);
    assert!(lines[1] < lines[2]);
}

#[test]
#[timeout(30000)]
fn test_sibling_directories_with_same_fixture() {
    let db = FixtureDatabase::new();

    let dir_a_conftest = r#"
import pytest

@pytest.fixture
def sibling_fixture():
    return "from_a"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/project/dir_a/conftest.py"),
        dir_a_conftest,
    );

    let dir_b_conftest = r#"
import pytest

@pytest.fixture
def sibling_fixture():
    return "from_b"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/project/dir_b/conftest.py"),
        dir_b_conftest,
    );

    // Test in dir_a should use dir_a's fixture
    let test_a_content = r#"
def test_a(sibling_fixture):
    assert sibling_fixture == "from_a"
"#;
    let test_a_path = PathBuf::from("/tmp/project/dir_a/test_a.py");
    db.analyze_file(test_a_path.clone(), test_a_content);

    let def_a = db.find_fixture_definition(&test_a_path, 1, 12);
    assert!(def_a.is_some());
    assert!(def_a.unwrap().file_path.to_str().unwrap().contains("dir_a"));

    // Test in dir_b should use dir_b's fixture
    let test_b_content = r#"
def test_b(sibling_fixture):
    assert sibling_fixture == "from_b"
"#;
    let test_b_path = PathBuf::from("/tmp/project/dir_b/test_b.py");
    db.analyze_file(test_b_path.clone(), test_b_content);

    let def_b = db.find_fixture_definition(&test_b_path, 1, 12);
    assert!(def_b.is_some());
    assert!(def_b.unwrap().file_path.to_str().unwrap().contains("dir_b"));
}

#[test]
#[timeout(30000)]
fn test_fixture_with_six_level_parameter_chain() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def level1():
    return 1

@pytest.fixture
def level2(level1):
    return level1 + 1

@pytest.fixture
def level3(level2):
    return level2 + 1

@pytest.fixture
def level4(level3):
    return level3 + 1

@pytest.fixture
def level5(level4):
    return level4 + 1

@pytest.fixture
def level6(level5):
    return level5 + 1
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path.clone(), content);

    // All fixtures should be detected
    for i in 1..=6 {
        let name = format!("level{}", i);
        assert!(db.definitions.contains_key(&name), "Should detect {}", name);
    }

    // Check dependency chain
    let usages = db.usages.get(&conftest_path).unwrap();
    assert!(usages.iter().any(|u| u.name == "level1"));
    assert!(usages.iter().any(|u| u.name == "level2"));
    assert!(usages.iter().any(|u| u.name == "level3"));
    assert!(usages.iter().any(|u| u.name == "level4"));
    assert!(usages.iter().any(|u| u.name == "level5"));
}

#[test]
#[timeout(30000)]
fn test_circular_dependency_detection() {
    let db = FixtureDatabase::new();

    // Note: This creates circular dependencies which pytest would reject at runtime
    // The parser should still detect the fixtures and dependencies
    let content = r#"
import pytest

@pytest.fixture
def fixture_a(fixture_b):
    return f"a_{fixture_b}"

@pytest.fixture
def fixture_b(fixture_a):
    return f"b_{fixture_a}"
"#;
    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(conftest_path.clone(), content);

    // Both fixtures should be detected despite circular dependency
    assert!(db.definitions.contains_key("fixture_a"));
    assert!(db.definitions.contains_key("fixture_b"));

    // Both dependencies should be recorded
    let usages = db.usages.get(&conftest_path).unwrap();
    assert!(usages.iter().any(|u| u.name == "fixture_a"));
    assert!(usages.iter().any(|u| u.name == "fixture_b"));

    // Note: Runtime detection of circular dependencies is pytest's responsibility
    println!("Circular dependencies detected but not validated (pytest's job)");
}

#[test]
#[timeout(30000)]
fn test_multiple_third_party_same_fixture_name() {
    let db = FixtureDatabase::new();

    // Simulate two different plugins defining same fixture
    let plugin1_content = r#"
import pytest

@pytest.fixture
def event_loop():
    return "from_plugin1"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/.venv/lib/python3.11/site-packages/plugin1/fixtures.py"),
        plugin1_content,
    );

    let plugin2_content = r#"
import pytest

@pytest.fixture
def event_loop():
    return "from_plugin2"
"#;
    db.analyze_file(
        PathBuf::from("/tmp/.venv/lib/python3.11/site-packages/plugin2/fixtures.py"),
        plugin2_content,
    );

    // Both should be detected
    let defs = db.definitions.get("event_loop").unwrap();
    assert_eq!(defs.len(), 2, "Should detect both third-party fixtures");

    // Verify both definitions are from site-packages
    let paths: Vec<&str> = defs.iter().map(|d| d.file_path.to_str().unwrap()).collect();
    assert!(
        paths.iter().all(|p| p.contains("site-packages")),
        "All definitions should be from site-packages"
    );

    // Verify usage detection works
    let test_content = r#"
def test_event_loop(event_loop):
    pass
"#;
    let test_path = PathBuf::from("/tmp/project/test_async.py");
    db.analyze_file(test_path.clone(), test_content);

    let usages = db.usages.get(&test_path).unwrap();
    assert_eq!(usages.len(), 1, "Should detect usage in test");
    assert_eq!(usages[0].name, "event_loop");
}

// MARK: File Path Edge Cases

#[test]
#[timeout(30000)]
fn test_unicode_characters_in_path() {
    let db = FixtureDatabase::new();

    // Test with Unicode characters in path
    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/日本語/тест/test_unicode.py");
    db.analyze_file(path.clone(), content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert_eq!(defs[0].file_path, path);
}

#[test]
#[timeout(30000)]
fn test_spaces_in_path() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/my folder/sub folder/test file.py");
    db.analyze_file(path.clone(), content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert_eq!(defs[0].file_path, path);
}

#[test]
#[timeout(30000)]
fn test_special_characters_in_path() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#;
    // Test with parentheses, brackets, and other special chars
    let path = PathBuf::from("/tmp/test/my(folder)[2023]/test-file_v2.py");
    db.analyze_file(path.clone(), content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert_eq!(defs[0].file_path, path);
}

#[test]
#[timeout(30000)]
fn test_very_long_path() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#;
    // Create a very long path (close to system limits)
    let long_component = "a".repeat(50);
    let path_str = format!(
        "/tmp/{}/{}/{}/{}/{}/{}/test.py",
        long_component,
        long_component,
        long_component,
        long_component,
        long_component,
        long_component
    );
    let path = PathBuf::from(path_str);
    db.analyze_file(path.clone(), content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_paths_with_dots() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#;
    // Path with .hidden directories
    let path = PathBuf::from("/tmp/test/.hidden/.config/test.py");
    db.analyze_file(path.clone(), content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert_eq!(defs[0].file_path, path);
}

#[test]
#[timeout(30000)]
fn test_conftest_hierarchy_with_unicode_paths() {
    let db = FixtureDatabase::new();

    // Parent conftest with unicode path
    let parent_content = r#"
import pytest

@pytest.fixture
def base_fixture():
    return "base"
"#;
    let parent_path = PathBuf::from("/tmp/проект/conftest.py");
    db.analyze_file(parent_path.clone(), parent_content);

    // Child test file
    let test_content = r#"
def test_something(base_fixture):
    assert base_fixture == "base"
"#;
    let test_path = PathBuf::from("/tmp/проект/tests/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should detect usage
    let usages = db.usages.get(&test_path).unwrap();
    assert_eq!(usages.len(), 1);
    assert_eq!(usages[0].name, "base_fixture");
}

#[test]
#[timeout(30000)]
fn test_fixture_resolution_with_special_char_paths() {
    let db = FixtureDatabase::new();

    // Conftest in path with special characters
    let conftest_content = r#"
import pytest

@pytest.fixture
def special_fixture():
    return "special"
"#;
    let conftest_path = PathBuf::from("/tmp/my-project (2023)/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    // Test file in subdirectory
    let test_content = r#"
def test_something(special_fixture):
    pass
"#;
    let test_path = PathBuf::from("/tmp/my-project (2023)/tests/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    let usages = db.usages.get(&test_path).unwrap();
    assert_eq!(usages.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_multiple_consecutive_slashes_in_path() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#;
    // Path with multiple consecutive slashes (should be normalized internally)
    let path = PathBuf::from("/tmp/test//subdir///test.py");
    db.analyze_file(path.clone(), content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_path_with_trailing_slash() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#;
    // Even though this is odd, the code should handle it
    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert_eq!(defs[0].file_path, path);
}

#[test]
#[timeout(30000)]
fn test_emoji_in_path() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/😀_folder/🎉test.py");
    db.analyze_file(path.clone(), content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert_eq!(defs[0].file_path, path);
}

// MARK: Workspace Scanning Edge Cases

#[test]
#[timeout(30000)]
fn test_scan_workspace_nonexistent_path() {
    let db = FixtureDatabase::new();

    // Try to scan a path that doesn't exist
    let nonexistent_path = std::path::PathBuf::from("/nonexistent/path/that/should/not/exist");

    // Scan should complete without panicking or errors
    db.scan_workspace(&nonexistent_path);

    // Should have no definitions
    assert!(db.definitions.is_empty());
    assert!(db.usages.is_empty());
}

#[test]
#[timeout(30000)]
fn test_scan_workspace_with_no_python_files() {
    let db = FixtureDatabase::new();
    let temp_dir = std::env::temp_dir().join("test_no_python_files");

    // Create directory structure without Python files
    std::fs::create_dir_all(&temp_dir).ok();

    // Scan should complete without errors
    db.scan_workspace(&temp_dir);

    // Should have no definitions
    assert!(db.definitions.is_empty());

    // Cleanup
    std::fs::remove_dir_all(&temp_dir).ok();
}

#[test]
#[timeout(30000)]
fn test_scan_workspace_with_only_non_test_files() {
    let db = FixtureDatabase::new();
    let temp_dir = std::env::temp_dir().join("test_no_test_files");

    std::fs::create_dir_all(&temp_dir).ok();

    // Create a Python file that doesn't match test patterns
    let file_path = temp_dir.join("utils.py");
    std::fs::write(
        &file_path,
        r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#,
    )
    .ok();

    db.scan_workspace(&temp_dir);

    // Should not detect fixtures in non-test files
    // (scan_workspace only looks for test_*.py, *_test.py, conftest.py)
    assert!(db.definitions.get("my_fixture").is_none());

    std::fs::remove_dir_all(&temp_dir).ok();
}

#[test]
#[timeout(30000)]
fn test_scan_workspace_with_deeply_nested_structure() {
    let db = FixtureDatabase::new();
    let temp_dir = std::env::temp_dir().join("test_deep_nesting");

    // Create deeply nested structure
    let deep_path = temp_dir.join("a/b/c/d/e/f/g/h/i/j");
    std::fs::create_dir_all(&deep_path).ok();

    // Add a test file at the deepest level
    let test_file = deep_path.join("test_deep.py");
    std::fs::write(
        &test_file,
        r#"
import pytest

@pytest.fixture
def deep_fixture():
    return "deep"
"#,
    )
    .ok();

    db.scan_workspace(&temp_dir);

    // Should find the deeply nested fixture
    let defs = db.definitions.get("deep_fixture");
    assert!(defs.is_some());

    std::fs::remove_dir_all(&temp_dir).ok();
}

#[test]
#[timeout(30000)]
fn test_scan_workspace_with_mixed_file_types() {
    let db = FixtureDatabase::new();
    let temp_dir = std::env::temp_dir().join("test_mixed_files");

    std::fs::create_dir_all(&temp_dir).ok();

    // Create conftest.py
    std::fs::write(
        temp_dir.join("conftest.py"),
        r#"
import pytest

@pytest.fixture
def conftest_fixture():
    return "conftest"
"#,
    )
    .ok();

    // Create test_*.py
    std::fs::write(
        temp_dir.join("test_example.py"),
        r#"
import pytest

@pytest.fixture
def test_file_fixture():
    return "test"
"#,
    )
    .ok();

    // Create *_test.py
    std::fs::write(
        temp_dir.join("example_test.py"),
        r#"
import pytest

@pytest.fixture
def suffix_test_fixture():
    return "suffix"
"#,
    )
    .ok();

    // Create non-test Python file
    std::fs::write(
        temp_dir.join("utils.py"),
        r#"
import pytest

@pytest.fixture
def utils_fixture():
    return "utils"
"#,
    )
    .ok();

    db.scan_workspace(&temp_dir);

    // Should find fixtures in test files and conftest
    assert!(db.definitions.get("conftest_fixture").is_some());
    assert!(db.definitions.get("test_file_fixture").is_some());
    assert!(db.definitions.get("suffix_test_fixture").is_some());
    // Should not find fixtures in non-test files
    assert!(db.definitions.get("utils_fixture").is_none());

    std::fs::remove_dir_all(&temp_dir).ok();
}

#[test]
#[timeout(30000)]
fn test_empty_conftest_file() {
    let db = FixtureDatabase::new();

    // Analyze empty conftest
    let content = "";
    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path, content);

    // Should not crash, should have no fixtures
    assert!(db.definitions.is_empty());
}

#[test]
#[timeout(30000)]
fn test_conftest_with_only_imports() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
import sys
from pathlib import Path
"#;
    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path, content);

    // Should not crash, should have no fixtures
    assert!(db.definitions.is_empty());
}

#[test]
#[timeout(30000)]
fn test_file_with_syntax_error_in_docstring() {
    let db = FixtureDatabase::new();

    // Python file with weird but valid docstring
    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    """
    This docstring has "quotes" and 'apostrophes'
    And some special chars: @#$%^&*()
    """
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    // Docstring should be preserved
    assert!(defs[0].docstring.is_some());
}

#[test]
#[timeout(30000)]
fn test_fixture_in_file_with_multiple_encodings_declared() {
    let db = FixtureDatabase::new();

    // File with encoding declaration
    let content = r#"# -*- coding: utf-8 -*-
import pytest

@pytest.fixture
def my_fixture():
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

// MARK: Docstring Variation Tests

#[test]
#[timeout(30000)]
fn test_fixture_with_empty_docstring() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    """"""
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    // Empty docstring might be None or Some("")
    if let Some(doc) = &defs[0].docstring {
        assert!(doc.trim().is_empty());
    }
}

#[test]
#[timeout(30000)]
fn test_fixture_with_multiline_docstring() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    """
    This is a multi-line docstring.

    It has multiple paragraphs.

    Args:
        None

    Returns:
        str: A test string
    """
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert!(defs[0].docstring.is_some());
    let docstring = defs[0].docstring.as_ref().unwrap();
    assert!(docstring.contains("multi-line"));
    assert!(docstring.contains("Returns:"));
}

#[test]
#[timeout(30000)]
fn test_fixture_with_single_quoted_docstring() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    '''Single quoted docstring'''
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert!(defs[0].docstring.is_some());
    assert_eq!(
        defs[0].docstring.as_ref().unwrap().trim(),
        "Single quoted docstring"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_with_rst_formatted_docstring() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    """
    Fixture with RST formatting.

    :param param1: First parameter
    :type param1: str
    :returns: Test value
    :rtype: str

    .. note::
        This is a note block
    """
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert!(defs[0].docstring.is_some());
    let docstring = defs[0].docstring.as_ref().unwrap();
    assert!(docstring.contains(":param"));
    assert!(docstring.contains(".. note::"));
}

#[test]
#[timeout(30000)]
fn test_fixture_with_google_style_docstring() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    """Fixture with Google-style docstring.

    This fixture provides a test value.

    Args:
        None

    Returns:
        str: A test string value

    Yields:
        str: Test value for the fixture
    """
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert!(defs[0].docstring.is_some());
    let docstring = defs[0].docstring.as_ref().unwrap();
    assert!(docstring.contains("Args:"));
    assert!(docstring.contains("Returns:"));
    assert!(docstring.contains("Yields:"));
}

#[test]
#[timeout(30000)]
fn test_fixture_with_numpy_style_docstring() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    """
    Fixture with NumPy-style docstring.

    Parameters
    ----------
    None

    Returns
    -------
    str
        A test string value

    Notes
    -----
    This is a test fixture
    """
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert!(defs[0].docstring.is_some());
    let docstring = defs[0].docstring.as_ref().unwrap();
    assert!(docstring.contains("Parameters"));
    assert!(docstring.contains("----------"));
    assert!(docstring.contains("Returns"));
}

#[test]
#[timeout(30000)]
fn test_fixture_with_unicode_in_docstring() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    """
    Fixture with Unicode characters: 日本語, Русский, العربية, 🎉

    This tests international character support in docstrings.
    """
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert!(defs[0].docstring.is_some());
    let docstring = defs[0].docstring.as_ref().unwrap();
    assert!(docstring.contains("日本語"));
    assert!(docstring.contains("Русский"));
    assert!(docstring.contains("🎉"));
}

#[test]
#[timeout(30000)]
fn test_fixture_with_code_blocks_in_docstring() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    """
    Fixture with code examples.

    Example:
        >>> result = my_fixture()
        >>> assert result == "test"

    Code block:
        ```python
        def use_fixture(my_fixture):
            print(my_fixture)
        ```
    """
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert!(defs[0].docstring.is_some());
    let docstring = defs[0].docstring.as_ref().unwrap();
    assert!(docstring.contains(">>>"));
    assert!(docstring.contains("```python"));
}

// MARK: Performance and Scalability Tests

#[test]
#[timeout(30000)]
fn test_large_number_of_fixtures_in_single_file() {
    let db = FixtureDatabase::new();

    // Generate a file with 100 fixtures
    let mut content = String::from("import pytest\n\n");
    for i in 0..100 {
        content.push_str(&format!(
            "@pytest.fixture\ndef fixture_{}():\n    return {}\n\n",
            i, i
        ));
    }

    let path = PathBuf::from("/tmp/test/test_many_fixtures.py");
    db.analyze_file(path, &content);

    // Should have all 100 fixtures
    assert_eq!(db.definitions.len(), 100);

    // Verify a few specific ones
    assert!(db.definitions.get("fixture_0").is_some());
    assert!(db.definitions.get("fixture_50").is_some());
    assert!(db.definitions.get("fixture_99").is_some());
}

#[test]
#[timeout(30000)]
fn test_deeply_nested_fixture_dependencies() {
    let db = FixtureDatabase::new();

    // Create a chain of 20 fixtures depending on each other
    let mut content = String::from("import pytest\n\n");
    content.push_str("@pytest.fixture\ndef fixture_0():\n    return 0\n\n");

    for i in 1..20 {
        content.push_str(&format!(
            "@pytest.fixture\ndef fixture_{}(fixture_{}):\n    return {} + fixture_{}\n\n",
            i,
            i - 1,
            i,
            i - 1
        ));
    }

    let path = PathBuf::from("/tmp/test/test_deep_chain.py");
    db.analyze_file(path, &content);

    // Should detect all fixtures
    assert_eq!(db.definitions.len(), 20);

    // Verify the deepest one
    let deepest = db.definitions.get("fixture_19").unwrap();
    assert_eq!(deepest.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_many_parameters() {
    let db = FixtureDatabase::new();

    // Create fixtures first
    let mut content = String::from("import pytest\n\n");
    for i in 0..15 {
        content.push_str(&format!(
            "@pytest.fixture\ndef dep_{}():\n    return {}\n\n",
            i, i
        ));
    }

    // Create a fixture that depends on all of them
    content.push_str("@pytest.fixture\ndef mega_fixture(");
    for i in 0..15 {
        if i > 0 {
            content.push_str(", ");
        }
        content.push_str(&format!("dep_{}", i));
    }
    content.push_str("):\n    return sum([");
    for i in 0..15 {
        if i > 0 {
            content.push_str(", ");
        }
        content.push_str(&format!("dep_{}", i));
    }
    content.push_str("])\n");

    let path = PathBuf::from("/tmp/test/test_many_params.py");
    db.analyze_file(path, &content);

    // Should have all 16 fixtures (15 deps + 1 mega)
    assert_eq!(db.definitions.len(), 16);
    assert!(db.definitions.get("mega_fixture").is_some());
}

#[test]
#[timeout(30000)]
fn test_very_long_fixture_function_body() {
    let db = FixtureDatabase::new();

    // Create a fixture with a very long function body (100 lines)
    let mut content = String::from("import pytest\n\n@pytest.fixture\ndef long_fixture():\n");
    content.push_str("    \"\"\"A fixture with a very long body.\"\"\"\n");
    for i in 0..100 {
        content.push_str(&format!("    line_{} = {}\n", i, i));
    }
    content.push_str("    return line_99\n");

    let path = PathBuf::from("/tmp/test/test_long_function.py");
    db.analyze_file(path, &content);

    let defs = db.definitions.get("long_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert!(defs[0].docstring.is_some());
}

#[test]
#[timeout(30000)]
fn test_multiple_files_with_same_fixture_names() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "value"
"#;

    // Analyze the same fixture in 50 different files
    for i in 0..50 {
        let path = PathBuf::from(format!("/tmp/test/dir_{}/test_file.py", i));
        db.analyze_file(path, content);
    }

    // Should have one fixture name with 50 definitions
    let defs = db.definitions.get("shared_fixture").unwrap();
    assert_eq!(defs.len(), 50);
}

#[test]
#[timeout(30000)]
fn test_rapid_file_updates() {
    let db = FixtureDatabase::new();

    let path = PathBuf::from("/tmp/test/test_updates.py");

    // Simulate rapid updates to the same file
    for i in 0..20 {
        let content = format!(
            r#"
import pytest

@pytest.fixture
def dynamic_fixture():
    return {}
"#,
            i
        );
        db.analyze_file(path.clone(), &content);
    }

    // Should have just one definition (the latest update)
    let defs = db.definitions.get("dynamic_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert_eq!(defs[0].file_path, path);
}

// MARK: Virtual Environment Variation Tests

#[test]
#[timeout(30000)]
fn test_fixture_detection_without_venv() {
    let db = FixtureDatabase::new();

    // Create a test project without scanning venv
    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test"

def test_example(my_fixture):
    assert my_fixture == "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path.clone(), content);

    // Should still work without venv
    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);

    let usages = db.usages.get(&path).unwrap();
    assert_eq!(usages.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_third_party_fixture_in_site_packages() {
    let db = FixtureDatabase::new();

    // Simulate a third-party plugin fixture
    let plugin_content = r#"
import pytest

@pytest.fixture
def third_party_fixture():
    """A fixture from a third-party plugin."""
    return "plugin_value"
"#;

    // Analyze as if it's from site-packages
    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_plugin/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    // Now use it in a test file
    let test_content = r#"
def test_example(third_party_fixture):
    assert third_party_fixture == "plugin_value"
"#;
    let test_path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should detect both definition and usage
    let defs = db.definitions.get("third_party_fixture").unwrap();
    assert_eq!(defs.len(), 1);

    let usages = db.usages.get(&test_path).unwrap();
    assert_eq!(usages.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_override_from_third_party() {
    let db = FixtureDatabase::new();

    // Third-party plugin defines a fixture
    let plugin_content = r#"
import pytest

@pytest.fixture
def event_loop():
    """Plugin event loop fixture."""
    return "plugin_loop"
"#;
    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_asyncio/fixtures.py");
    db.analyze_file(plugin_path.clone(), plugin_content);

    // User overrides it in conftest.py
    let conftest_content = r#"
import pytest

@pytest.fixture
def event_loop():
    """Custom event loop fixture."""
    return "custom_loop"
"#;
    let conftest_path = PathBuf::from("/tmp/project/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    // Test uses it
    let test_content = r#"
def test_example(event_loop):
    assert event_loop is not None
"#;
    let test_path = PathBuf::from("/tmp/project/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // Should have 2 definitions (plugin + override)
    let defs = db.definitions.get("event_loop").unwrap();
    assert_eq!(defs.len(), 2);

    // Verify the conftest definition is present
    let conftest_def = defs.iter().find(|d| d.file_path == conftest_path);
    assert!(conftest_def.is_some());

    // Verify the plugin definition is present
    let plugin_def = defs.iter().find(|d| d.file_path == plugin_path);
    assert!(plugin_def.is_some());
}

#[test]
#[timeout(30000)]
fn test_multiple_third_party_plugins_same_fixture() {
    let db = FixtureDatabase::new();

    // Plugin 1 defines a fixture
    let plugin1_content = r#"
import pytest

@pytest.fixture
def common_fixture():
    return "plugin1"
"#;
    let plugin1_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_plugin1/fixtures.py");
    db.analyze_file(plugin1_path, plugin1_content);

    // Plugin 2 also defines the same fixture name
    let plugin2_content = r#"
import pytest

@pytest.fixture
def common_fixture():
    return "plugin2"
"#;
    let plugin2_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_plugin2/fixtures.py");
    db.analyze_file(plugin2_path, plugin2_content);

    // Should have both definitions
    let defs = db.definitions.get("common_fixture").unwrap();
    assert_eq!(defs.len(), 2);
}

#[test]
#[timeout(30000)]
fn test_venv_fixture_with_no_usage() {
    let db = FixtureDatabase::new();

    // Third-party fixture that's never used
    let plugin_content = r#"
import pytest

@pytest.fixture
def unused_plugin_fixture():
    """A fixture that's defined but never used."""
    return "unused"
"#;
    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_plugin/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    // Should still be in definitions
    let defs = db.definitions.get("unused_plugin_fixture").unwrap();
    assert_eq!(defs.len(), 1);

    // Should have no usages
    let refs = db.find_fixture_references("unused_plugin_fixture");
    assert!(refs.is_empty());
}

// MARK: Miscellaneous Edge Case Tests

#[test]
#[timeout(30000)]
fn test_fixture_with_property_decorator() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

class MyFixture:
    @property
    def value(self):
        return "test"

@pytest.fixture
def my_fixture():
    return MyFixture()
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_staticmethod() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

class FixtureHelper:
    @staticmethod
    def create():
        return "test"

@pytest.fixture
def my_fixture():
    return FixtureHelper.create()
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_classmethod() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

class FixtureHelper:
    @classmethod
    def create(cls):
        return "test"

@pytest.fixture
def my_fixture():
    return FixtureHelper.create()
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_contextmanager() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from contextlib import contextmanager

@contextmanager
def resource():
    yield "resource"

@pytest.fixture
def my_fixture():
    with resource() as r:
        return r
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_multiple_decorators() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def custom_decorator(func):
    return func

@pytest.fixture
@custom_decorator
def my_fixture():
    return "test"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_inside_if_block_not_supported() {
    let db = FixtureDatabase::new();

    // Fixtures inside if blocks are a known limitation
    let content = r#"
import pytest
import sys

if sys.version_info >= (3, 8):
    @pytest.fixture
    def version_specific_fixture():
        return "py38+"
"#;
    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path, content);

    // Currently not detected - this is a known limitation
    assert!(db.definitions.get("version_specific_fixture").is_none());
}

#[test]
#[timeout(30000)]
fn test_fixture_with_walrus_operator_in_body() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    if (result := expensive_operation()):
        return result
    return None
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_match_statement() {
    let db = FixtureDatabase::new();

    // Python 3.10+ match statement
    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    value = "test"
    match value:
        case "test":
            return "matched"
        case _:
            return "default"
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_exception_group() {
    let db = FixtureDatabase::new();

    // Python 3.11+ exception groups
    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    try:
        return "test"
    except* ValueError as e:
        return None
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_dataclass() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from dataclasses import dataclass

@dataclass
class Config:
    value: str

@pytest.fixture
def config_fixture():
    return Config(value="test")
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("config_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_named_tuple() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from typing import NamedTuple

class Point(NamedTuple):
    x: int
    y: int

@pytest.fixture
def point_fixture():
    return Point(1, 2)
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("point_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_protocol() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from typing import Protocol

class Readable(Protocol):
    def read(self) -> str: ...

@pytest.fixture
def readable_fixture() -> Readable:
    class TextReader:
        def read(self) -> str:
            return "test"
    return TextReader()
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("readable_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

#[test]
#[timeout(30000)]
fn test_fixture_with_generic_type() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from typing import Generic, TypeVar

T = TypeVar('T')

class Container(Generic[T]):
    def __init__(self, value: T):
        self.value = value

@pytest.fixture
def container_fixture() -> Container[str]:
    return Container("test")
"#;
    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path, content);

    let defs = db.definitions.get("container_fixture").unwrap();
    assert_eq!(defs.len(), 1);
}

// MARK: Additional Third-Party Plugin Tests

#[test]
#[timeout(30000)]
fn test_pytest_flask_fixtures() {
    let db = FixtureDatabase::new();

    // Simulate pytest-flask plugin fixtures
    let plugin_content = r#"
import pytest

@pytest.fixture
def app():
    """Flask application fixture."""
    from flask import Flask
    app = Flask(__name__)
    return app

@pytest.fixture
def client(app):
    """Flask test client fixture."""
    return app.test_client()
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_flask/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    // Should detect both fixtures
    assert!(db.definitions.get("app").is_some());
    assert!(db.definitions.get("client").is_some());
}

#[test]
#[timeout(30000)]
fn test_pytest_httpx_fixtures() {
    let db = FixtureDatabase::new();

    let plugin_content = r#"
import pytest

@pytest.fixture
async def async_client():
    """HTTPX async client fixture."""
    import httpx
    async with httpx.AsyncClient() as client:
        yield client
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_httpx/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    assert!(db.definitions.get("async_client").is_some());
}

#[test]
#[timeout(30000)]
fn test_pytest_postgresql_fixtures() {
    let db = FixtureDatabase::new();

    let plugin_content = r#"
import pytest

@pytest.fixture
def postgresql():
    """PostgreSQL database fixture."""
    return {"host": "localhost", "port": 5432}

@pytest.fixture
def postgresql_proc(postgresql):
    """PostgreSQL process fixture."""
    return postgresql
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_postgresql/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    assert!(db.definitions.get("postgresql").is_some());
    assert!(db.definitions.get("postgresql_proc").is_some());
}

#[test]
#[timeout(30000)]
fn test_pytest_docker_fixtures() {
    let db = FixtureDatabase::new();

    let plugin_content = r#"
import pytest

@pytest.fixture(scope="session")
def docker_compose_file():
    """Docker compose file fixture."""
    return "docker-compose.yml"

@pytest.fixture(scope="session")
def docker_services(docker_compose_file):
    """Docker services fixture."""
    return {"web": "http://localhost:8000"}
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_docker/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    assert!(db.definitions.get("docker_compose_file").is_some());
    assert!(db.definitions.get("docker_services").is_some());
}

#[test]
#[timeout(30000)]
fn test_pytest_factoryboy_fixtures() {
    let db = FixtureDatabase::new();

    let plugin_content = r#"
import pytest
import factory

class UserFactory(factory.Factory):
    class Meta:
        model = dict

    username = "testuser"
    email = "test@example.com"

@pytest.fixture
def user_factory():
    """User factory fixture."""
    return UserFactory
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_factoryboy/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    assert!(db.definitions.get("user_factory").is_some());
}

#[test]
#[timeout(30000)]
fn test_pytest_freezegun_fixtures() {
    let db = FixtureDatabase::new();

    let plugin_content = r#"
import pytest
from freezegun import freeze_time

@pytest.fixture
def frozen_time():
    """Frozen time fixture."""
    with freeze_time("2024-01-01"):
        yield
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_freezegun/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    assert!(db.definitions.get("frozen_time").is_some());
}

#[test]
#[timeout(30000)]
fn test_pytest_celery_fixtures() {
    let db = FixtureDatabase::new();

    let plugin_content = r#"
import pytest

@pytest.fixture(scope="session")
def celery_config():
    """Celery configuration fixture."""
    return {"broker_url": "redis://localhost:6379"}

@pytest.fixture
def celery_app(celery_config):
    """Celery application fixture."""
    from celery import Celery
    return Celery("test_app", **celery_config)

@pytest.fixture
def celery_worker(celery_app):
    """Celery worker fixture."""
    return celery_app.Worker()
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_celery/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    assert!(db.definitions.get("celery_config").is_some());
    assert!(db.definitions.get("celery_app").is_some());
    assert!(db.definitions.get("celery_worker").is_some());
}

#[test]
#[timeout(30000)]
fn test_pytest_aiohttp_fixtures() {
    let db = FixtureDatabase::new();

    let plugin_content = r#"
import pytest

@pytest.fixture
async def aiohttp_client():
    """Aiohttp client fixture."""
    import aiohttp
    async with aiohttp.ClientSession() as session:
        yield session

@pytest.fixture
async def aiohttp_server():
    """Aiohttp server fixture."""
    from aiohttp import web
    app = web.Application()
    return app
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_aiohttp/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    assert!(db.definitions.get("aiohttp_client").is_some());
    assert!(db.definitions.get("aiohttp_server").is_some());
}

#[test]
#[timeout(30000)]
fn test_pytest_benchmark_fixtures() {
    let db = FixtureDatabase::new();

    let plugin_content = r#"
import pytest

@pytest.fixture
def benchmark():
    """Benchmark fixture."""
    class Benchmark:
        def __call__(self, func):
            return func()
    return Benchmark()
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_benchmark/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    assert!(db.definitions.get("benchmark").is_some());
}

#[test]
#[timeout(30000)]
fn test_pytest_playwright_fixtures() {
    let db = FixtureDatabase::new();

    let plugin_content = r#"
import pytest

@pytest.fixture(scope="session")
def browser():
    """Playwright browser fixture."""
    from playwright.sync_api import sync_playwright
    with sync_playwright() as p:
        yield p.chromium.launch()

@pytest.fixture
def page(browser):
    """Playwright page fixture."""
    page = browser.new_page()
    yield page
    page.close()

@pytest.fixture
def context(browser):
    """Playwright browser context fixture."""
    context = browser.new_context()
    yield context
    context.close()
"#;

    let plugin_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_playwright/fixtures.py");
    db.analyze_file(plugin_path, plugin_content);

    assert!(db.definitions.get("browser").is_some());
    assert!(db.definitions.get("page").is_some());
    assert!(db.definitions.get("context").is_some());
}

// =============================================================================
// Tests for keyword-only and positional-only fixture arguments
// =============================================================================

#[test]
#[timeout(30000)]
fn test_keyword_only_fixture_usage_in_test() {
    let db = FixtureDatabase::new();

    // Add a fixture in conftest
    let conftest_content = r#"
import pytest

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

    // Add a test that uses keyword-only argument (after *)
    let test_content = r#"
def test_with_kwonly(*, my_fixture):
    assert my_fixture == 42
"#;
    let test_path = PathBuf::from("/tmp/test_kwonly.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that the fixture usage was detected
    let usages = db.usages.get(&test_path);
    assert!(usages.is_some(), "Usages should be detected");
    let usages = usages.unwrap();
    assert!(
        usages.iter().any(|u| u.name == "my_fixture"),
        "Should detect my_fixture usage in keyword-only argument"
    );

    // Check that no undeclared fixtures were detected (the fixture is properly declared)
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect any undeclared fixtures for keyword-only arg"
    );
}

#[test]
#[timeout(30000)]
fn test_keyword_only_fixture_usage_with_type_annotation() {
    let db = FixtureDatabase::new();

    // Add a fixture in conftest
    let conftest_content = r#"
import pytest
from pathlib import Path

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

    // Add a test that uses keyword-only argument with type annotation (like in the issue)
    let test_content = r#"
from pathlib import Path

def test_run_command(*, tmp_path: Path) -> None:
    """Test that uses a keyword-only fixture with type annotation."""
    rst_file = tmp_path / "example.rst"
    assert rst_file.parent == tmp_path
"#;
    let test_path = PathBuf::from("/tmp/test_kwonly_typed.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that the fixture usage was detected
    let usages = db.usages.get(&test_path);
    assert!(usages.is_some(), "Usages should be detected");
    let usages = usages.unwrap();
    assert!(
        usages.iter().any(|u| u.name == "tmp_path"),
        "Should detect tmp_path usage in keyword-only argument"
    );

    // Check that no undeclared fixtures were detected
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect any undeclared fixtures for keyword-only arg with type annotation"
    );
}

#[test]
#[timeout(30000)]
fn test_positional_only_fixture_usage() {
    let db = FixtureDatabase::new();

    // Add a fixture in conftest
    let conftest_content = r#"
import pytest

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

    // Add a test that uses positional-only argument (before /)
    let test_content = r#"
def test_with_posonly(my_fixture, /):
    assert my_fixture == 42
"#;
    let test_path = PathBuf::from("/tmp/test_posonly.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that the fixture usage was detected
    let usages = db.usages.get(&test_path);
    assert!(usages.is_some(), "Usages should be detected");
    let usages = usages.unwrap();
    assert!(
        usages.iter().any(|u| u.name == "my_fixture"),
        "Should detect my_fixture usage in positional-only argument"
    );

    // Check that no undeclared fixtures were detected
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect any undeclared fixtures for positional-only arg"
    );
}

#[test]
#[timeout(30000)]
fn test_mixed_argument_types_fixture_usage() {
    let db = FixtureDatabase::new();

    // Add fixtures in conftest
    let conftest_content = r#"
import pytest

@pytest.fixture
def fixture_a():
    return "a"

@pytest.fixture
def fixture_b():
    return "b"

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

    // Add a test that uses all argument types: positional-only, regular, and keyword-only
    let test_content = r#"
def test_with_all_types(fixture_a, /, fixture_b, *, fixture_c):
    assert fixture_a == "a"
    assert fixture_b == "b"
    assert fixture_c == "c"
"#;
    let test_path = PathBuf::from("/tmp/test_mixed.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that all fixture usages were detected
    let usages = db.usages.get(&test_path);
    assert!(usages.is_some(), "Usages should be detected");
    let usages = usages.unwrap();
    assert!(
        usages.iter().any(|u| u.name == "fixture_a"),
        "Should detect fixture_a usage in positional-only argument"
    );
    assert!(
        usages.iter().any(|u| u.name == "fixture_b"),
        "Should detect fixture_b usage in regular argument"
    );
    assert!(
        usages.iter().any(|u| u.name == "fixture_c"),
        "Should detect fixture_c usage in keyword-only argument"
    );

    // Check that no undeclared fixtures were detected
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect any undeclared fixtures for mixed argument types"
    );
}

#[test]
#[timeout(30000)]
fn test_keyword_only_fixture_in_fixture_definition() {
    let db = FixtureDatabase::new();

    // Add fixtures in conftest - one depends on another via keyword-only arg
    let conftest_content = r#"
import pytest

@pytest.fixture
def base_fixture():
    return 42

@pytest.fixture
def dependent_fixture(*, base_fixture):
    return base_fixture * 2
"#;
    let conftest_path = PathBuf::from("/tmp/conftest.py");
    db.analyze_file(conftest_path.clone(), conftest_content);

    // Check that both fixtures were detected
    assert!(
        db.definitions.contains_key("base_fixture"),
        "base_fixture should be detected"
    );
    assert!(
        db.definitions.contains_key("dependent_fixture"),
        "dependent_fixture should be detected"
    );

    // Check that the usage of base_fixture in dependent_fixture was detected
    let usages = db.usages.get(&conftest_path);
    assert!(usages.is_some(), "Usages should be detected");
    let usages = usages.unwrap();
    assert!(
        usages.iter().any(|u| u.name == "base_fixture"),
        "Should detect base_fixture usage as keyword-only dependency in dependent_fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_keyword_only_with_multiple_fixtures() {
    let db = FixtureDatabase::new();

    // Add fixtures in conftest
    let conftest_content = r#"
import pytest

@pytest.fixture
def fixture_x():
    return "x"

@pytest.fixture
def fixture_y():
    return "y"

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

    // Add a test with multiple keyword-only fixtures
    let test_content = r#"
def test_multi_kwonly(*, fixture_x, fixture_y, fixture_z):
    assert fixture_x == "x"
    assert fixture_y == "y"
    assert fixture_z == "z"
"#;
    let test_path = PathBuf::from("/tmp/test_multi_kwonly.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that all fixture usages were detected
    let usages = db.usages.get(&test_path);
    assert!(usages.is_some(), "Usages should be detected");
    let usages = usages.unwrap();
    assert!(
        usages.iter().any(|u| u.name == "fixture_x"),
        "Should detect fixture_x usage"
    );
    assert!(
        usages.iter().any(|u| u.name == "fixture_y"),
        "Should detect fixture_y usage"
    );
    assert!(
        usages.iter().any(|u| u.name == "fixture_z"),
        "Should detect fixture_z usage"
    );

    // Check that no undeclared fixtures were detected
    let undeclared = db.get_undeclared_fixtures(&test_path);
    assert_eq!(
        undeclared.len(),
        0,
        "Should not detect any undeclared fixtures"
    );
}

#[test]
#[timeout(30000)]
fn test_go_to_definition_for_keyword_only_fixture() {
    let db = FixtureDatabase::new();

    // Set up conftest.py with a fixture
    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.clone(), conftest_content);

    // Set up a test file that uses the fixture as keyword-only
    let test_content = r#"
def test_something(*, my_fixture):
    assert my_fixture == 42
"#;
    let test_path = PathBuf::from("/tmp/test/test_kwonly.py");
    db.analyze_file(test_path.clone(), test_content);

    // Verify fixture usage was detected
    let usages = db.usages.get(&test_path);
    assert!(usages.is_some(), "Usages should be detected");
    let usages = usages.unwrap();
    let fixture_usage = usages.iter().find(|u| u.name == "my_fixture");
    assert!(
        fixture_usage.is_some(),
        "Should detect my_fixture usage in keyword-only position"
    );

    // Get the line and character position of the usage
    let usage = fixture_usage.unwrap();

    // Try to find the definition from the test file at the usage position
    // usage.line is 1-based, but find_fixture_definition expects 0-based LSP coordinates
    let definition =
        db.find_fixture_definition(&test_path, (usage.line - 1) as u32, usage.start_char as u32);

    assert!(definition.is_some(), "Definition should be found");
    let def = definition.unwrap();
    assert_eq!(def.name, "my_fixture");
    assert_eq!(def.file_path, conftest_path);
}

// =============================================================================
// Tests for directory filtering during workspace scanning
// =============================================================================

#[test]
#[timeout(30000)]
fn test_scan_skips_node_modules() {
    use std::fs;
    use tempfile::TempDir;

    let temp_dir = TempDir::new().unwrap();
    let root = temp_dir.path();

    // Create a test file in root
    let root_test = root.join("test_root.py");
    fs::write(
        &root_test,
        r#"
def test_root(root_fixture):
    pass
"#,
    )
    .unwrap();

    // Create conftest in root
    let root_conftest = root.join("conftest.py");
    fs::write(
        &root_conftest,
        r#"
import pytest

@pytest.fixture
def root_fixture():
    return 1
"#,
    )
    .unwrap();

    // Create node_modules with a test file that should be skipped
    let node_modules = root.join("node_modules");
    fs::create_dir_all(&node_modules).unwrap();
    let node_test = node_modules.join("test_node.py");
    fs::write(
        &node_test,
        r#"
def test_node(node_fixture):
    pass
"#,
    )
    .unwrap();
    let node_conftest = node_modules.join("conftest.py");
    fs::write(
        &node_conftest,
        r#"
import pytest

@pytest.fixture
def node_fixture():
    return 2
"#,
    )
    .unwrap();

    let db = FixtureDatabase::new();
    db.scan_workspace(root);

    // Should find root_fixture but not node_fixture
    assert!(
        db.definitions.contains_key("root_fixture"),
        "root_fixture should be found"
    );
    assert!(
        !db.definitions.contains_key("node_fixture"),
        "node_fixture should NOT be found (node_modules should be skipped)"
    );
}

#[test]
#[timeout(30000)]
fn test_scan_skips_git_directory() {
    use std::fs;
    use tempfile::TempDir;

    let temp_dir = TempDir::new().unwrap();
    let root = temp_dir.path();

    // Create a test file in root
    let root_conftest = root.join("conftest.py");
    fs::write(
        &root_conftest,
        r#"
import pytest

@pytest.fixture
def real_fixture():
    return 1
"#,
    )
    .unwrap();

    // Create .git with a conftest.py that should be skipped
    let git_dir = root.join(".git");
    fs::create_dir_all(&git_dir).unwrap();
    let git_conftest = git_dir.join("conftest.py");
    fs::write(
        &git_conftest,
        r#"
import pytest

@pytest.fixture
def git_fixture():
    return 2
"#,
    )
    .unwrap();

    let db = FixtureDatabase::new();
    db.scan_workspace(root);

    // Should find real_fixture but not git_fixture
    assert!(
        db.definitions.contains_key("real_fixture"),
        "real_fixture should be found"
    );
    assert!(
        !db.definitions.contains_key("git_fixture"),
        "git_fixture should NOT be found (.git should be skipped)"
    );
}

#[test]
#[timeout(30000)]
fn test_scan_skips_pycache() {
    use std::fs;
    use tempfile::TempDir;

    let temp_dir = TempDir::new().unwrap();
    let root = temp_dir.path();

    // Create a test file in root
    let root_conftest = root.join("conftest.py");
    fs::write(
        &root_conftest,
        r#"
import pytest

@pytest.fixture
def actual_fixture():
    return 1
"#,
    )
    .unwrap();

    // Create __pycache__ with a conftest.py that should be skipped
    let pycache = root.join("__pycache__");
    fs::create_dir_all(&pycache).unwrap();
    let cache_conftest = pycache.join("conftest.py");
    fs::write(
        &cache_conftest,
        r#"
import pytest

@pytest.fixture
def cache_fixture():
    return 2
"#,
    )
    .unwrap();

    let db = FixtureDatabase::new();
    db.scan_workspace(root);

    // Should find actual_fixture but not cache_fixture
    assert!(
        db.definitions.contains_key("actual_fixture"),
        "actual_fixture should be found"
    );
    assert!(
        !db.definitions.contains_key("cache_fixture"),
        "cache_fixture should NOT be found (__pycache__ should be skipped)"
    );
}

#[test]
#[timeout(30000)]
fn test_scan_skips_venv_but_scans_plugins() {
    use std::fs;
    use tempfile::TempDir;

    let temp_dir = TempDir::new().unwrap();
    let root = temp_dir.path();

    // Create a test file in root
    let root_conftest = root.join("conftest.py");
    fs::write(
        &root_conftest,
        r#"
import pytest

@pytest.fixture
def project_fixture():
    return 1
"#,
    )
    .unwrap();

    // Create .venv with a random test file that should be skipped during main scan
    let venv = root.join(".venv");
    fs::create_dir_all(&venv).unwrap();
    let venv_test = venv.join("test_venv.py");
    fs::write(
        &venv_test,
        r#"
def test_venv(venv_fixture):
    pass
"#,
    )
    .unwrap();

    let db = FixtureDatabase::new();
    db.scan_workspace(root);

    // Should find project_fixture
    assert!(
        db.definitions.contains_key("project_fixture"),
        "project_fixture should be found"
    );

    // venv test files should not create usages in the main scan
    // (venv is scanned separately for plugin fixtures only)
    let venv_test_path = venv_test.canonicalize().unwrap_or(venv_test);
    assert!(
        !db.usages.contains_key(&venv_test_path),
        "test files in .venv should not be scanned"
    );
}

#[test]
#[timeout(30000)]
fn test_scan_skips_multiple_directories() {
    use std::fs;
    use tempfile::TempDir;

    let temp_dir = TempDir::new().unwrap();
    let root = temp_dir.path();

    // Create a test file in root
    let root_conftest = root.join("conftest.py");
    fs::write(
        &root_conftest,
        r#"
import pytest

@pytest.fixture
def main_fixture():
    return 1
"#,
    )
    .unwrap();

    // Create multiple directories that should be skipped
    for skip_dir in &[
        "node_modules",
        ".git",
        "__pycache__",
        ".pytest_cache",
        ".mypy_cache",
        "build",
        "dist",
        ".tox",
    ] {
        let dir = root.join(skip_dir);
        fs::create_dir_all(&dir).unwrap();
        let conftest = dir.join("conftest.py");
        fs::write(
            &conftest,
            format!(
                r#"
import pytest

@pytest.fixture
def {}_fixture():
    return 2
"#,
                skip_dir.replace(".", "").replace("-", "_")
            ),
        )
        .unwrap();
    }

    let db = FixtureDatabase::new();
    db.scan_workspace(root);

    // Should only find main_fixture
    assert!(
        db.definitions.contains_key("main_fixture"),
        "main_fixture should be found"
    );

    // None of the skipped directory fixtures should be found
    assert!(
        !db.definitions.contains_key("node_modules_fixture"),
        "node_modules fixture should be skipped"
    );
    assert!(
        !db.definitions.contains_key("git_fixture"),
        ".git fixture should be skipped"
    );
    assert!(
        !db.definitions.contains_key("__pycache___fixture"),
        "__pycache__ fixture should be skipped"
    );
    assert!(
        !db.definitions.contains_key("pytest_cache_fixture"),
        ".pytest_cache fixture should be skipped"
    );
}

#[test]
#[timeout(30000)]
fn test_scan_skips_nested_node_modules() {
    use std::fs;
    use tempfile::TempDir;

    let temp_dir = TempDir::new().unwrap();
    let root = temp_dir.path();

    // Create a test file in root
    let root_conftest = root.join("conftest.py");
    fs::write(
        &root_conftest,
        r#"
import pytest

@pytest.fixture
def root_fix():
    return 1
"#,
    )
    .unwrap();

    // Create a tests directory with a test file (should be scanned)
    let tests_dir = root.join("tests");
    fs::create_dir_all(&tests_dir).unwrap();
    let tests_conftest = tests_dir.join("conftest.py");
    fs::write(
        &tests_conftest,
        r#"
import pytest

@pytest.fixture
def tests_fix():
    return 2
"#,
    )
    .unwrap();

    // Create deeply nested node_modules (should be skipped entirely)
    let deep_node = root.join("frontend/app/node_modules/some_package");
    fs::create_dir_all(&deep_node).unwrap();
    let deep_conftest = deep_node.join("conftest.py");
    fs::write(
        &deep_conftest,
        r#"
import pytest

@pytest.fixture
def deep_node_fix():
    return 3
"#,
    )
    .unwrap();

    let db = FixtureDatabase::new();
    db.scan_workspace(root);

    // Should find root and tests fixtures
    assert!(
        db.definitions.contains_key("root_fix"),
        "root_fix should be found"
    );
    assert!(
        db.definitions.contains_key("tests_fix"),
        "tests_fix should be found"
    );

    // Should NOT find deeply nested node_modules fixture
    assert!(
        !db.definitions.contains_key("deep_node_fix"),
        "deep_node_fix should NOT be found (nested node_modules should be skipped)"
    );
}

// =============================================================================
// pytest.mark.usefixtures tests
// =============================================================================

#[test]
#[timeout(30000)]
fn test_usefixtures_decorator_on_function() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def db_connection():
    return "connection"

@pytest.fixture
def auth_user():
    return "user"
"#;

    let test_content = r#"
import pytest

@pytest.mark.usefixtures("db_connection")
def test_with_usefixtures():
    pass

@pytest.mark.usefixtures("db_connection", "auth_user")
def test_with_multiple_usefixtures():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_usefixtures/conftest.py");
    let test_path = PathBuf::from("/tmp/test_usefixtures/test_example.py");

    db.analyze_file(conftest_path, conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Check that usefixtures usages were detected
    let usages = db.usages.get(&test_path).unwrap();

    assert!(
        usages.iter().any(|u| u.name == "db_connection"),
        "db_connection should be detected as usage from usefixtures"
    );
    assert!(
        usages.iter().any(|u| u.name == "auth_user"),
        "auth_user should be detected as usage from usefixtures"
    );

    // Count occurrences - db_connection should appear twice (once for each test)
    let db_conn_count = usages.iter().filter(|u| u.name == "db_connection").count();
    assert_eq!(
        db_conn_count, 2,
        "db_connection should be used twice (once in each test)"
    );
}

#[test]
#[timeout(30000)]
fn test_usefixtures_decorator_on_class() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def setup_database():
    return "db"
"#;

    let test_content = r#"
import pytest

@pytest.mark.usefixtures("setup_database")
class TestWithSetup:
    def test_first(self):
        pass

    def test_second(self):
        pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_usefixtures/conftest.py");
    let test_path = PathBuf::from("/tmp/test_usefixtures/test_class.py");

    db.analyze_file(conftest_path, conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Check that usefixtures usage on class was detected
    let usages = db.usages.get(&test_path).unwrap();

    assert!(
        usages.iter().any(|u| u.name == "setup_database"),
        "setup_database should be detected as usage from class usefixtures"
    );
}

#[test]
#[timeout(30000)]
fn test_usefixtures_goto_definition() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42
"#;

    let test_content = r#"
import pytest

@pytest.mark.usefixtures("my_fixture")
def test_something():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_usefixtures/conftest.py");
    let test_path = PathBuf::from("/tmp/test_usefixtures/test_goto.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // The fixture "my_fixture" in @pytest.mark.usefixtures("my_fixture") is on line 4 (1-indexed)
    // In 0-indexed LSP coords: line 3
    // Position is within the string "my_fixture"
    // @pytest.mark.usefixtures("my_fixture")
    //                          ^--- somewhere in the middle of the fixture name

    let definition = db.find_fixture_definition(&test_path, 3, 27);

    assert!(
        definition.is_some(),
        "Definition should be found for fixture used in usefixtures"
    );
    let def = definition.unwrap();
    assert_eq!(def.name, "my_fixture");
    assert_eq!(def.file_path, conftest_path);
}

#[test]
#[timeout(30000)]
fn test_usefixtures_affects_unused_detection() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def used_via_usefixtures():
    return "used"

@pytest.fixture
def actually_unused():
    return "unused"
"#;

    let test_content = r#"
import pytest

@pytest.mark.usefixtures("used_via_usefixtures")
def test_something():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_usefixtures/conftest.py");
    let test_path = PathBuf::from("/tmp/test_usefixtures/test_unused.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Get all usages across all files
    let mut all_usages: Vec<String> = Vec::new();
    for entry in db.usages.iter() {
        for usage in entry.value().iter() {
            all_usages.push(usage.name.clone());
        }
    }

    // used_via_usefixtures should be in usages (not unused)
    assert!(
        all_usages.contains(&"used_via_usefixtures".to_string()),
        "Fixture used via usefixtures should be tracked as used"
    );
}

#[test]
#[timeout(30000)]
fn test_usefixtures_with_mark_import() {
    let db = FixtureDatabase::new();

    let test_content = r#"
from pytest import mark, fixture

@fixture
def my_fix():
    return 1

@mark.usefixtures("my_fix")
def test_with_mark():
    pass
"#;

    let test_path = PathBuf::from("/tmp/test_usefixtures/test_mark.py");
    db.analyze_file(test_path.clone(), test_content);

    // Check that both the fixture definition and usage were detected
    assert!(
        db.definitions.contains_key("my_fix"),
        "my_fix fixture should be detected"
    );

    let usages = db.usages.get(&test_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "my_fix"),
        "my_fix should be detected as usage from mark.usefixtures"
    );
}

#[test]
#[timeout(30000)]
fn test_pytestmark_usefixtures_single() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def db_connection():
    return "connection"
"#;

    let test_content = r#"
import pytest

pytestmark = pytest.mark.usefixtures("db_connection")

def test_something():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_pytestmark/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pytestmark/test_single.py");

    db.analyze_file(conftest_path, conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let usages = db.usages.get(&test_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "db_connection"),
        "db_connection should be detected from pytestmark = pytest.mark.usefixtures(...)"
    );
}

#[test]
#[timeout(30000)]
fn test_pytestmark_usefixtures_list() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def db_connection():
    return "connection"

@pytest.fixture
def auth_user():
    return "user"
"#;

    let test_content = r#"
import pytest

pytestmark = [pytest.mark.usefixtures("db_connection", "auth_user"), pytest.mark.skip]

def test_something():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_pytestmark_list/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pytestmark_list/test_list.py");

    db.analyze_file(conftest_path, conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let usages = db.usages.get(&test_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "db_connection"),
        "db_connection should be detected from pytestmark list"
    );
    assert!(
        usages.iter().any(|u| u.name == "auth_user"),
        "auth_user should be detected from pytestmark list"
    );
}

#[test]
#[timeout(30000)]
fn test_pytestmark_usefixtures_tuple() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def fix1():
    return 1

@pytest.fixture
def fix2():
    return 2
"#;

    let test_content = r#"
import pytest

pytestmark = (pytest.mark.usefixtures("fix1"), pytest.mark.usefixtures("fix2"))

def test_something():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_pytestmark_tuple/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pytestmark_tuple/test_tuple.py");

    db.analyze_file(conftest_path, conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let usages = db.usages.get(&test_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "fix1"),
        "fix1 should be detected from pytestmark tuple"
    );
    assert!(
        usages.iter().any(|u| u.name == "fix2"),
        "fix2 should be detected from pytestmark tuple"
    );
}

#[test]
#[timeout(30000)]
fn test_pytestmark_usefixtures_in_class() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def setup_fixture():
    return "setup"

class TestWithPytestmark:
    pytestmark = [pytest.mark.usefixtures("setup_fixture")]

    def test_something(self):
        pass
"#;

    let file_path = PathBuf::from("/tmp/test_pytestmark_class/test_class.py");
    db.analyze_file(file_path.clone(), content);

    let usages = db.usages.get(&file_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "setup_fixture"),
        "setup_fixture should be detected from pytestmark inside class"
    );
}

#[test]
#[timeout(30000)]
fn test_pytestmark_usefixtures_affects_unused_detection() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def used_via_pytestmark():
    return "used"

@pytest.fixture
def truly_unused():
    return "unused"

pytestmark = pytest.mark.usefixtures("used_via_pytestmark")

def test_something():
    pass
"#;

    let file_path = PathBuf::from("/tmp/test_pytestmark_unused/test_unused.py");
    db.analyze_file(file_path.clone(), content);

    let unused = db.get_unused_fixtures();

    assert!(
        !unused.iter().any(|(_, name)| name == "used_via_pytestmark"),
        "used_via_pytestmark should NOT be in unused list"
    );
    assert!(
        unused.iter().any(|(_, name)| name == "truly_unused"),
        "truly_unused should be in unused list"
    );
}

#[test]
#[timeout(30000)]
fn test_pytestmark_usefixtures_annotated_assignment() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def db_connection():
    return "connection"

@pytest.fixture
def auth_user():
    return "user"
"#;

    let test_content = r#"
import pytest
from pytest import MarkDecorator

pytestmark: list[MarkDecorator] = [pytest.mark.usefixtures("db_connection", "auth_user")]

def test_something():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_pytestmark_annassign/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pytestmark_annassign/test_annotated.py");

    db.analyze_file(conftest_path, conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let usages = db.usages.get(&test_path).unwrap();
    assert!(
        usages.iter().any(|u| u.name == "db_connection"),
        "db_connection should be detected from annotated pytestmark assignment"
    );
    assert!(
        usages.iter().any(|u| u.name == "auth_user"),
        "auth_user should be detected from annotated pytestmark assignment"
    );
}

#[test]
#[timeout(30000)]
fn test_pytestmark_bare_annotation_no_panic() {
    // pytestmark: T  (no value) — should be handled without panic
    let db = FixtureDatabase::new();

    let content = r#"
import pytest
from pytest import MarkDecorator

pytestmark: list[MarkDecorator]

def test_something():
    pass
"#;

    let file_path = PathBuf::from("/tmp/test_pytestmark_bare_ann/test_bare.py");
    // Should not panic or error
    db.analyze_file(file_path.clone(), content);
}

// =============================================================================
// is_parameter flag tests
// =============================================================================

#[test]
#[timeout(30000)]
fn test_is_parameter_true_for_test_function_args() {
    // Function parameters must have is_parameter = true so that
    // code actions and inlay hints can add type annotations to them.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_db() -> str:
    return "db"

def test_uses_fixture(my_db):
    pass
"#;

    let path = PathBuf::from("/tmp/test_is_param/test_param_true.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).unwrap();
    let usage = usages
        .iter()
        .find(|u| u.name == "my_db")
        .expect("my_db usage should be detected");

    assert!(
        usage.is_parameter,
        "Test function parameter usages must have is_parameter = true"
    );
}

#[test]
#[timeout(30000)]
fn test_is_parameter_true_for_fixture_function_args() {
    // Parameters of fixture functions (fixture-on-fixture dependencies) must
    // also have is_parameter = true.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def base_db() -> str:
    return "db"

@pytest.fixture
def extended_db(base_db) -> str:
    return base_db + "_ext"
"#;

    let path = PathBuf::from("/tmp/test_is_param/test_fixture_param_true.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).unwrap();
    let usage = usages
        .iter()
        .find(|u| u.name == "base_db")
        .expect("base_db usage in extended_db should be detected");

    assert!(
        usage.is_parameter,
        "Fixture function parameter usages must have is_parameter = true"
    );
}

#[test]
#[timeout(30000)]
fn test_is_parameter_false_for_usefixtures_on_function() {
    // Fixtures referenced as strings inside @pytest.mark.usefixtures(...)
    // on a function must have is_parameter = false — they are not function
    // parameters and cannot receive a type annotation.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_db() -> str:
    return "db"

@pytest.mark.usefixtures("my_db")
def test_with_usefixtures():
    pass
"#;

    let path = PathBuf::from("/tmp/test_is_param/test_usefixtures_func.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).unwrap();
    let usage = usages
        .iter()
        .find(|u| u.name == "my_db")
        .expect("my_db usage from usefixtures should be detected");

    assert!(
        !usage.is_parameter,
        "usefixtures string usages on functions must have is_parameter = false"
    );
}

#[test]
#[timeout(30000)]
fn test_is_parameter_false_for_usefixtures_multiple_args() {
    // Multiple fixture names inside a single usefixtures decorator must all
    // have is_parameter = false.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def fix_a():
    return "a"

@pytest.fixture
def fix_b():
    return "b"

@pytest.mark.usefixtures("fix_a", "fix_b")
def test_multi_usefixtures():
    pass
"#;

    let path = PathBuf::from("/tmp/test_is_param/test_usefixtures_multi.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).unwrap();

    for name in &["fix_a", "fix_b"] {
        let usage = usages
            .iter()
            .find(|u| u.name == *name)
            .unwrap_or_else(|| panic!("{} usage should be detected", name));
        assert!(
            !usage.is_parameter,
            "{} from usefixtures must have is_parameter = false",
            name
        );
    }
}

#[test]
#[timeout(30000)]
fn test_is_parameter_false_for_usefixtures_on_class() {
    // Fixtures referenced via @pytest.mark.usefixtures on a class must also
    // have is_parameter = false.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_db() -> str:
    return "db"

@pytest.mark.usefixtures("my_db")
class TestSomething:
    def test_method(self):
        pass
"#;

    let path = PathBuf::from("/tmp/test_is_param/test_usefixtures_class.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).unwrap();
    let usage = usages
        .iter()
        .find(|u| u.name == "my_db")
        .expect("my_db usage from usefixtures on class should be detected");

    assert!(
        !usage.is_parameter,
        "usefixtures string usages on classes must have is_parameter = false"
    );
}

#[test]
#[timeout(30000)]
fn test_is_parameter_false_for_pytestmark_usefixtures_assignment() {
    // Fixtures referenced inside `pytestmark = pytest.mark.usefixtures(...)` must
    // have is_parameter = false — they are strings in a module-level assignment.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

pytestmark = pytest.mark.usefixtures("my_db")

@pytest.fixture
def my_db() -> str:
    return "db"

def test_something():
    pass
"#;

    let path = PathBuf::from("/tmp/test_is_param/test_pytestmark_usefixtures.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).unwrap();
    let usage = usages
        .iter()
        .find(|u| u.name == "my_db")
        .expect("my_db usage from pytestmark should be detected");

    assert!(
        !usage.is_parameter,
        "pytestmark usefixtures string usages must have is_parameter = false"
    );
}

#[test]
#[timeout(30000)]
fn test_is_parameter_false_for_pytestmark_usefixtures_list() {
    // Fixtures in a list-style pytestmark must also have is_parameter = false.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

pytestmark = [pytest.mark.usefixtures("fix_a", "fix_b")]

@pytest.fixture
def fix_a():
    return "a"

@pytest.fixture
def fix_b():
    return "b"

def test_something():
    pass
"#;

    let path = PathBuf::from("/tmp/test_is_param/test_pytestmark_list.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).unwrap();

    for name in &["fix_a", "fix_b"] {
        let usage = usages
            .iter()
            .find(|u| u.name == *name)
            .unwrap_or_else(|| panic!("{} usage should be detected in pytestmark list", name));
        assert!(
            !usage.is_parameter,
            "{} from pytestmark list must have is_parameter = false",
            name
        );
    }
}

#[test]
#[timeout(30000)]
fn test_is_parameter_false_for_parametrize_indirect() {
    // Fixtures referenced via @pytest.mark.parametrize(..., indirect=True) must
    // have is_parameter = false — they are string identifiers, not parameters.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture(request):
    return request.param

@pytest.mark.parametrize("my_fixture", [1, 2], indirect=True)
def test_indirect(my_fixture):
    pass
"#;

    let path = PathBuf::from("/tmp/test_is_param/test_parametrize_indirect.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).unwrap();

    // There should be two my_fixture usages: one from indirect parametrize
    // (is_parameter = false) and one from the function parameter (is_parameter = true).
    let indirect_usage = usages
        .iter()
        .find(|u| u.name == "my_fixture" && !u.is_parameter);
    assert!(
        indirect_usage.is_some(),
        "my_fixture from parametrize indirect should have is_parameter = false"
    );

    let param_usage = usages
        .iter()
        .find(|u| u.name == "my_fixture" && u.is_parameter);
    assert!(
        param_usage.is_some(),
        "my_fixture as a function parameter should have is_parameter = true"
    );
}

#[test]
#[timeout(30000)]
fn test_is_parameter_mixed_param_and_marker_usages_in_same_file() {
    // A file can have the same fixture referenced both as a function parameter
    // (is_parameter = true) and inside a usefixtures marker (is_parameter = false).
    // The two usages must be distinguished correctly.
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_db() -> str:
    return "db"

# Marker usage — string, not a parameter.
@pytest.mark.usefixtures("my_db")
def test_marker_usage():
    pass

# Parameter usage — should receive a type annotation.
def test_param_usage(my_db):
    pass
"#;

    let path = PathBuf::from("/tmp/test_is_param/test_mixed.py");
    db.analyze_file(path.clone(), content);

    let usages = db.usages.get(&path).unwrap();

    let marker_usage = usages
        .iter()
        .find(|u| u.name == "my_db" && !u.is_parameter)
        .expect("marker usage of my_db should have is_parameter = false");
    assert!(!marker_usage.is_parameter);

    let param_usage = usages
        .iter()
        .find(|u| u.name == "my_db" && u.is_parameter)
        .expect("parameter usage of my_db should have is_parameter = true");
    assert!(param_usage.is_parameter);
}

// =============================================================================
// pytest_plugins tests (basic detection)
// =============================================================================

#[test]
#[timeout(30000)]
fn test_pytest_plugins_declaration_detected() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
pytest_plugins = ["myapp.fixtures", "other.fixtures"]

import pytest

@pytest.fixture
def local_fixture():
    return "local"
"#;

    let conftest_path = PathBuf::from("/tmp/test_plugins/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    assert!(
        db.definitions.contains_key("local_fixture"),
        "local_fixture should be detected even with pytest_plugins"
    );
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_tuple_declaration_detected() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
pytest_plugins = ("plugin1", "plugin2")

import pytest

@pytest.fixture
def another_fixture():
    return "value"
"#;

    let conftest_path = PathBuf::from("/tmp/test_plugins/conftest.py");
    db.analyze_file(conftest_path, conftest_content);

    assert!(
        db.definitions.contains_key("another_fixture"),
        "another_fixture should be detected"
    );
}

// =============================================================================
// pytest.mark.parametrize with indirect tests
// =============================================================================

#[test]
#[timeout(30000)]
fn test_parametrize_indirect_true() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def my_fixture(request):
    return request.param * 2
"#;

    let test_content = r#"
import pytest

@pytest.mark.parametrize("my_fixture", [1, 2, 3], indirect=True)
def test_with_indirect(my_fixture):
    assert my_fixture in [2, 4, 6]
"#;

    let conftest_path = PathBuf::from("/tmp/test_indirect/conftest.py");
    let test_path = PathBuf::from("/tmp/test_indirect/test_indirect.py");

    db.analyze_file(conftest_path, conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // my_fixture should be detected as usage both from the parameter and from indirect
    let usages = db.usages.get(&test_path).unwrap();
    let fixture_usages: Vec<_> = usages.iter().filter(|u| u.name == "my_fixture").collect();

    // Should have 2 usages: one from indirect decorator, one from function parameter
    assert!(
        fixture_usages.len() >= 2,
        "my_fixture should be used at least twice (indirect + parameter)"
    );
}

#[test]
#[timeout(30000)]
fn test_parametrize_indirect_multiple_fixtures() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def fixture_a(request):
    return request.param

@pytest.fixture
def fixture_b(request):
    return request.param

@pytest.mark.parametrize("fixture_a,fixture_b", [(1, 2), (3, 4)], indirect=True)
def test_multiple_indirect(fixture_a, fixture_b):
    pass
"#;

    let test_path = PathBuf::from("/tmp/test_indirect/test_multiple.py");
    db.analyze_file(test_path.clone(), test_content);

    let usages = db.usages.get(&test_path).unwrap();

    // Both fixtures should be detected as indirect usages
    assert!(
        usages.iter().any(|u| u.name == "fixture_a"),
        "fixture_a should be detected as indirect usage"
    );
    assert!(
        usages.iter().any(|u| u.name == "fixture_b"),
        "fixture_b should be detected as indirect usage"
    );
}

#[test]
#[timeout(30000)]
fn test_parametrize_indirect_list_selective() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def indirect_fix(request):
    return request.param

@pytest.fixture
def direct_fix():
    return "direct"

@pytest.mark.parametrize("indirect_fix,direct_fix", [(1, 2)], indirect=["indirect_fix"])
def test_selective_indirect(indirect_fix, direct_fix):
    pass
"#;

    let test_path = PathBuf::from("/tmp/test_indirect/test_selective.py");
    db.analyze_file(test_path.clone(), test_content);

    let usages = db.usages.get(&test_path).unwrap();

    // indirect_fix should have an additional usage from the indirect list
    let indirect_usages: Vec<_> = usages.iter().filter(|u| u.name == "indirect_fix").collect();
    assert!(
        indirect_usages.len() >= 2,
        "indirect_fix should have at least 2 usages (from indirect list + parameter)"
    );
}

#[test]
#[timeout(30000)]
fn test_parametrize_without_indirect() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.mark.parametrize("value", [1, 2, 3])
def test_normal_parametrize(value):
    pass
"#;

    let test_path = PathBuf::from("/tmp/test_indirect/test_normal.py");
    db.analyze_file(test_path.clone(), test_content);

    // value should be detected as a parameter usage, but not as an indirect fixture
    let usages = db.usages.get(&test_path).unwrap();
    let value_usages: Vec<_> = usages.iter().filter(|u| u.name == "value").collect();

    // Should only have 1 usage from the function parameter
    assert_eq!(
        value_usages.len(),
        1,
        "value should only have 1 usage (from parameter, not indirect)"
    );
}

// MARK: Scoping Tests - Issue #23

#[test]
#[timeout(30000)]
fn test_fixture_scoping_sibling_files() {
    // Test case from issue #23:
    // A fixture defined in one test file should NOT be counted as "used"
    // when a sibling test file uses a parameter with the same name.
    let db = FixtureDatabase::new();

    // File 1: defines a fixture
    let test1_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "example"
"#;
    let test1_path = PathBuf::from("/tmp/test_scope/test_example_2.py");
    db.analyze_file(test1_path.clone(), test1_content);

    // File 2: uses a parameter with the same name, but the fixture is NOT in scope
    let test2_content = r#"
def test_example_fixture(my_fixture):
    assert my_fixture == "example"
"#;
    let test2_path = PathBuf::from("/tmp/test_scope/test_example.py");
    db.analyze_file(test2_path.clone(), test2_content);

    // Verify the fixture is defined
    let fixture_defs = db.definitions.get("my_fixture").unwrap();
    assert_eq!(fixture_defs.len(), 1);
    let fixture_def = &fixture_defs[0];
    assert_eq!(fixture_def.file_path, test1_path);

    // The key assertion: find_references_for_definition should NOT include
    // the usage from test_example.py because the fixture is not in scope there
    let refs = db.find_references_for_definition(fixture_def);
    assert_eq!(
        refs.len(),
        0,
        "Fixture defined in test_example_2.py should have 0 references \
         because test_example.py cannot access it (not in conftest.py hierarchy)"
    );
}

#[test]
#[timeout(30000)]
fn test_fixture_scoping_with_conftest() {
    // When a fixture IS in conftest.py, sibling files CAN use it
    let db = FixtureDatabase::new();

    // conftest.py defines a fixture
    let conftest_content = r#"
import pytest

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

    // Test file uses the fixture
    let test_content = r#"
def test_uses_shared(shared_fixture):
    assert shared_fixture == "shared"
"#;
    let test_path = PathBuf::from("/tmp/test_scope2/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    // The fixture from conftest.py should be accessible
    let fixture_defs = db.definitions.get("shared_fixture").unwrap();
    let fixture_def = &fixture_defs[0];

    let refs = db.find_references_for_definition(fixture_def);
    assert_eq!(
        refs.len(),
        1,
        "Fixture in conftest.py should have 1 reference from sibling test file"
    );
    assert_eq!(refs[0].file_path, test_path);
}

#[test]
#[timeout(30000)]
fn test_fixture_scoping_same_file() {
    // Fixture defined in the same file should be usable
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def local_fixture():
    return "local"

def test_uses_local(local_fixture):
    assert local_fixture == "local"
"#;
    let test_path = PathBuf::from("/tmp/test_scope3/test_local.py");
    db.analyze_file(test_path.clone(), test_content);

    let fixture_defs = db.definitions.get("local_fixture").unwrap();
    let fixture_def = &fixture_defs[0];

    let refs = db.find_references_for_definition(fixture_def);
    assert_eq!(
        refs.len(),
        1,
        "Fixture defined in same file should have 1 reference"
    );
    assert_eq!(refs[0].file_path, test_path);
}

#[test]
#[timeout(30000)]
fn test_get_scoped_usage_count() {
    // Test the new get_scoped_usage_count method
    let db = FixtureDatabase::new();

    // Setup: conftest.py with a fixture
    let conftest_content = r#"
import pytest

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

    // File 1: defines a local fixture with the same name (overrides)
    let test1_content = r#"
import pytest

@pytest.fixture
def global_fixture():
    return "local override"

def test_uses_local(global_fixture):
    pass
"#;
    let test1_path = PathBuf::from("/tmp/test_scope4/subdir/test_override.py");
    db.analyze_file(test1_path.clone(), test1_content);

    // File 2: uses the global fixture (no override)
    let test2_content = r#"
def test_uses_global(global_fixture):
    pass
"#;
    let test2_path = PathBuf::from("/tmp/test_scope4/test_global.py");
    db.analyze_file(test2_path.clone(), test2_content);

    // The conftest fixture should only be used by test_global.py (1 reference)
    let conftest_defs = db.definitions.get("global_fixture").unwrap();
    let conftest_def = conftest_defs
        .iter()
        .find(|d| d.file_path == conftest_path)
        .unwrap();

    let conftest_refs = db.find_references_for_definition(conftest_def);
    assert_eq!(
        conftest_refs.len(),
        1,
        "Conftest fixture should have 1 reference (from test_global.py)"
    );
    assert_eq!(conftest_refs[0].file_path, test2_path);

    // The local override fixture should be used by test_override.py (1 reference)
    let local_def = conftest_defs
        .iter()
        .find(|d| d.file_path == test1_path)
        .unwrap();

    let local_refs = db.find_references_for_definition(local_def);
    assert_eq!(
        local_refs.len(),
        1,
        "Local override fixture should have 1 reference"
    );
    assert_eq!(local_refs[0].file_path, test1_path);
}

// ============================================================================
// Completion Context Tests
// ============================================================================

#[test]
#[timeout(30000)]
fn test_completion_context_function_signature() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42

def test_something():
    pass
"#;

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

    // LSP uses 0-indexed lines, position inside the parentheses of test_something()
    // Line 7 (0-indexed) is "def test_something():"
    // Cursor at position 18 (inside parentheses)
    let ctx = db.get_completion_context(&test_path, 7, 18);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            declared_params,
            ..
        } => {
            assert_eq!(function_name, "test_something");
            assert!(!is_fixture);
            assert!(declared_params.is_empty());
        }
        _ => panic!("Expected FunctionSignature context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_function_signature_with_params() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42

def test_something(my_fixture, ):
    pass
"#;

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

    // Position after the comma, inside parentheses
    // Line 7 (0-indexed): "def test_something(my_fixture, ):"
    // Cursor at position 31 (after the comma and space)
    let ctx = db.get_completion_context(&test_path, 7, 31);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            declared_params,
            ..
        } => {
            assert_eq!(function_name, "test_something");
            assert!(!is_fixture);
            assert_eq!(declared_params, vec!["my_fixture"]);
        }
        _ => panic!("Expected FunctionSignature context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_function_body() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42

def test_something(my_fixture):

    pass
"#;

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

    // Position inside the function body (the empty line)
    // Line 8 (0-indexed) is the empty line inside the function
    let ctx = db.get_completion_context(&test_path, 8, 4);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody {
            function_name,
            is_fixture,
            declared_params,
            ..
        } => {
            assert_eq!(function_name, "test_something");
            assert!(!is_fixture);
            assert_eq!(declared_params, vec!["my_fixture"]);
        }
        _ => panic!("Expected FunctionBody context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_fixture_function() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def base_fixture():
    return 42

@pytest.fixture
def dependent_fixture():
    pass
"#;

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

    // Position inside parentheses of dependent_fixture
    // Line 8 (0-indexed): "@pytest.fixture" followed by line 9: "def dependent_fixture():"
    let ctx = db.get_completion_context(&test_path, 8, 22);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            declared_params,
            ..
        } => {
            assert_eq!(function_name, "dependent_fixture");
            assert!(is_fixture);
            assert!(declared_params.is_empty());
        }
        _ => panic!("Expected FunctionSignature context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_usefixtures_decorator() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42

@pytest.mark.usefixtures("")
def test_something():
    pass
"#;

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

    // Position inside the string of usefixtures decorator
    // Line 7 (0-indexed): "@pytest.mark.usefixtures("")"
    // Cursor at position 27 (inside the empty quotes)
    let ctx = db.get_completion_context(&test_path, 7, 27);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        _ => panic!("Expected UsefixturesDecorator context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_outside_function() {
    let db = FixtureDatabase::new();

    let test_content = r#"
import pytest

# A comment

def test_something():
    pass
"#;

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

    // Position on the comment line (not inside a function)
    // Line 3 (0-indexed): "# A comment"
    let ctx = db.get_completion_context(&test_path, 3, 5);

    assert!(ctx.is_none());
}

#[test]
#[timeout(30000)]
fn test_completion_context_pytestmark_usefixtures_single() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Line 0 (0-indexed): ""  (raw string starts with newline)
    // Line 1: "import pytest"
    // Line 2: ""
    // Line 3: "pytestmark = pytest.mark.usefixtures("")"
    let test_content = r#"
import pytest

pytestmark = pytest.mark.usefixtures("")
"#;

    let test_path = PathBuf::from("/tmp/test_completion_pytestmark/test_single.py");
    db.analyze_file(test_path.clone(), test_content);

    // Line 3 (0-indexed), cursor inside the empty string argument
    // "pytestmark = pytest.mark.usefixtures("") -> quote is at col 38
    let ctx = db.get_completion_context(&test_path, 3, 38);

    assert!(
        ctx.is_some(),
        "Expected a completion context inside pytestmark usefixtures"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_pytestmark_usefixtures_in_list() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Line 0: ""
    // Line 1: "import pytest"
    // Line 2: ""
    // Line 3: "pytestmark = [pytest.mark.usefixtures(""), pytest.mark.skip]"
    let test_content = r#"
import pytest

pytestmark = [pytest.mark.usefixtures(""), pytest.mark.skip]
"#;

    let test_path = PathBuf::from("/tmp/test_completion_pytestmark/test_list.py");
    db.analyze_file(test_path.clone(), test_content);

    // Line 3 (0-indexed), cursor inside the empty string argument
    // "pytestmark = [pytest.mark.usefixtures("") -> quote at col 39
    let ctx = db.get_completion_context(&test_path, 3, 39);

    assert!(
        ctx.is_some(),
        "Expected a completion context inside pytestmark list usefixtures"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_pytestmark_outside_usefixtures() {
    // Cursor is on the pytestmark line but NOT inside a usefixtures() call
    let db = FixtureDatabase::new();

    // Line 3: "pytestmark = [pytest.mark.skip]"
    let test_content = r#"
import pytest

pytestmark = [pytest.mark.skip]
"#;

    let test_path = PathBuf::from("/tmp/test_completion_pytestmark/test_outside.py");
    db.analyze_file(test_path.clone(), test_content);

    // Cursor inside pytest.mark.skip — not a usefixtures call
    let ctx = db.get_completion_context(&test_path, 3, 20);

    // Should NOT return UsefixturesDecorator
    assert!(
        !matches!(
            ctx,
            Some(pytest_language_server::CompletionContext::UsefixturesDecorator)
        ),
        "Should not return UsefixturesDecorator for non-usefixtures marks"
    );
}

#[test]
#[timeout(30000)]
fn test_completion_context_pytestmark_annotated_assignment() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Line 0: ""
    // Line 1: "import pytest"
    // Line 2: ""
    // Line 3: "pytestmark: list = [pytest.mark.usefixtures("")]"
    let test_content = r#"
import pytest

pytestmark: list = [pytest.mark.usefixtures("")]
"#;

    let test_path = PathBuf::from("/tmp/test_completion_pytestmark/test_annotated.py");
    db.analyze_file(test_path.clone(), test_content);

    // Line 3 (0-indexed), cursor inside the empty string argument
    // "pytestmark: list = [pytest.mark.usefixtures("") -> quote at col 45
    let ctx = db.get_completion_context(&test_path, 3, 45);

    assert!(
        ctx.is_some(),
        "Expected completion context inside annotated pytestmark usefixtures"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_get_function_param_insertion_info_empty_params() {
    let db = FixtureDatabase::new();

    let test_content = r#"
def test_something():
    pass
"#;

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

    // Function is on line 2 (1-indexed)
    let info = db.get_function_param_insertion_info(&test_path, 2);

    assert!(info.is_some());
    let info = info.unwrap();
    assert_eq!(info.line, 2);
    assert!(!info.needs_comma);
}

#[test]
#[timeout(30000)]
fn test_get_function_param_insertion_info_with_params() {
    let db = FixtureDatabase::new();

    let test_content = r#"
def test_something(existing_param):
    pass
"#;

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

    // Function is on line 2 (1-indexed)
    let info = db.get_function_param_insertion_info(&test_path, 2);

    assert!(info.is_some());
    let info = info.unwrap();
    assert_eq!(info.line, 2);
    assert!(info.needs_comma);
}

// ============ Cycle Detection Tests ============

#[test]
#[timeout(30000)]
fn test_cycle_detection_simple_cycle() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def fixture_a(fixture_b):
    return "a"

@pytest.fixture
def fixture_b(fixture_a):
    return "b"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let cycles = db.detect_fixture_cycles();
    assert!(!cycles.is_empty(), "Should detect the A->B->A cycle");

    // Check the cycle contains both fixtures
    let cycle = &cycles[0];
    assert!(cycle.cycle_path.contains(&"fixture_a".to_string()));
    assert!(cycle.cycle_path.contains(&"fixture_b".to_string()));
}

#[test]
#[timeout(30000)]
fn test_cycle_detection_three_node_cycle() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def fixture_a(fixture_b):
    return "a"

@pytest.fixture
def fixture_b(fixture_c):
    return "b"

@pytest.fixture
def fixture_c(fixture_a):
    return "c"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let cycles = db.detect_fixture_cycles();
    assert!(!cycles.is_empty(), "Should detect the A->B->C->A cycle");

    // The cycle should contain all three fixtures
    let cycle = &cycles[0];
    assert!(
        cycle.cycle_path.len() >= 3,
        "Cycle should have at least 3 nodes"
    );
}

#[test]
#[timeout(30000)]
fn test_cycle_detection_no_cycle() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def base_fixture():
    return "base"

@pytest.fixture
def dependent_fixture(base_fixture):
    return base_fixture + "_dep"

@pytest.fixture
def top_fixture(dependent_fixture):
    return dependent_fixture + "_top"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let cycles = db.detect_fixture_cycles();
    assert!(cycles.is_empty(), "Should not detect any cycles in a DAG");
}

#[test]
#[timeout(30000)]
fn test_cycle_detection_self_referencing() {
    let db = FixtureDatabase::new();

    // Self-referencing fixture (same name as parameter) - this is actually valid
    // in pytest when overriding a parent fixture, but we detect it as a cycle
    // Note: In practice, pytest resolves this by looking up the conftest hierarchy
    let content = r#"
import pytest

@pytest.fixture
def my_fixture(my_fixture):
    return my_fixture + "_modified"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let cycles = db.detect_fixture_cycles();
    // Self-reference creates a cycle A->A
    assert!(
        !cycles.is_empty(),
        "Should detect self-referencing as a cycle"
    );
}

#[test]
#[timeout(30000)]
fn test_cycle_detection_caching() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def fixture_a(fixture_b):
    return "a"

@pytest.fixture
def fixture_b(fixture_a):
    return "b"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // First call computes cycles
    let cycles1 = db.detect_fixture_cycles();
    assert!(!cycles1.is_empty());

    // Second call should use cache (same Arc)
    let cycles2 = db.detect_fixture_cycles();
    assert_eq!(cycles1.len(), cycles2.len());

    // Add new content to invalidate cache
    let content2 = r#"
import pytest

@pytest.fixture
def standalone():
    return "standalone"
"#;
    let path2 = PathBuf::from("/tmp/test/other.py");
    db.analyze_file(path2, content2);

    // Cache should be invalidated, cycles recalculated
    let cycles3 = db.detect_fixture_cycles();
    // Original cycle should still be detected
    assert!(!cycles3.is_empty());
}

#[test]
#[timeout(30000)]
fn test_cycle_detection_in_file() {
    let db = FixtureDatabase::new();

    let content1 = r#"
import pytest

@pytest.fixture
def fixture_a(fixture_b):
    return "a"

@pytest.fixture
def fixture_b(fixture_a):
    return "b"
"#;

    let content2 = r#"
import pytest

@pytest.fixture
def standalone():
    return "standalone"
"#;

    let path1 = PathBuf::from("/tmp/test/conftest.py");
    let path2 = PathBuf::from("/tmp/test/other.py");
    db.analyze_file(path1.clone(), content1);
    db.analyze_file(path2.clone(), content2);

    // Cycles in file1
    let cycles_file1 = db.detect_fixture_cycles_in_file(&path1);
    assert!(
        !cycles_file1.is_empty(),
        "Should find cycles in conftest.py"
    );

    // No cycles in file2
    let cycles_file2 = db.detect_fixture_cycles_in_file(&path2);
    assert!(
        cycles_file2.is_empty(),
        "Should not find cycles in other.py"
    );
}

#[test]
#[timeout(30000)]
fn test_cycle_detection_with_external_dependencies() {
    let db = FixtureDatabase::new();

    // Fixtures with dependencies on unknown fixtures (like third-party)
    let content = r#"
import pytest

@pytest.fixture
def my_fixture(unknown_fixture, another_unknown):
    return "my"

@pytest.fixture
def other_fixture(my_fixture):
    return "other"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // No cycles - unknown_fixture is not in the graph
    let cycles = db.detect_fixture_cycles();
    assert!(
        cycles.is_empty(),
        "Unknown fixtures should not cause false positive cycles"
    );
}

#[test]
#[timeout(30000)]
fn test_cycle_detection_multiple_independent_cycles() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

# Cycle 1: a -> b -> a
@pytest.fixture
def cycle1_a(cycle1_b):
    return "1a"

@pytest.fixture
def cycle1_b(cycle1_a):
    return "1b"

# Cycle 2: x -> y -> z -> x
@pytest.fixture
def cycle2_x(cycle2_y):
    return "2x"

@pytest.fixture
def cycle2_y(cycle2_z):
    return "2y"

@pytest.fixture
def cycle2_z(cycle2_x):
    return "2z"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let cycles = db.detect_fixture_cycles();
    // Should detect both cycles (may be reported as 2+ depending on detection order)
    assert!(
        cycles.len() >= 2,
        "Should detect multiple independent cycles, got {}",
        cycles.len()
    );
}

// ============ Scope Validation Tests ============

#[test]
#[timeout(30000)]
fn test_scope_mismatch_session_depends_on_function() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def function_fixture():
    return "function"

@pytest.fixture(scope="session")
def session_fixture(function_fixture):
    return function_fixture + "_session"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let mismatches = db.detect_scope_mismatches_in_file(&path);
    assert_eq!(
        mismatches.len(),
        1,
        "Should detect session->function scope mismatch"
    );

    let mismatch = &mismatches[0];
    assert_eq!(mismatch.fixture.name, "session_fixture");
    assert_eq!(mismatch.dependency.name, "function_fixture");
}

#[test]
#[timeout(30000)]
fn test_scope_mismatch_module_depends_on_function() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def func_fixture():
    return "func"

@pytest.fixture(scope="module")
def mod_fixture(func_fixture):
    return func_fixture + "_mod"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let mismatches = db.detect_scope_mismatches_in_file(&path);
    assert_eq!(
        mismatches.len(),
        1,
        "Should detect module->function scope mismatch"
    );
}

#[test]
#[timeout(30000)]
fn test_scope_no_mismatch_valid_hierarchy() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

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

@pytest.fixture(scope="module")
def module_fixture(session_fixture):
    return session_fixture + "_module"

@pytest.fixture
def function_fixture(module_fixture):
    return module_fixture + "_function"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let mismatches = db.detect_scope_mismatches_in_file(&path);
    assert!(
        mismatches.is_empty(),
        "Should not detect mismatches in valid hierarchy"
    );
}

#[test]
#[timeout(30000)]
fn test_scope_same_scope_no_mismatch() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(scope="module")
def mod_fixture_a():
    return "a"

@pytest.fixture(scope="module")
def mod_fixture_b(mod_fixture_a):
    return mod_fixture_a + "_b"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let mismatches = db.detect_scope_mismatches_in_file(&path);
    assert!(mismatches.is_empty(), "Same scope should not be a mismatch");
}

#[test]
#[timeout(30000)]
fn test_scope_class_depends_on_function() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def func_fixture():
    return "func"

@pytest.fixture(scope="class")
def class_fixture(func_fixture):
    return func_fixture + "_class"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let mismatches = db.detect_scope_mismatches_in_file(&path);
    assert_eq!(
        mismatches.len(),
        1,
        "Should detect class->function scope mismatch"
    );
}

#[test]
#[timeout(30000)]
fn test_scope_package_depends_on_module() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(scope="module")
def mod_fixture():
    return "module"

@pytest.fixture(scope="package")
def pkg_fixture(mod_fixture):
    return mod_fixture + "_pkg"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let mismatches = db.detect_scope_mismatches_in_file(&path);
    assert_eq!(
        mismatches.len(),
        1,
        "Should detect package->module scope mismatch"
    );
}

#[test]
#[timeout(30000)]
fn test_scope_multiple_mismatches() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def func_a():
    return "a"

@pytest.fixture
def func_b():
    return "b"

@pytest.fixture(scope="session")
def session_fixture(func_a, func_b):
    return func_a + func_b
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let mismatches = db.detect_scope_mismatches_in_file(&path);
    assert_eq!(mismatches.len(), 2, "Should detect two scope mismatches");
}

#[test]
#[timeout(30000)]
fn test_scope_default_is_function() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def default_fixture():
    return "default"
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let defs = db.definitions.get("default_fixture").unwrap();
    assert_eq!(
        defs[0].scope,
        pytest_language_server::FixtureScope::Function
    );
}

#[test]
#[timeout(30000)]
fn test_scope_extraction_all_scopes() {
    use pytest_language_server::FixtureScope;

    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(scope="function")
def func_fix():
    pass

@pytest.fixture(scope="class")
def class_fix():
    pass

@pytest.fixture(scope="module")
def mod_fix():
    pass

@pytest.fixture(scope="package")
def pkg_fix():
    pass

@pytest.fixture(scope="session")
def session_fix():
    pass
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    assert_eq!(
        db.definitions.get("func_fix").unwrap()[0].scope,
        FixtureScope::Function
    );
    assert_eq!(
        db.definitions.get("class_fix").unwrap()[0].scope,
        FixtureScope::Class
    );
    assert_eq!(
        db.definitions.get("mod_fix").unwrap()[0].scope,
        FixtureScope::Module
    );
    assert_eq!(
        db.definitions.get("pkg_fix").unwrap()[0].scope,
        FixtureScope::Package
    );
    assert_eq!(
        db.definitions.get("session_fix").unwrap()[0].scope,
        FixtureScope::Session
    );
}

// ============ Assignment-Style Fixture Scope Tests ============

#[test]
#[timeout(30000)]
fn test_assignment_fixture_default_scope_is_function() {
    use pytest_language_server::FixtureScope;

    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def _mocker():
    pass

mocker = pytest.fixture()(_mocker)
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    assert!(
        db.definitions.contains_key("mocker"),
        "mocker fixture should be detected"
    );
    assert_eq!(
        db.definitions.get("mocker").unwrap()[0].scope,
        FixtureScope::Function,
        "mocker with no scope argument should default to function scope"
    );
}

#[test]
#[timeout(30000)]
fn test_assignment_fixture_class_scope() {
    use pytest_language_server::FixtureScope;

    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def _mocker():
    pass

class_mocker = pytest.fixture(scope="class")(_mocker)
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    assert!(
        db.definitions.contains_key("class_mocker"),
        "class_mocker fixture should be detected"
    );
    assert_eq!(
        db.definitions.get("class_mocker").unwrap()[0].scope,
        FixtureScope::Class,
        "class_mocker should have class scope"
    );
}

#[test]
#[timeout(30000)]
fn test_assignment_fixture_module_scope() {
    use pytest_language_server::FixtureScope;

    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def _mocker():
    pass

module_mocker = pytest.fixture(scope="module")(_mocker)
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    assert!(
        db.definitions.contains_key("module_mocker"),
        "module_mocker fixture should be detected"
    );
    assert_eq!(
        db.definitions.get("module_mocker").unwrap()[0].scope,
        FixtureScope::Module,
        "module_mocker should have module scope"
    );
}

#[test]
#[timeout(30000)]
fn test_assignment_fixture_package_scope() {
    use pytest_language_server::FixtureScope;

    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def _mocker():
    pass

package_mocker = pytest.fixture(scope="package")(_mocker)
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    assert!(
        db.definitions.contains_key("package_mocker"),
        "package_mocker fixture should be detected"
    );
    assert_eq!(
        db.definitions.get("package_mocker").unwrap()[0].scope,
        FixtureScope::Package,
        "package_mocker should have package scope"
    );
}

#[test]
#[timeout(30000)]
fn test_assignment_fixture_session_scope() {
    use pytest_language_server::FixtureScope;

    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def _mocker():
    pass

session_mocker = pytest.fixture(scope="session")(_mocker)
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    assert!(
        db.definitions.contains_key("session_mocker"),
        "session_mocker fixture should be detected"
    );
    assert_eq!(
        db.definitions.get("session_mocker").unwrap()[0].scope,
        FixtureScope::Session,
        "session_mocker should have session scope"
    );
}

#[test]
#[timeout(30000)]
fn test_assignment_fixture_all_scopes_together() {
    use pytest_language_server::FixtureScope;

    let db = FixtureDatabase::new();

    // This mirrors the real pytest-mock pattern exactly
    let content = r#"
import pytest

def _mocker():
    pass

mocker = pytest.fixture()(_mocker)
class_mocker = pytest.fixture(scope="class")(_mocker)
module_mocker = pytest.fixture(scope="module")(_mocker)
package_mocker = pytest.fixture(scope="package")(_mocker)
session_mocker = pytest.fixture(scope="session")(_mocker)
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    assert_eq!(
        db.definitions.get("mocker").unwrap()[0].scope,
        FixtureScope::Function,
        "mocker (no scope arg) should be function scope"
    );
    assert_eq!(
        db.definitions.get("class_mocker").unwrap()[0].scope,
        FixtureScope::Class,
        "class_mocker should be class scope"
    );
    assert_eq!(
        db.definitions.get("module_mocker").unwrap()[0].scope,
        FixtureScope::Module,
        "module_mocker should be module scope"
    );
    assert_eq!(
        db.definitions.get("package_mocker").unwrap()[0].scope,
        FixtureScope::Package,
        "package_mocker should be package scope"
    );
    assert_eq!(
        db.definitions.get("session_mocker").unwrap()[0].scope,
        FixtureScope::Session,
        "session_mocker should be session scope"
    );
}

// ============ Yield Line Extraction Tests ============

#[test]
#[timeout(30000)]
fn test_yield_line_simple_generator_fixture() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def db_connection():
    conn = connect()
    yield conn
    conn.close()
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let fixture = &db.definitions.get("db_connection").unwrap()[0];
    // Line 7 is where "yield conn" is (1-indexed)
    assert_eq!(fixture.yield_line, Some(7));
}

#[test]
#[timeout(30000)]
fn test_yield_line_no_yield() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def simple_fixture():
    return 42
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let fixture = &db.definitions.get("simple_fixture").unwrap()[0];
    assert_eq!(fixture.yield_line, None);
}

#[test]
#[timeout(30000)]
fn test_yield_line_in_with_block() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def resource():
    with open("/tmp/file") as f:
        yield f
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let fixture = &db.definitions.get("resource").unwrap()[0];
    // Line 7 is where "yield f" is (1-indexed)
    assert_eq!(fixture.yield_line, Some(7));
}

#[test]
#[timeout(30000)]
fn test_yield_line_in_try_block() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def safe_resource():
    try:
        resource = create()
        yield resource
    finally:
        cleanup()
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let fixture = &db.definitions.get("safe_resource").unwrap()[0];
    // Line 8 is where "yield resource" is (1-indexed)
    assert_eq!(fixture.yield_line, Some(8));
}

#[test]
#[timeout(30000)]
fn test_yield_line_in_if_block() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def conditional_fixture():
    if True:
        yield 42
    else:
        yield 0
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let fixture = &db.definitions.get("conditional_fixture").unwrap()[0];
    // First yield on line 7
    assert_eq!(fixture.yield_line, Some(7));
}

// ============ Call Hierarchy Tests ============

#[test]
#[timeout(30000)]
fn test_find_containing_function_simple() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42

def test_something(my_fixture):
    assert my_fixture == 42
"#;

    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path.clone(), content);

    // Line 9 is inside test_something (the assert line)
    assert_eq!(
        db.find_containing_function(&path, 9),
        Some("test_something".to_string())
    );

    // Line 8 is the def line of test_something
    assert_eq!(
        db.find_containing_function(&path, 8),
        Some("test_something".to_string())
    );

    // Line 6 is inside my_fixture (the return line)
    assert_eq!(
        db.find_containing_function(&path, 6),
        Some("my_fixture".to_string())
    );

    // Line 10 is empty - outside any function
    assert_eq!(db.find_containing_function(&path, 10), None);
}

#[test]
#[timeout(30000)]
fn test_resolve_fixture_for_file_same_file() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "parent"
"#;

    let test_content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "local"

def test_it(shared_fixture):
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    let test_path = PathBuf::from("/tmp/test/test_example.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // From test file, should resolve to local fixture
    let resolved = db.resolve_fixture_for_file(&test_path, "shared_fixture");
    assert!(resolved.is_some());
    assert_eq!(resolved.unwrap().file_path, test_path);
}

#[test]
#[timeout(30000)]
fn test_resolve_fixture_for_file_conftest() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture
def parent_fixture():
    return "parent"
"#;

    let test_content = r#"
def test_it(parent_fixture):
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    let test_path = PathBuf::from("/tmp/test/test_example.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // From test file, should resolve to conftest fixture
    let resolved = db.resolve_fixture_for_file(&test_path, "parent_fixture");
    assert!(resolved.is_some());
    assert_eq!(resolved.unwrap().file_path, conftest_path);
}

// ============ Imported Fixture Tests ============

#[test]
#[timeout(30000)]
fn test_star_import_fixtures_are_resolved() {
    let db = FixtureDatabase::new();

    // Set up a fixture module with fixtures
    let fixture_module_content = r#"
import pytest

@pytest.fixture
def imported_fixture():
    return "imported_value"

@pytest.fixture
def another_imported_fixture():
    return 42
"#;

    // Set up conftest.py that imports from the fixture module
    let conftest_content = r#"
from .fixture_module import *

import pytest

@pytest.fixture
def local_fixture():
    return "local_value"
"#;

    // Set up a test file that uses the imported fixture
    let test_content = r#"
def test_uses_imported(imported_fixture, local_fixture):
    assert imported_fixture == "imported_value"
    assert local_fixture == "local_value"
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_import/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_import/conftest.py");
    let test_path = PathBuf::from("/tmp/test_import/test_example.py");

    // Analyze all files
    db.analyze_file(fixture_module_path.clone(), fixture_module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // The imported_fixture should be resolvable from the test file
    // because conftest.py imports it via star import
    let resolved = db.resolve_fixture_for_file(&test_path, "imported_fixture");

    assert!(
        resolved.is_some(),
        "imported_fixture should be resolvable via conftest star import"
    );
    let def = resolved.unwrap();
    assert_eq!(def.name, "imported_fixture");
    assert_eq!(def.file_path, fixture_module_path);
}

#[test]
#[timeout(30000)]
fn test_explicit_import_fixtures_are_resolved() {
    let db = FixtureDatabase::new();

    // Set up a fixture module with fixtures
    let fixture_module_content = r#"
import pytest

@pytest.fixture
def explicitly_imported():
    return "explicit"

@pytest.fixture
def not_imported():
    return "should not be available"
"#;

    // Set up conftest.py that explicitly imports only one fixture
    let conftest_content = r#"
from .fixture_module import explicitly_imported

import pytest

@pytest.fixture
def local_fixture():
    return "local"
"#;

    // Set up a test file
    let test_content = r#"
def test_uses_explicit(explicitly_imported, local_fixture):
    pass
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_explicit/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_explicit/conftest.py");
    let test_path = PathBuf::from("/tmp/test_explicit/test_example.py");

    db.analyze_file(fixture_module_path.clone(), fixture_module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // explicitly_imported should be resolvable
    let resolved = db.resolve_fixture_for_file(&test_path, "explicitly_imported");
    assert!(
        resolved.is_some(),
        "explicitly_imported should be resolvable via explicit import"
    );

    // not_imported should NOT be resolvable (it wasn't imported)
    // Actually, it IS defined in fixture_module.py, but it's not imported into conftest
    // So from the test file's perspective, it should only be available if we look at fixture_module directly
    // For now, we'll just verify the explicitly imported one works
}

#[test]
#[timeout(30000)]
fn test_circular_import_handling() {
    let db = FixtureDatabase::new();

    // Set up module A that imports from B
    let module_a_content = r#"
from .module_b import *

import pytest

@pytest.fixture
def fixture_a():
    return "a"
"#;

    // Set up module B that imports from A (circular!)
    let module_b_content = r#"
from .module_a import *

import pytest

@pytest.fixture
def fixture_b():
    return "b"
"#;

    let module_a_path = PathBuf::from("/tmp/test_circular/module_a.py");
    let module_b_path = PathBuf::from("/tmp/test_circular/module_b.py");

    db.analyze_file(module_a_path.clone(), module_a_content);
    db.analyze_file(module_b_path.clone(), module_b_content);

    // Getting imported fixtures should not hang or panic due to circular imports
    use std::collections::HashSet;
    let mut visited = HashSet::new();
    let _imported_a = db.get_imported_fixtures(&module_a_path, &mut visited);

    // Should complete without hanging and return some fixtures
    // The exact result depends on which module is processed first,
    // but it should definitely not panic or hang
    assert!(visited.len() <= 2, "Should have visited at most 2 modules");
}

#[test]
#[timeout(30000)]
fn test_transitive_imports() {
    let db = FixtureDatabase::new();

    // Module C has a fixture
    let module_c_content = r#"
import pytest

@pytest.fixture
def deep_fixture():
    return "deep"
"#;

    // Module B imports from C
    let module_b_content = r#"
from .module_c import *

import pytest

@pytest.fixture
def mid_fixture():
    return "mid"
"#;

    // Conftest imports from B (transitively getting C's fixtures)
    let conftest_content = r#"
from .module_b import *

import pytest

@pytest.fixture
def local_fixture():
    return "local"
"#;

    let test_content = r#"
def test_uses_deep(deep_fixture, mid_fixture, local_fixture):
    pass
"#;

    let module_c_path = PathBuf::from("/tmp/test_transitive/module_c.py");
    let module_b_path = PathBuf::from("/tmp/test_transitive/module_b.py");
    let conftest_path = PathBuf::from("/tmp/test_transitive/conftest.py");
    let test_path = PathBuf::from("/tmp/test_transitive/test_example.py");

    db.analyze_file(module_c_path.clone(), module_c_content);
    db.analyze_file(module_b_path.clone(), module_b_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // deep_fixture should be resolvable through transitive imports
    let resolved = db.resolve_fixture_for_file(&test_path, "deep_fixture");
    assert!(
        resolved.is_some(),
        "deep_fixture should be resolvable via transitive imports (C -> B -> conftest)"
    );
    assert_eq!(resolved.unwrap().file_path, module_c_path);
}

#[test]
#[timeout(30000)]
fn test_available_fixtures_includes_imported() {
    let db = FixtureDatabase::new();

    // Fixture module with a fixture
    let fixture_module_content = r#"
import pytest

@pytest.fixture
def module_fixture():
    return "from module"
"#;

    // Conftest that imports it
    let conftest_content = r#"
from .fixture_module import *

import pytest

@pytest.fixture
def conftest_fixture():
    return "from conftest"
"#;

    let test_content = r#"
def test_something():
    pass
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_available/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_available/conftest.py");
    let test_path = PathBuf::from("/tmp/test_available/test_example.py");

    db.analyze_file(fixture_module_path.clone(), fixture_module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Get available fixtures for the test file
    let available = db.get_available_fixtures(&test_path);
    let names: Vec<&str> = available.iter().map(|f| f.name.as_str()).collect();

    // Should include both the conftest fixture and the imported module fixture
    assert!(
        names.contains(&"conftest_fixture"),
        "conftest_fixture should be in available fixtures"
    );
    assert!(
        names.contains(&"module_fixture"),
        "module_fixture should be in available fixtures (via import)"
    );
}

#[test]
#[timeout(30000)]
fn test_find_definition_for_imported_fixture() {
    // Test that go-to-definition works for fixtures imported via star import
    let db = FixtureDatabase::new();

    let fixture_module_content = r#"
import pytest

@pytest.fixture
def imported_fixture():
    return "imported"
"#;

    let conftest_content = r#"
from .fixture_module import *
"#;

    let test_content = r#"
def test_uses_imported(imported_fixture):
    pass
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_def/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_def/conftest.py");
    let test_path = PathBuf::from("/tmp/test_def/test_example.py");

    db.analyze_file(fixture_module_path.clone(), fixture_module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Go-to-definition from the test file should find the fixture in fixture_module.py
    // Line 2 (0-based: 1) contains "def test_uses_imported(imported_fixture):"
    // Character position for "imported_fixture" starts at column 23
    let definition = db.find_fixture_definition(&test_path, 1, 24);

    assert!(
        definition.is_some(),
        "Should find definition for imported_fixture from test file"
    );
    let def = definition.unwrap();
    assert_eq!(def.name, "imported_fixture");
    assert_eq!(
        def.file_path, fixture_module_path,
        "Definition should be in fixture_module.py, not conftest.py"
    );
}

#[test]
#[timeout(30000)]
fn test_find_references_for_imported_fixture() {
    // Test that find-references works for fixtures that are imported
    let db = FixtureDatabase::new();

    let fixture_module_content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "shared"
"#;

    let conftest_content = r#"
from .fixture_module import *
"#;

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

def test_two(shared_fixture):
    pass
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_refs/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_refs/conftest.py");
    let test_path = PathBuf::from("/tmp/test_refs/test_example.py");

    db.analyze_file(fixture_module_path.clone(), fixture_module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Find all references to shared_fixture
    let references = db.find_fixture_references("shared_fixture");

    // Should find 2 usages in the test file
    assert_eq!(
        references.len(),
        2,
        "Should find 2 references to shared_fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_multi_level_relative_import() {
    // Test imports like `from ..utils.fixtures import *`
    let db = FixtureDatabase::new();

    let utils_fixtures_content = r#"
import pytest

@pytest.fixture
def util_fixture():
    return "from utils"
"#;

    // conftest.py is in tests/subdir/, imports from tests/utils/fixtures.py
    let conftest_content = r#"
from ..utils.fixtures import *
"#;

    let test_content = r#"
def test_uses_util(util_fixture):
    pass
"#;

    // File structure: tests/utils/fixtures.py, tests/subdir/conftest.py, tests/subdir/test_example.py
    let utils_fixtures_path = PathBuf::from("/tmp/test_multi_level/tests/utils/fixtures.py");
    let conftest_path = PathBuf::from("/tmp/test_multi_level/tests/subdir/conftest.py");
    let test_path = PathBuf::from("/tmp/test_multi_level/tests/subdir/test_example.py");

    db.analyze_file(utils_fixtures_path.clone(), utils_fixtures_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // The fixture should be resolvable from the test
    let resolved = db.resolve_fixture_for_file(&test_path, "util_fixture");

    assert!(
        resolved.is_some(),
        "util_fixture should be resolvable via multi-level relative import"
    );
    assert_eq!(resolved.unwrap().file_path, utils_fixtures_path);
}

#[test]
#[timeout(30000)]
fn test_mixed_star_and_explicit_imports() {
    // Test conftest with both star imports and explicit imports
    let db = FixtureDatabase::new();

    let module_a_content = r#"
import pytest

@pytest.fixture
def fixture_a():
    return "a"
"#;

    let module_b_content = r#"
import pytest

@pytest.fixture
def fixture_b():
    return "b"

@pytest.fixture
def fixture_b2():
    return "b2"
"#;

    let conftest_content = r#"
from .module_a import *
from .module_b import fixture_b  # Only import fixture_b, not fixture_b2

import pytest

@pytest.fixture
def local_fixture():
    return "local"
"#;

    let test_content = r#"
def test_uses_all(fixture_a, fixture_b, local_fixture):
    pass
"#;

    let module_a_path = PathBuf::from("/tmp/test_mixed/module_a.py");
    let module_b_path = PathBuf::from("/tmp/test_mixed/module_b.py");
    let conftest_path = PathBuf::from("/tmp/test_mixed/conftest.py");
    let test_path = PathBuf::from("/tmp/test_mixed/test_example.py");

    db.analyze_file(module_a_path.clone(), module_a_content);
    db.analyze_file(module_b_path.clone(), module_b_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // fixture_a should be available (via star import)
    let resolved_a = db.resolve_fixture_for_file(&test_path, "fixture_a");
    assert!(resolved_a.is_some(), "fixture_a should be available");
    assert_eq!(resolved_a.unwrap().file_path, module_a_path);

    // fixture_b should be available (via explicit import)
    let resolved_b = db.resolve_fixture_for_file(&test_path, "fixture_b");
    assert!(resolved_b.is_some(), "fixture_b should be available");
    assert_eq!(resolved_b.unwrap().file_path, module_b_path);
}

#[test]
#[timeout(30000)]
fn test_import_with_alias() {
    // Test `from .module import fixture as alias`
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def original_name():
    return "original"
"#;

    // NOTE: Importing a fixture with an alias is unusual in pytest, but let's test it
    let conftest_content = r#"
from .module import original_name as aliased_fixture
"#;

    let test_content = r#"
def test_uses_alias(aliased_fixture):
    pass
"#;

    let module_path = PathBuf::from("/tmp/test_alias/module.py");
    let conftest_path = PathBuf::from("/tmp/test_alias/conftest.py");
    let test_path = PathBuf::from("/tmp/test_alias/test_example.py");

    db.analyze_file(module_path.clone(), module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // With an alias, the fixture should be available under the alias name
    // Note: This depends on how the import extraction handles aliases
    // The current implementation may or may not support this fully
    let resolved = db.resolve_fixture_for_file(&test_path, "aliased_fixture");

    // If aliases are supported, this should find the fixture
    // This test documents the current behavior
    if resolved.is_some() {
        assert_eq!(
            resolved.unwrap().name,
            "original_name",
            "Aliased import should resolve to original fixture"
        );
    }
    // If not supported, the test passes anyway to document the limitation
}

#[test]
#[timeout(30000)]
fn test_nested_package_imports() {
    // Test imports from nested packages: from .subpackage.module import *
    let db = FixtureDatabase::new();

    let nested_module_content = r#"
import pytest

@pytest.fixture
def nested_fixture():
    return "nested"
"#;

    // Import from a nested package
    let conftest_content = r#"
from .subpackage.module import *
"#;

    let test_content = r#"
def test_uses_nested(nested_fixture):
    pass
"#;

    // File structure: tests/subpackage/module.py, tests/conftest.py, tests/test_example.py
    let nested_path = PathBuf::from("/tmp/test_nested/tests/subpackage/module.py");
    let conftest_path = PathBuf::from("/tmp/test_nested/tests/conftest.py");
    let test_path = PathBuf::from("/tmp/test_nested/tests/test_example.py");

    db.analyze_file(nested_path.clone(), nested_module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let resolved = db.resolve_fixture_for_file(&test_path, "nested_fixture");

    assert!(
        resolved.is_some(),
        "nested_fixture should be resolvable via nested package import"
    );
    assert_eq!(resolved.unwrap().file_path, nested_path);
}

#[test]
#[timeout(30000)]
fn test_imported_fixture_with_dependencies() {
    // Test that fixtures with dependencies are correctly resolved when imported
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def base_fixture():
    return "base"

@pytest.fixture
def dependent_fixture(base_fixture):
    return f"depends on {base_fixture}"
"#;

    let conftest_content = r#"
from .module import *
"#;

    let test_content = r#"
def test_uses_dependent(dependent_fixture):
    pass
"#;

    let module_path = PathBuf::from("/tmp/test_deps/module.py");
    let conftest_path = PathBuf::from("/tmp/test_deps/conftest.py");
    let test_path = PathBuf::from("/tmp/test_deps/test_example.py");

    db.analyze_file(module_path.clone(), module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Both fixtures should be resolvable
    let resolved_base = db.resolve_fixture_for_file(&test_path, "base_fixture");
    let resolved_dependent = db.resolve_fixture_for_file(&test_path, "dependent_fixture");

    assert!(resolved_base.is_some(), "base_fixture should be resolvable");
    assert!(
        resolved_dependent.is_some(),
        "dependent_fixture should be resolvable"
    );

    // The dependent fixture should have base_fixture in its dependencies
    let dep_def = resolved_dependent.unwrap();
    assert!(
        dep_def.dependencies.contains(&"base_fixture".to_string()),
        "dependent_fixture should list base_fixture as a dependency"
    );
}

#[test]
#[timeout(30000)]
fn test_import_from_init_py() {
    // Test importing from a package's __init__.py
    let db = FixtureDatabase::new();

    let init_content = r#"
import pytest

@pytest.fixture
def package_fixture():
    return "from package init"
"#;

    // Import from the package (which reads __init__.py)
    let conftest_content = r#"
from .fixtures import *
"#;

    let test_content = r#"
def test_uses_package_fixture(package_fixture):
    pass
"#;

    // File structure: tests/fixtures/__init__.py, tests/conftest.py, tests/test_example.py
    let init_path = PathBuf::from("/tmp/test_init/tests/fixtures/__init__.py");
    let conftest_path = PathBuf::from("/tmp/test_init/tests/conftest.py");
    let test_path = PathBuf::from("/tmp/test_init/tests/test_example.py");

    db.analyze_file(init_path.clone(), init_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let resolved = db.resolve_fixture_for_file(&test_path, "package_fixture");

    assert!(
        resolved.is_some(),
        "package_fixture should be resolvable from __init__.py"
    );
    assert_eq!(resolved.unwrap().file_path, init_path);
}

#[test]
#[timeout(30000)]
fn test_shadowed_imported_fixture() {
    // Test that a local fixture shadows an imported one
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def shared_name():
    return "from module"
"#;

    let conftest_content = r#"
from .module import *

import pytest

@pytest.fixture
def shared_name():
    return "from conftest"
"#;

    let test_content = r#"
def test_uses_shared(shared_name):
    pass
"#;

    let module_path = PathBuf::from("/tmp/test_shadow/module.py");
    let conftest_path = PathBuf::from("/tmp/test_shadow/conftest.py");
    let test_path = PathBuf::from("/tmp/test_shadow/test_example.py");

    db.analyze_file(module_path.clone(), module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // The conftest's definition should shadow the imported one
    let resolved = db.resolve_fixture_for_file(&test_path, "shared_name");

    assert!(resolved.is_some(), "shared_name should be resolvable");
    assert_eq!(
        resolved.unwrap().file_path,
        conftest_path,
        "Local conftest fixture should shadow imported fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_import_in_test_file() {
    // Test that imports in test files (not just conftest) work
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def module_fixture():
    return "from module"
"#;

    // Test file directly imports fixtures
    let test_content = r#"
from .fixture_module import *

def test_uses_imported(module_fixture):
    pass
"#;

    let module_path = PathBuf::from("/tmp/test_in_test/fixture_module.py");
    let test_path = PathBuf::from("/tmp/test_in_test/test_example.py");

    db.analyze_file(module_path.clone(), module_content);
    db.analyze_file(test_path.clone(), test_content);

    // The fixture should be available in the test file that imports it
    let resolved = db.resolve_fixture_for_file(&test_path, "module_fixture");

    // This tests whether imports in test files work, not just in conftest
    // Currently the resolution focuses on conftest.py, so this may not work
    // This test documents the expected behavior
    if resolved.is_some() {
        assert_eq!(resolved.unwrap().file_path, module_path);
    }
}

#[test]
#[timeout(30000)]
fn test_conftest_hierarchy_with_imports() {
    // Test that the conftest hierarchy works correctly with imports
    // Parent conftest imports fixtures, child tests should see them
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def parent_imported():
    return "imported in parent"
"#;

    let parent_conftest_content = r#"
from .fixture_module import *

import pytest

@pytest.fixture
def parent_local():
    return "local in parent"
"#;

    let child_conftest_content = r#"
import pytest

@pytest.fixture
def child_local():
    return "local in child"
"#;

    let test_content = r#"
def test_all_available(parent_imported, parent_local, child_local):
    pass
"#;

    let module_path = PathBuf::from("/tmp/test_hier/tests/fixture_module.py");
    let parent_conftest_path = PathBuf::from("/tmp/test_hier/tests/conftest.py");
    let child_conftest_path = PathBuf::from("/tmp/test_hier/tests/subdir/conftest.py");
    let test_path = PathBuf::from("/tmp/test_hier/tests/subdir/test_example.py");

    db.analyze_file(module_path.clone(), module_content);
    db.analyze_file(parent_conftest_path.clone(), parent_conftest_content);
    db.analyze_file(child_conftest_path.clone(), child_conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // All three fixtures should be resolvable from the test
    let resolved_imported = db.resolve_fixture_for_file(&test_path, "parent_imported");
    let resolved_parent = db.resolve_fixture_for_file(&test_path, "parent_local");
    let resolved_child = db.resolve_fixture_for_file(&test_path, "child_local");

    assert!(
        resolved_imported.is_some(),
        "parent_imported should be resolvable from child test"
    );
    assert!(
        resolved_parent.is_some(),
        "parent_local should be resolvable from child test"
    );
    assert!(
        resolved_child.is_some(),
        "child_local should be resolvable from child test"
    );

    // Verify the fixtures come from the correct files
    assert_eq!(resolved_imported.unwrap().file_path, module_path);
    assert_eq!(resolved_parent.unwrap().file_path, parent_conftest_path);
    assert_eq!(resolved_child.unwrap().file_path, child_conftest_path);
}

#[test]
#[timeout(30000)]
fn test_diagnostics_for_imported_fixtures() {
    // Test that undeclared fixture diagnostics don't fire for imported fixtures
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def imported_fixture():
    return "imported"
"#;

    let conftest_content = r#"
from .fixture_module import *
"#;

    let test_content = r#"
def test_uses_imported(imported_fixture):
    pass
"#;

    let module_path = PathBuf::from("/tmp/test_diag/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_diag/conftest.py");
    let test_path = PathBuf::from("/tmp/test_diag/test_example.py");

    db.analyze_file(module_path.clone(), module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Get undeclared fixtures for the test file
    let undeclared = db.get_undeclared_fixtures(&test_path);

    // imported_fixture should NOT be in undeclared since it's available via import
    let undeclared_names: Vec<&str> = undeclared.iter().map(|u| u.name.as_str()).collect();
    assert!(
        !undeclared_names.contains(&"imported_fixture"),
        "imported_fixture should not be flagged as undeclared"
    );
}

#[test]
#[timeout(30000)]
fn test_completion_includes_imported_fixtures() {
    // Test that completion suggestions include imported fixtures
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def completion_fixture():
    return "for completion"
"#;

    let conftest_content = r#"
from .fixture_module import *

import pytest

@pytest.fixture
def local_completion():
    return "local"
"#;

    let test_content = r#"
def test_something():
    pass
"#;

    let module_path = PathBuf::from("/tmp/test_compl/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_compl/conftest.py");
    let test_path = PathBuf::from("/tmp/test_compl/test_example.py");

    db.analyze_file(module_path.clone(), module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Get available fixtures for completion
    let available = db.get_available_fixtures(&test_path);
    let names: Vec<&str> = available.iter().map(|f| f.name.as_str()).collect();

    assert!(
        names.contains(&"completion_fixture"),
        "completion_fixture should be in available fixtures for completion"
    );
    assert!(
        names.contains(&"local_completion"),
        "local_completion should be in available fixtures for completion"
    );
}

#[test]
#[timeout(30000)]
fn test_empty_module_import() {
    // Test importing from a module that has no fixtures
    let db = FixtureDatabase::new();

    let empty_module_content = r#"
# This module has no fixtures
def helper():
    return "helper"
"#;

    let conftest_content = r#"
from .empty_module import *

import pytest

@pytest.fixture
def local_fixture():
    return "local"
"#;

    let test_content = r#"
def test_something(local_fixture):
    pass
"#;

    let empty_module_path = PathBuf::from("/tmp/test_empty/empty_module.py");
    let conftest_path = PathBuf::from("/tmp/test_empty/conftest.py");
    let test_path = PathBuf::from("/tmp/test_empty/test_example.py");

    db.analyze_file(empty_module_path.clone(), empty_module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Should still work without errors
    let resolved = db.resolve_fixture_for_file(&test_path, "local_fixture");
    assert!(
        resolved.is_some(),
        "local_fixture should still be resolvable"
    );
}

/// Test that parse error recovery keeps previous fixture data
#[test]
#[timeout(30000)]
fn test_parse_error_keeps_previous_data() {
    let db = FixtureDatabase::new();

    // First, analyze a valid file
    let valid_content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return "test value"
"#;

    let path = PathBuf::from("/tmp/test_parse_error/conftest.py");
    db.analyze_file(path.clone(), valid_content);

    // Verify fixture was detected
    assert!(
        db.definitions.contains_key("my_fixture"),
        "my_fixture should be detected initially"
    );

    // Now analyze the same file with syntax errors
    let invalid_content = r#"
import pytest

@pytest.fixture
def my_fixture(
    # Missing closing parenthesis - syntax error
    return "test value"
"#;

    db.analyze_file(path.clone(), invalid_content);

    // The fixture should still be present due to parse error recovery
    assert!(
        db.definitions.contains_key("my_fixture"),
        "my_fixture should still be present after parse error"
    );
}

/// Test that fixture definitions have correct end_line set
#[test]
#[timeout(30000)]
fn test_fixture_end_line_tracking() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def short_fixture():
    return 1

@pytest.fixture
def multiline_fixture():
    x = 1
    y = 2
    z = 3
    return x + y + z
"#;

    let path = PathBuf::from("/tmp/test_end_line/conftest.py");
    db.analyze_file(path.clone(), content);

    // Check short_fixture end_line
    let short_fixture = db.definitions.get("short_fixture").unwrap();
    assert_eq!(short_fixture[0].line, 5); // def line
    assert!(
        short_fixture[0].end_line >= short_fixture[0].line,
        "end_line should be >= line"
    );
    assert!(
        short_fixture[0].end_line <= 7,
        "short_fixture end_line should be around line 6-7"
    );

    // Check multiline_fixture end_line
    let multiline_fixture = db.definitions.get("multiline_fixture").unwrap();
    assert_eq!(multiline_fixture[0].line, 9); // def line
    assert!(
        multiline_fixture[0].end_line >= 13,
        "multiline_fixture end_line should be at least line 13"
    );
}

/// Test multi-level relative imports with different levels
#[test]
#[timeout(30000)]
fn test_multi_level_relative_import_levels() {
    let db = FixtureDatabase::new();

    // Test that the import level extraction works for various depths
    // The extract_fixture_imports function should correctly parse:
    // - level=1: from .module import *
    // - level=2: from ..module import *
    // - level=3: from ...module import *

    let single_dot_conftest = r#"
from .fixtures import *
"#;

    let double_dot_conftest = r#"
from ..shared.fixtures import *
"#;

    let triple_dot_conftest = r#"
from ...common.fixtures import *
"#;

    // Analyze these files to ensure they parse without errors
    // The actual import resolution depends on file structure
    let path1 = PathBuf::from("/tmp/test_levels/pkg/subpkg/conftest.py");
    let path2 = PathBuf::from("/tmp/test_levels/pkg/subpkg/deep/conftest.py");
    let path3 = PathBuf::from("/tmp/test_levels/pkg/subpkg/deep/deeper/conftest.py");

    // These should not panic or error
    db.analyze_file(path1.clone(), single_dot_conftest);
    db.analyze_file(path2.clone(), double_dot_conftest);
    db.analyze_file(path3.clone(), triple_dot_conftest);

    // Verify files were analyzed (file_cache should contain them)
    assert!(
        db.file_cache.contains_key(&path1),
        "single dot import file should be cached"
    );
    assert!(
        db.file_cache.contains_key(&path2),
        "double dot import file should be cached"
    );
    assert!(
        db.file_cache.contains_key(&path3),
        "triple dot import file should be cached"
    );
}

// ============ pytest_plugins Variable Tests ============

#[test]
#[timeout(30000)]
fn test_pytest_plugins_single_string() {
    let db = FixtureDatabase::new();

    let fixture_module_content = r#"
import pytest

@pytest.fixture
def plugin_fixture():
    return "from plugin"
"#;

    let conftest_content = r#"
pytest_plugins = "fixture_module"
"#;

    let test_content = r#"
def test_uses_plugin(plugin_fixture):
    pass
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_pytest_plugins/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_pytest_plugins/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pytest_plugins/test_example.py");

    db.analyze_file(fixture_module_path.clone(), fixture_module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let resolved = db.resolve_fixture_for_file(&test_path, "plugin_fixture");
    assert!(
        resolved.is_some(),
        "plugin_fixture should be resolvable via pytest_plugins single string"
    );
    assert_eq!(resolved.unwrap().file_path, fixture_module_path);
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_list_syntax() {
    let db = FixtureDatabase::new();

    let module_a_content = r#"
import pytest

@pytest.fixture
def fixture_a():
    return "a"
"#;

    let module_b_content = r#"
import pytest

@pytest.fixture
def fixture_b():
    return "b"
"#;

    let conftest_content = r#"
pytest_plugins = ["module_a", "module_b"]
"#;

    let test_content = r#"
def test_uses_both(fixture_a, fixture_b):
    pass
"#;

    let module_a_path = PathBuf::from("/tmp/test_pp_list/module_a.py");
    let module_b_path = PathBuf::from("/tmp/test_pp_list/module_b.py");
    let conftest_path = PathBuf::from("/tmp/test_pp_list/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pp_list/test_example.py");

    db.analyze_file(module_a_path.clone(), module_a_content);
    db.analyze_file(module_b_path.clone(), module_b_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let resolved_a = db.resolve_fixture_for_file(&test_path, "fixture_a");
    assert!(
        resolved_a.is_some(),
        "fixture_a should be resolvable via pytest_plugins list"
    );
    assert_eq!(resolved_a.unwrap().file_path, module_a_path);

    let resolved_b = db.resolve_fixture_for_file(&test_path, "fixture_b");
    assert!(
        resolved_b.is_some(),
        "fixture_b should be resolvable via pytest_plugins list"
    );
    assert_eq!(resolved_b.unwrap().file_path, module_b_path);
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_tuple_resolution() {
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def tuple_fixture():
    return "from tuple"
"#;

    let conftest_content = r#"
pytest_plugins = ("fixture_module",)
"#;

    let test_content = r#"
def test_uses_tuple(tuple_fixture):
    pass
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_pp_tuple/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_pp_tuple/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pp_tuple/test_example.py");

    db.analyze_file(fixture_module_path.clone(), module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let resolved = db.resolve_fixture_for_file(&test_path, "tuple_fixture");
    assert!(
        resolved.is_some(),
        "tuple_fixture should be resolvable via pytest_plugins tuple"
    );
    assert_eq!(resolved.unwrap().file_path, fixture_module_path);
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_dotted_path() {
    let db = FixtureDatabase::new();

    let fixture_content = r#"
import pytest

@pytest.fixture
def nested_fixture():
    return "nested"
"#;

    let conftest_content = r#"
pytest_plugins = "myapp.sub.fixtures"
"#;

    let test_content = r#"
def test_uses_nested(nested_fixture):
    pass
"#;

    let fixture_path = PathBuf::from("/tmp/test_pp_dotted/myapp/sub/fixtures.py");
    let conftest_path = PathBuf::from("/tmp/test_pp_dotted/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pp_dotted/test_example.py");

    db.analyze_file(fixture_path.clone(), fixture_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let resolved = db.resolve_fixture_for_file(&test_path, "nested_fixture");
    assert!(
        resolved.is_some(),
        "nested_fixture should be resolvable via dotted pytest_plugins path"
    );
    assert_eq!(resolved.unwrap().file_path, fixture_path);
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_in_test_file() {
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def test_file_plugin_fixture():
    return "from test file plugin"
"#;

    let test_content = r#"
pytest_plugins = "fixture_module"

def test_uses_plugin(test_file_plugin_fixture):
    pass
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_pp_testfile/fixture_module.py");
    let test_path = PathBuf::from("/tmp/test_pp_testfile/test_example.py");

    db.analyze_file(fixture_module_path.clone(), module_content);
    db.analyze_file(test_path.clone(), test_content);

    let resolved = db.resolve_fixture_for_file(&test_path, "test_file_plugin_fixture");
    assert!(
        resolved.is_some(),
        "test_file_plugin_fixture should be resolvable via pytest_plugins in test file"
    );
    assert_eq!(resolved.unwrap().file_path, fixture_module_path);
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_transitive() {
    let db = FixtureDatabase::new();

    let module_c_content = r#"
import pytest

@pytest.fixture
def deep_plugin_fixture():
    return "deep"
"#;

    let module_b_content = r#"
pytest_plugins = ["module_c"]

import pytest

@pytest.fixture
def mid_plugin_fixture():
    return "mid"
"#;

    let conftest_content = r#"
pytest_plugins = ["module_b"]
"#;

    let test_content = r#"
def test_uses_deep(deep_plugin_fixture, mid_plugin_fixture):
    pass
"#;

    let module_c_path = PathBuf::from("/tmp/test_pp_transitive/module_c.py");
    let module_b_path = PathBuf::from("/tmp/test_pp_transitive/module_b.py");
    let conftest_path = PathBuf::from("/tmp/test_pp_transitive/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pp_transitive/test_example.py");

    db.analyze_file(module_c_path.clone(), module_c_content);
    db.analyze_file(module_b_path.clone(), module_b_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let resolved_deep = db.resolve_fixture_for_file(&test_path, "deep_plugin_fixture");
    assert!(
        resolved_deep.is_some(),
        "deep_plugin_fixture should be resolvable via transitive pytest_plugins"
    );
    assert_eq!(resolved_deep.unwrap().file_path, module_c_path);

    let resolved_mid = db.resolve_fixture_for_file(&test_path, "mid_plugin_fixture");
    assert!(
        resolved_mid.is_some(),
        "mid_plugin_fixture should be resolvable via pytest_plugins"
    );
    assert_eq!(resolved_mid.unwrap().file_path, module_b_path);
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_dynamic_value_ignored() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
pytest_plugins = get_plugins()

import pytest

@pytest.fixture
def local_fixture():
    return "local"
"#;

    let test_content = r#"
def test_local(local_fixture):
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test_pp_dynamic/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pp_dynamic/test_example.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Should not crash, local fixture should still work
    let resolved = db.resolve_fixture_for_file(&test_path, "local_fixture");
    assert!(
        resolved.is_some(),
        "local_fixture should still be resolvable even with dynamic pytest_plugins"
    );
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_available_fixtures() {
    let db = FixtureDatabase::new();

    let module_content = r#"
import pytest

@pytest.fixture
def plugin_avail_fixture():
    return "available"
"#;

    let conftest_content = r#"
pytest_plugins = ["fixture_module"]

import pytest

@pytest.fixture
def conftest_fixture():
    return "conftest"
"#;

    let test_content = r#"
def test_something():
    pass
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_pp_avail/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_pp_avail/conftest.py");
    let test_path = PathBuf::from("/tmp/test_pp_avail/test_example.py");

    db.analyze_file(fixture_module_path.clone(), module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let available = db.get_available_fixtures(&test_path);
    let names: Vec<&str> = available.iter().map(|f| f.name.as_str()).collect();

    assert!(
        names.contains(&"conftest_fixture"),
        "conftest_fixture should be in available fixtures"
    );
    assert!(
        names.contains(&"plugin_avail_fixture"),
        "plugin_avail_fixture should be in available fixtures (via pytest_plugins)"
    );
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_in_venv_plugin_module() {
    let db = FixtureDatabase::new();

    // Simulate a venv plugin that declares pytest_plugins to load an internal sub-module.
    // The plugin __init__.py is registered via pytest11 entry point and declares:
    //   pytest_plugins = ["my_plugin.internal_fixtures"]
    // The internal_fixtures.py defines fixtures that should be discoverable.

    let plugin_init_content = r#"
pytest_plugins = ["my_plugin.internal_fixtures"]
"#;

    let internal_fixtures_content = r#"
import pytest

@pytest.fixture
def venv_internal_fixture():
    return "from internal sub-module"
"#;

    let conftest_content = r#"
import pytest

@pytest.fixture
def local_fixture():
    return "local"
"#;

    let test_content = r#"
def test_uses_venv_fixture(venv_internal_fixture):
    pass
"#;

    let site_packages = PathBuf::from("/tmp/test_venv_pp/venv/lib/python3.11/site-packages");
    let plugin_init_path = site_packages.join("my_plugin/__init__.py");
    let internal_path = site_packages.join("my_plugin/internal_fixtures.py");
    let conftest_path = PathBuf::from("/tmp/test_venv_pp/conftest.py");
    let test_path = PathBuf::from("/tmp/test_venv_pp/test_example.py");

    // Register site-packages path (normally done by scan_venv_site_packages)
    db.site_packages_paths
        .lock()
        .unwrap()
        .push(site_packages.clone());

    // Analyze the plugin init (as if scanned via entry points)
    db.analyze_file(plugin_init_path.clone(), plugin_init_content);
    // Analyze the internal fixtures module
    db.analyze_file(internal_path.clone(), internal_fixtures_content);
    // Analyze local project files
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // The plugin's pytest_plugins should resolve "my_plugin.internal_fixtures"
    // via the site-packages fallback in resolve_absolute_import
    let resolved = db.resolve_fixture_for_file(&test_path, "venv_internal_fixture");
    assert!(
        resolved.is_some(),
        "venv_internal_fixture should be resolvable via venv plugin pytest_plugins"
    );
    assert_eq!(resolved.unwrap().file_path, internal_path);
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_annotated_assignment() {
    let db = FixtureDatabase::new();

    let fixture_module_content = r#"
import pytest

@pytest.fixture
def annotated_plugin_fixture():
    return "from annotated plugin"
"#;

    let conftest_content = r#"
pytest_plugins: list[str] = ["fixture_module"]
"#;

    let test_content = r#"
def test_uses_annotated(annotated_plugin_fixture):
    pass
"#;

    let fixture_module_path = PathBuf::from("/tmp/test_annassign/fixture_module.py");
    let conftest_path = PathBuf::from("/tmp/test_annassign/conftest.py");
    let test_path = PathBuf::from("/tmp/test_annassign/test_example.py");

    db.analyze_file(fixture_module_path.clone(), fixture_module_content);
    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let resolved = db.resolve_fixture_for_file(&test_path, "annotated_plugin_fixture");
    assert!(
        resolved.is_some(),
        "annotated_plugin_fixture should be resolvable via annotated pytest_plugins"
    );
    assert_eq!(resolved.unwrap().file_path, fixture_module_path);
}

#[test]
#[timeout(30000)]
fn test_pytest_plugins_last_assignment_wins() {
    let db = FixtureDatabase::new();

    let module_a_content = r#"
import pytest

@pytest.fixture
def fixture_a():
    return "a"
"#;

    let module_b_content = r#"
import pytest

@pytest.fixture
def fixture_b():
    return "b"
"#;

    let conftest_content = r#"
pytest_plugins = ["module_a"]
pytest_plugins = ["module_b"]
"#;

    let module_a_path = PathBuf::from("/tmp/test_last_wins/module_a.py");
    let module_b_path = PathBuf::from("/tmp/test_last_wins/module_b.py");
    let conftest_path = PathBuf::from("/tmp/test_last_wins/conftest.py");

    db.analyze_file(module_a_path.clone(), module_a_content);
    db.analyze_file(module_b_path.clone(), module_b_content);
    db.analyze_file(conftest_path.clone(), conftest_content);

    // Only module_b should be imported via conftest (last assignment wins)
    let imported = db.is_fixture_imported_in_file("fixture_b", &conftest_path);
    assert!(
        imported,
        "fixture_b should be imported (last pytest_plugins assignment)"
    );

    let imported_a = db.is_fixture_imported_in_file("fixture_a", &conftest_path);
    assert!(
        !imported_a,
        "fixture_a should NOT be imported (overwritten by second pytest_plugins)"
    );
}

#[test]
#[timeout(30000)]
fn test_editable_install_is_third_party() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    // Simulate workspace root (canonicalize to match analyze_file behavior)
    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical);

    // Simulate an external editable install source root
    let external_src = tempdir().unwrap();
    let external_src_canonical = external_src.path().canonicalize().unwrap();
    db.editable_install_roots.lock().unwrap().push(
        pytest_language_server::fixtures::EditableInstall {
            package_name: "external_pkg".to_string(),
            raw_package_name: "external_pkg".to_string(),
            source_root: external_src_canonical,
            site_packages: PathBuf::from("/fake/site-packages"),
        },
    );

    // Create a fixture file in the external source root
    let fixture_file = external_src.path().join("plugin.py");
    let fixture_content = r#"
import pytest

@pytest.fixture
def ext_editable_fixture():
    return "from external editable"
"#;
    fs::write(&fixture_file, fixture_content).unwrap();
    db.analyze_file(fixture_file.clone(), fixture_content);

    // The fixture should be marked as third-party
    let defs = db.definitions.get("ext_editable_fixture").unwrap();
    assert!(
        defs[0].is_third_party,
        "Fixture from external editable install should be third-party"
    );
}

#[test]
#[timeout(30000)]
fn test_editable_install_in_workspace_not_third_party() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    // Simulate workspace root that CONTAINS the editable source (canonicalize paths)
    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    let editable_src = workspace_canonical
        .join("packages")
        .join("mylib")
        .join("src");
    std::fs::create_dir_all(&editable_src).unwrap();

    *db.workspace_root.lock().unwrap() = Some(workspace_canonical);
    db.editable_install_roots.lock().unwrap().push(
        pytest_language_server::fixtures::EditableInstall {
            package_name: "mylib".to_string(),
            raw_package_name: "mylib".to_string(),
            source_root: editable_src.clone(),
            site_packages: PathBuf::from("/fake/site-packages"),
        },
    );

    // Create a fixture in the in-workspace editable source
    let fixture_file = editable_src.join("conftest.py");
    let fixture_content = r#"
import pytest

@pytest.fixture
def local_editable_fixture():
    return "from local editable"
"#;
    fs::write(&fixture_file, fixture_content).unwrap();
    db.analyze_file(fixture_file.clone(), fixture_content);

    // Should NOT be third-party since source is inside workspace
    let defs = db.definitions.get("local_editable_fixture").unwrap();
    assert!(
        !defs[0].is_third_party,
        "Fixture from in-workspace editable install should NOT be third-party"
    );
}

#[test]
#[timeout(30000)]
fn test_editable_install_unused_fixtures_excluded() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    // Simulate an external editable install (canonicalize paths)
    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    let external_src = tempdir().unwrap();
    let external_src_canonical = external_src.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical);
    db.editable_install_roots.lock().unwrap().push(
        pytest_language_server::fixtures::EditableInstall {
            package_name: "ext_pkg".to_string(),
            raw_package_name: "ext_pkg".to_string(),
            source_root: external_src_canonical,
            site_packages: PathBuf::from("/fake/site-packages"),
        },
    );

    // Create fixtures in external source
    let fixture_file = external_src.path().join("plugin.py");
    let fixture_content = r#"
import pytest

@pytest.fixture
def third_party_editable_fixture():
    return "external"
"#;
    fs::write(&fixture_file, fixture_content).unwrap();
    db.analyze_file(fixture_file, fixture_content);

    // Create a local fixture
    let local_file = workspace.path().join("conftest.py");
    let local_content = r#"
import pytest

@pytest.fixture
def unused_local_fixture():
    return "local"
"#;
    fs::write(&local_file, local_content).unwrap();
    db.analyze_file(local_file, local_content);

    let unused = db.get_unused_fixtures();
    let unused_names: Vec<&str> = unused.iter().map(|(_, name)| name.as_str()).collect();

    // Third-party editable fixture should be excluded from unused report
    assert!(
        !unused_names.contains(&"third_party_editable_fixture"),
        "Third-party editable fixture should be excluded from unused report"
    );
    // Local fixture should appear in unused report
    assert!(
        unused_names.contains(&"unused_local_fixture"),
        "Local unused fixture should appear in unused report"
    );
}

#[test]
#[timeout(30000)]
fn test_autouse_fixture_not_reported_as_unused() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture(autouse=True)
def auto_setup():
    yield

@pytest.fixture
def regular_fixture():
    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_something():
    assert True
"#;
    let test_path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(test_path.clone(), test_content);

    let unused = db.get_unused_fixtures();
    let unused_names: Vec<&str> = unused.iter().map(|(_, name)| name.as_str()).collect();

    assert!(
        !unused_names.contains(&"auto_setup"),
        "autouse fixture should NOT be reported as unused"
    );
    assert!(
        unused_names.contains(&"regular_fixture"),
        "regular unused fixture should be reported as unused"
    );
}

#[test]
#[timeout(30000)]
fn test_autouse_with_scope_not_reported_unused() {
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(scope="session", autouse=True)
def session_auto():
    yield
"#;
    let file_path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(file_path.clone(), content);

    let unused = db.get_unused_fixtures();
    let unused_names: Vec<&str> = unused.iter().map(|(_, name)| name.as_str()).collect();

    assert!(
        !unused_names.contains(&"session_auto"),
        "autouse fixture with scope should NOT be reported as unused"
    );
}

#[test]
#[timeout(30000)]
fn test_extract_fixture_autouse() {
    use rustpython_parser::{parse, Mode};

    let cases = vec![
        ("@pytest.fixture(autouse=True)\ndef f(): pass", true),
        ("@pytest.fixture(autouse=False)\ndef f(): pass", false),
        ("@pytest.fixture\ndef f(): pass", false),
        ("@pytest.fixture()\ndef f(): pass", false),
        (
            "@pytest.fixture(scope=\"session\", autouse=True)\ndef f(): pass",
            true,
        ),
    ];

    for (source, expected) in cases {
        let parsed = parse(source, Mode::Module, "<test>").unwrap();
        let module = parsed.as_module().unwrap();
        let stmt = &module.body[0];
        if let rustpython_parser::ast::Stmt::FunctionDef(func) = stmt {
            let decorator = &func.decorator_list[0];
            let result =
                pytest_language_server::fixtures::decorators::extract_fixture_autouse(decorator);
            assert_eq!(
                result, expected,
                "extract_fixture_autouse({:?}) should be {}, got {}",
                source, expected, result
            );
        } else {
            panic!("Expected FunctionDef, got {:?}", stmt);
        }
    }
}

#[test]
#[timeout(30000)]
fn test_workspace_editable_plugin_fixture_is_plugin_flag() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical.clone());

    // Create a plugin module inside the workspace
    let pkg_dir = workspace_canonical.join("mypackage");
    fs::create_dir_all(&pkg_dir).unwrap();
    let plugin_file = pkg_dir.join("plugin.py");
    let plugin_content = r#"
import pytest

@pytest.fixture
def ws_plugin_fixture():
    return "from workspace plugin"
"#;
    fs::write(&plugin_file, plugin_content).unwrap();

    // Mark the file as a plugin file (simulating what scan_single_plugin_file does)
    let canonical_plugin = plugin_file.canonicalize().unwrap();
    db.plugin_fixture_files.insert(canonical_plugin.clone(), ());

    db.analyze_file(canonical_plugin.clone(), plugin_content);

    let defs = db.definitions.get("ws_plugin_fixture").unwrap();
    assert_eq!(defs.len(), 1);
    assert!(
        defs[0].is_plugin,
        "Fixture from plugin file should have is_plugin=true"
    );
    assert!(
        !defs[0].is_third_party,
        "Fixture from workspace plugin should NOT be is_third_party"
    );
}

#[test]
#[timeout(30000)]
fn test_workspace_editable_plugin_fixture_resolution() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical.clone());

    // Create a plugin module inside the workspace (not conftest, not test file)
    let pkg_dir = workspace_canonical.join("mypackage");
    fs::create_dir_all(&pkg_dir).unwrap();
    let plugin_file = pkg_dir.join("plugin.py");
    let plugin_content = r#"
import pytest

@pytest.fixture
def plugin_fixture():
    return "from plugin"
"#;
    fs::write(&plugin_file, plugin_content).unwrap();
    let canonical_plugin = plugin_file.canonicalize().unwrap();
    db.plugin_fixture_files.insert(canonical_plugin.clone(), ());
    db.analyze_file(canonical_plugin.clone(), plugin_content);

    // Create a test file in the workspace
    let tests_dir = workspace_canonical.join("tests");
    fs::create_dir_all(&tests_dir).unwrap();
    let test_file = tests_dir.join("test_foo.py");
    let test_content = r#"
def test_something(plugin_fixture):
    assert plugin_fixture == "from plugin"
"#;
    fs::write(&test_file, test_content).unwrap();
    let canonical_test = test_file.canonicalize().unwrap();
    db.analyze_file(canonical_test.clone(), test_content);

    // The fixture should be resolvable from the test file via find_closest_definition
    let resolved = db.find_fixture_definition(&canonical_test, 1, 19);
    assert!(
        resolved.is_some(),
        "Plugin fixture should be resolvable from test file via find_closest_definition"
    );
    let resolved = resolved.unwrap();
    assert_eq!(resolved.name, "plugin_fixture");
    assert_eq!(resolved.file_path, canonical_plugin);
}

#[test]
#[timeout(30000)]
fn test_workspace_editable_plugin_available_fixtures() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical.clone());

    // Create a plugin module inside the workspace
    let pkg_dir = workspace_canonical.join("mypackage");
    fs::create_dir_all(&pkg_dir).unwrap();
    let plugin_file = pkg_dir.join("plugin.py");
    let plugin_content = r#"
import pytest

@pytest.fixture
def available_plugin_fixture():
    return "available"
"#;
    fs::write(&plugin_file, plugin_content).unwrap();
    let canonical_plugin = plugin_file.canonicalize().unwrap();
    db.plugin_fixture_files.insert(canonical_plugin.clone(), ());
    db.analyze_file(canonical_plugin, plugin_content);

    // Create a test file
    let tests_dir = workspace_canonical.join("tests");
    fs::create_dir_all(&tests_dir).unwrap();
    let test_file = tests_dir.join("test_bar.py");
    let test_content = r#"
def test_bar(available_plugin_fixture):
    pass
"#;
    fs::write(&test_file, test_content).unwrap();
    let canonical_test = test_file.canonicalize().unwrap();
    db.analyze_file(canonical_test.clone(), test_content);

    // get_available_fixtures should include the plugin fixture
    let available = db.get_available_fixtures(&canonical_test);
    let available_names: Vec<&str> = available.iter().map(|d| d.name.as_str()).collect();
    assert!(
        available_names.contains(&"available_plugin_fixture"),
        "Plugin fixture should appear in available fixtures for test file. Got: {:?}",
        available_names
    );
}

#[test]
#[timeout(30000)]
fn test_workspace_editable_plugin_conftest_wins_over_plugin() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical.clone());

    // Create a plugin fixture
    let pkg_dir = workspace_canonical.join("mypackage");
    fs::create_dir_all(&pkg_dir).unwrap();
    let plugin_file = pkg_dir.join("plugin.py");
    let plugin_content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "from plugin"
"#;
    fs::write(&plugin_file, plugin_content).unwrap();
    let canonical_plugin = plugin_file.canonicalize().unwrap();
    db.plugin_fixture_files.insert(canonical_plugin.clone(), ());
    db.analyze_file(canonical_plugin.clone(), plugin_content);

    // Create a conftest.py that overrides the same fixture
    let tests_dir = workspace_canonical.join("tests");
    fs::create_dir_all(&tests_dir).unwrap();
    let conftest_file = tests_dir.join("conftest.py");
    let conftest_content = r#"
import pytest

@pytest.fixture
def shared_fixture():
    return "from conftest"
"#;
    fs::write(&conftest_file, conftest_content).unwrap();
    let canonical_conftest = conftest_file.canonicalize().unwrap();
    db.analyze_file(canonical_conftest.clone(), conftest_content);

    // Create a test file
    let test_file = tests_dir.join("test_priority.py");
    let test_content = r#"
def test_priority(shared_fixture):
    pass
"#;
    fs::write(&test_file, test_content).unwrap();
    let canonical_test = test_file.canonicalize().unwrap();
    db.analyze_file(canonical_test.clone(), test_content);

    // Conftest should win over plugin (Priority 2 > Priority 3)
    let resolved = db.find_fixture_definition(&canonical_test, 1, 20);
    assert!(resolved.is_some(), "shared_fixture should be resolvable");
    let resolved = resolved.unwrap();
    assert_eq!(
        resolved.file_path, canonical_conftest,
        "conftest.py fixture should win over plugin fixture"
    );
}

#[test]
#[timeout(30000)]
fn test_workspace_editable_plugin_fixture_is_available_for_undeclared_check() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical.clone());

    // Create a plugin fixture
    let pkg_dir = workspace_canonical.join("mypackage");
    fs::create_dir_all(&pkg_dir).unwrap();
    let plugin_file = pkg_dir.join("plugin.py");
    let plugin_content = r#"
import pytest

@pytest.fixture
def undeclared_check_fixture():
    return "plugin"
"#;
    fs::write(&plugin_file, plugin_content).unwrap();
    let canonical_plugin = plugin_file.canonicalize().unwrap();
    db.plugin_fixture_files.insert(canonical_plugin.clone(), ());
    db.analyze_file(canonical_plugin, plugin_content);

    // Plugin fixture should appear in available fixtures for any test file
    // (this is what the undeclared fixture checker uses under the hood)
    let tests_dir = workspace_canonical.join("tests");
    fs::create_dir_all(&tests_dir).unwrap();
    let test_file = tests_dir.join("test_x.py");
    let test_content = "def test_x(): pass\n";
    fs::write(&test_file, test_content).unwrap();
    let canonical_test = test_file.canonicalize().unwrap();
    db.analyze_file(canonical_test.clone(), test_content);

    let available = db.get_available_fixtures(&canonical_test);
    let available_names: Vec<&str> = available.iter().map(|d| d.name.as_str()).collect();
    assert!(
        available_names.contains(&"undeclared_check_fixture"),
        "Plugin fixture should be recognized as available (used by undeclared fixture checker). Got: {:?}",
        available_names
    );
}

#[test]
#[timeout(30000)]
fn test_workspace_editable_plugin_resolve_fixture_for_file() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical.clone());

    // Create a plugin fixture
    let pkg_dir = workspace_canonical.join("mypackage");
    fs::create_dir_all(&pkg_dir).unwrap();
    let plugin_file = pkg_dir.join("plugin.py");
    let plugin_content = r#"
import pytest

@pytest.fixture
def resolve_for_file_fixture():
    return "plugin"
"#;
    fs::write(&plugin_file, plugin_content).unwrap();
    let canonical_plugin = plugin_file.canonicalize().unwrap();
    db.plugin_fixture_files.insert(canonical_plugin.clone(), ());
    db.analyze_file(canonical_plugin.clone(), plugin_content);

    // resolve_fixture_for_file (used by diagnostics) should also find plugin fixtures
    let test_file = workspace_canonical.join("tests").join("test_resolve.py");
    let resolved = db.resolve_fixture_for_file(&test_file, "resolve_for_file_fixture");
    assert!(
        resolved.is_some(),
        "resolve_fixture_for_file should find plugin fixtures"
    );
    assert_eq!(resolved.unwrap().file_path, canonical_plugin);
}

#[test]
#[timeout(30000)]
fn test_external_editable_plugin_is_third_party_and_resolvable() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical.clone());

    // Create an external editable install (source outside workspace)
    let external_src = tempdir().unwrap();
    let external_src_canonical = external_src.path().canonicalize().unwrap();
    db.editable_install_roots.lock().unwrap().push(
        pytest_language_server::fixtures::EditableInstall {
            package_name: "ext_plugin".to_string(),
            raw_package_name: "ext_plugin".to_string(),
            source_root: external_src_canonical.clone(),
            site_packages: PathBuf::from("/fake/site-packages"),
        },
    );

    // Create a fixture in the external source
    let plugin_file = external_src_canonical.join("plugin.py");
    let plugin_content = r#"
import pytest

@pytest.fixture
def ext_plugin_fixture():
    return "external"
"#;
    fs::write(&plugin_file, plugin_content).unwrap();
    let canonical_plugin = plugin_file.canonicalize().unwrap();
    db.plugin_fixture_files.insert(canonical_plugin.clone(), ());
    db.analyze_file(canonical_plugin.clone(), plugin_content);

    // The fixture should be third-party AND is_plugin
    let defs = db.definitions.get("ext_plugin_fixture").unwrap();
    assert!(
        defs[0].is_third_party,
        "External editable should be third-party"
    );
    assert!(defs[0].is_plugin, "Should also be marked as plugin");

    // It should be resolvable from a test file via Priority 4 (third-party)
    let test_file = workspace_canonical.join("tests").join("test_ext.py");
    let resolved = db.resolve_fixture_for_file(&test_file, "ext_plugin_fixture");
    assert!(
        resolved.is_some(),
        "External editable plugin fixture should be resolvable as third-party"
    );
}

#[test]
#[timeout(30000)]
fn test_non_plugin_file_fixture_not_marked_is_plugin() {
    let db = FixtureDatabase::new();

    // A regular conftest.py fixture should NOT have is_plugin
    let conftest_content = r#"
import pytest

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

    let defs = db.definitions.get("regular_fixture").unwrap();
    assert!(
        !defs[0].is_plugin,
        "Regular conftest fixture should NOT be marked as plugin"
    );
    assert!(
        !defs[0].is_third_party,
        "Regular conftest fixture should NOT be third-party"
    );
}

#[test]
#[timeout(30000)]
fn test_cli_and_resolver_agree_on_workspace_editable_plugin_fixtures() {
    use std::fs;
    use tempfile::tempdir;

    let db = FixtureDatabase::new();

    let workspace = tempdir().unwrap();
    let workspace_canonical = workspace.path().canonicalize().unwrap();
    *db.workspace_root.lock().unwrap() = Some(workspace_canonical.clone());

    // Create a conftest fixture
    let conftest_file = workspace_canonical.join("conftest.py");
    let conftest_content = r#"
import pytest

@pytest.fixture
def conftest_fixture():
    return "conftest"
"#;
    fs::write(&conftest_file, conftest_content).unwrap();
    let canonical_conftest = conftest_file.canonicalize().unwrap();
    db.analyze_file(canonical_conftest, conftest_content);

    // Create a plugin fixture (not in conftest, not test file)
    let pkg_dir = workspace_canonical.join("mypackage");
    fs::create_dir_all(&pkg_dir).unwrap();
    let plugin_file = pkg_dir.join("plugin.py");
    let plugin_content = r#"
import pytest

@pytest.fixture
def plugin_only_fixture():
    return "plugin"
"#;
    fs::write(&plugin_file, plugin_content).unwrap();
    let canonical_plugin = plugin_file.canonicalize().unwrap();
    db.plugin_fixture_files.insert(canonical_plugin.clone(), ());
    db.analyze_file(canonical_plugin, plugin_content);

    // Create a test file
    let test_file = workspace_canonical.join("test_agree.py");
    let test_content = r#"
def test_agree(conftest_fixture, plugin_only_fixture):
    pass
"#;
    fs::write(&test_file, test_content).unwrap();
    let canonical_test = test_file.canonicalize().unwrap();
    db.analyze_file(canonical_test.clone(), test_content);

    // CLI view: all definitions
    let all_fixture_names: std::collections::HashSet<String> = db
        .definitions
        .iter()
        .map(|entry| entry.key().clone())
        .collect();

    // LSP view: available fixtures for the test file
    let available = db.get_available_fixtures(&canonical_test);
    let available_names: std::collections::HashSet<String> =
        available.iter().map(|d| d.name.clone()).collect();

    // Every fixture visible in the CLI should also be available in the LSP
    assert!(
        available_names.contains("conftest_fixture"),
        "conftest_fixture should be in available fixtures"
    );
    assert!(
        available_names.contains("plugin_only_fixture"),
        "plugin_only_fixture should be in available fixtures (was missing before fix)"
    );
    // Verify they agree
    for name in &all_fixture_names {
        assert!(
            available_names.contains(name),
            "Fixture '{}' is in definitions (CLI view) but NOT in available fixtures (LSP view)",
            name
        );
    }
}

/// End-to-end test: set up a realistic workspace with a venv containing a
/// workspace-local editable install that registers a pytest11 entry point,
/// then call `scan_workspace` and verify that fixtures from the plugin are
/// discoverable, resolvable, and available for completions / diagnostics.
#[test]
#[timeout(30000)]
fn test_e2e_scan_workspace_editable_plugin_entry_point() {
    use std::fs;
    use tempfile::tempdir;

    let workspace = tempdir().unwrap();
    let ws = workspace.path().canonicalize().unwrap();

    // ── workspace source ──────────────────────────────────────────────
    //   mypackage/
    //     __init__.py
    //     plugin.py          <- pytest11 entry point module
    //     helpers.py         <- imported by plugin.py via pytest_plugins
    //   tests/
    //     test_foo.py        <- uses the plugin fixtures
    //   conftest.py          <- root conftest with a normal fixture

    let pkg_dir = ws.join("mypackage");
    fs::create_dir_all(&pkg_dir).unwrap();
    fs::write(pkg_dir.join("__init__.py"), "").unwrap();

    let plugin_content = r#"
import pytest

pytest_plugins = ["mypackage.helpers"]

@pytest.fixture
def direct_plugin_fixture():
    """Fixture defined directly in the plugin entry point module."""
    return "direct"
"#;
    fs::write(pkg_dir.join("plugin.py"), plugin_content).unwrap();

    let helpers_content = r#"
import pytest

@pytest.fixture
def transitive_plugin_fixture():
    """Fixture imported transitively via pytest_plugins in plugin.py."""
    return "transitive"
"#;
    fs::write(pkg_dir.join("helpers.py"), helpers_content).unwrap();

    let conftest_content = r#"
import pytest

@pytest.fixture
def root_conftest_fixture():
    return "conftest"
"#;
    fs::write(ws.join("conftest.py"), conftest_content).unwrap();

    let tests_dir = ws.join("tests");
    fs::create_dir_all(&tests_dir).unwrap();
    let test_content = r#"
def test_uses_plugin(direct_plugin_fixture, transitive_plugin_fixture, root_conftest_fixture):
    pass
"#;
    fs::write(tests_dir.join("test_foo.py"), test_content).unwrap();

    // ── fake venv with editable install ──────────────────────────────
    let site_packages = ws
        .join(".venv")
        .join("lib")
        .join("python3.12")
        .join("site-packages");
    fs::create_dir_all(&site_packages).unwrap();

    // dist-info with pytest11 entry point
    let dist_info = site_packages.join("mypackage-0.1.0.dist-info");
    fs::create_dir_all(&dist_info).unwrap();
    fs::write(
        dist_info.join("entry_points.txt"),
        "[pytest11]\nmyplugin = mypackage.plugin\n",
    )
    .unwrap();

    // direct_url.json marking it as editable
    let direct_url = serde_json::json!({
        "url": format!("file://{}", ws.display()),
        "dir_info": { "editable": true }
    });
    fs::write(
        dist_info.join("direct_url.json"),
        serde_json::to_string(&direct_url).unwrap(),
    )
    .unwrap();

    // METADATA (needed by extract_package_name_from_dist_info indirectly, but
    // the dist-info dirname is sufficient for discover_editable_installs)

    // .pth file pointing at workspace root so Python can find mypackage
    fs::write(
        site_packages.join("mypackage.pth"),
        format!("{}\n", ws.display()),
    )
    .unwrap();

    // ── scan ──────────────────────────────────────────────────────────
    let db = FixtureDatabase::new();
    db.scan_workspace(&ws);

    // ── assertions ────────────────────────────────────────────────────
    let canonical_test = ws.join("tests").join("test_foo.py");
    // (files are already canonical because ws is canonical and we created them there)

    // 1. The plugin fixture should be in definitions
    assert!(
        db.definitions.contains_key("direct_plugin_fixture"),
        "direct_plugin_fixture should be in definitions after scan_workspace. \
         definitions keys: {:?}",
        db.definitions
            .iter()
            .map(|e| e.key().clone())
            .collect::<Vec<_>>()
    );

    // 2. Check is_third_party – workspace-local editable should NOT be third-party
    {
        let defs = db.definitions.get("direct_plugin_fixture").unwrap();
        assert!(
            !defs[0].is_third_party,
            "Workspace-local editable plugin fixture should NOT be third_party"
        );
    }

    // 3. The fixture should be resolvable from the test file
    let resolved = db.resolve_fixture_for_file(&canonical_test, "direct_plugin_fixture");
    assert!(
        resolved.is_some(),
        "direct_plugin_fixture should be resolvable from test file via resolve_fixture_for_file. \
         definitions: {:?}",
        db.definitions
            .get("direct_plugin_fixture")
            .map(|d| d.value().clone())
    );

    // 4. The fixture should appear in available fixtures (completions / diagnostics)
    let available = db.get_available_fixtures(&canonical_test);
    let available_names: Vec<&str> = available.iter().map(|d| d.name.as_str()).collect();
    assert!(
        available_names.contains(&"direct_plugin_fixture"),
        "direct_plugin_fixture should be in available fixtures for test file. Got: {:?}",
        available_names
    );
    assert!(
        available_names.contains(&"root_conftest_fixture"),
        "root_conftest_fixture should be in available fixtures. Got: {:?}",
        available_names
    );

    // 5. Transitive plugin fixture (imported via pytest_plugins) should also work
    // Note: this depends on Phase 4 (scan_imported_fixture_modules) propagating
    // plugin status through pytest_plugins declarations.
    let transitive_available = available_names.contains(&"transitive_plugin_fixture");
    let transitive_in_defs = db.definitions.contains_key("transitive_plugin_fixture");
    assert!(
        transitive_in_defs,
        "transitive_plugin_fixture should be in definitions (discovered via pytest_plugins). \
         All definitions: {:?}",
        db.definitions
            .iter()
            .map(|e| e.key().clone())
            .collect::<Vec<_>>()
    );
    assert!(
        transitive_available,
        "transitive_plugin_fixture should be available for the test file. Got: {:?}",
        available_names
    );

    // 6. The plugin fixture should not generate false-positive "undeclared" diagnostics
    let undeclared = db.get_undeclared_fixtures(&canonical_test);
    let undeclared_names: Vec<&str> = undeclared.iter().map(|u| u.name.as_str()).collect();
    assert!(
        !undeclared_names.contains(&"direct_plugin_fixture"),
        "direct_plugin_fixture should NOT be reported as undeclared. Undeclared: {:?}",
        undeclared_names
    );
    assert!(
        !undeclared_names.contains(&"transitive_plugin_fixture"),
        "transitive_plugin_fixture should NOT be reported as undeclared. Undeclared: {:?}",
        undeclared_names
    );

    // 7. find_fixture_definition (go-to-definition) should work
    //    Line 1 (0-based), "direct_plugin_fixture" starts at char 21
    let goto = db.find_fixture_definition(&canonical_test, 1, 21);
    assert!(
        goto.is_some(),
        "find_fixture_definition should resolve direct_plugin_fixture from the test file"
    );
    let goto_def = goto.unwrap();
    assert_eq!(goto_def.name, "direct_plugin_fixture");
}

// ============================================================================
// Completion Enhancement Tests: Scope-aware filtering, Proximity sorting,
// Richer detail strings (Features #6, #8, #9)
// ============================================================================

#[test]
#[timeout(30000)]
fn test_completion_context_fixture_scope_default() {
    use pytest_language_server::CompletionContext;
    use pytest_language_server::FixtureScope;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture():
    return 42
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Cursor inside my_fixture's parentheses (line 4, 0-indexed)
    let ctx = db.get_completion_context(&path, 4, 15);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            fixture_scope,
            ..
        } => {
            assert_eq!(function_name, "my_fixture");
            assert!(is_fixture);
            assert_eq!(fixture_scope, Some(FixtureScope::Function));
        }
        _ => panic!("Expected FunctionSignature context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_fixture_scope_session() {
    use pytest_language_server::CompletionContext;
    use pytest_language_server::FixtureScope;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(scope="session")
def my_session_fixture():
    return 42
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Cursor inside the fixture's parentheses (line 4, 0-indexed)
    let ctx = db.get_completion_context(&path, 4, 22);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            fixture_scope,
            ..
        } => {
            assert_eq!(function_name, "my_session_fixture");
            assert!(is_fixture);
            assert_eq!(fixture_scope, Some(FixtureScope::Session));
        }
        _ => panic!("Expected FunctionSignature context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_fixture_scope_module() {
    use pytest_language_server::CompletionContext;
    use pytest_language_server::FixtureScope;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(scope="module")
def my_module_fixture():
    return 42
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 4, 22);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { fixture_scope, .. } => {
            assert_eq!(fixture_scope, Some(FixtureScope::Module));
        }
        _ => panic!("Expected FunctionSignature context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_test_function_no_scope() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def test_something():
    pass
"#;

    let path = PathBuf::from("/tmp/test/test_example.py");
    db.analyze_file(path.clone(), content);

    // Cursor inside test_something's parentheses (line 3, 0-indexed)
    let ctx = db.get_completion_context(&path, 3, 18);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            fixture_scope,
            ..
        } => {
            assert_eq!(function_name, "test_something");
            assert!(!is_fixture);
            assert_eq!(fixture_scope, None);
        }
        _ => panic!("Expected FunctionSignature context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_fixture_body_has_scope() {
    use pytest_language_server::CompletionContext;
    use pytest_language_server::FixtureScope;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture(scope="session")
def my_session_fixture():
    x = 1
    return x
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Cursor inside the function body (line 5, 0-indexed, on "x = 1")
    let ctx = db.get_completion_context(&path, 5, 4);

    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody {
            function_name,
            is_fixture,
            fixture_scope,
            ..
        } => {
            assert_eq!(function_name, "my_session_fixture");
            assert!(is_fixture);
            assert_eq!(fixture_scope, Some(FixtureScope::Session));
        }
        _ => panic!("Expected FunctionBody context"),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_scope_filtering_session_fixture() {
    use pytest_language_server::FixtureScope;
    let db = FixtureDatabase::new();

    // Set up a conftest with fixtures of various scopes
    let conftest_content = r#"
import pytest

@pytest.fixture(scope="function")
def func_fixture():
    return "func"

@pytest.fixture(scope="class")
def class_fixture():
    return "class"

@pytest.fixture(scope="module")
def module_fixture():
    return "module"

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

    let test_content = r#"
import pytest

@pytest.fixture(scope="session")
def my_session_fixture():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    let test_path = PathBuf::from("/tmp/test/test_scope.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    // Get completion context for the session fixture
    let ctx = db.get_completion_context(&test_path, 4, 22);
    assert!(ctx.is_some());

    let fixture_scope = match ctx.unwrap() {
        pytest_language_server::CompletionContext::FunctionSignature { fixture_scope, .. } => {
            fixture_scope
        }
        _ => panic!("Expected FunctionSignature"),
    };

    assert_eq!(fixture_scope, Some(FixtureScope::Session));

    // Simulate what the completion provider does: get available fixtures and
    // filter out those with incompatible (narrower) scope. A session-scoped
    // fixture can only depend on other session-scoped fixtures.
    let available = db.get_available_fixtures(&test_path);
    let filtered: Vec<_> = available
        .into_iter()
        .filter(|f| f.scope >= FixtureScope::Session)
        .collect();

    let filtered_names: Vec<&str> = filtered.iter().map(|f| f.name.as_str()).collect();

    // Only session_fixture (and my_session_fixture itself) should survive
    assert!(
        filtered_names.contains(&"session_fixture"),
        "session_fixture should be included, got: {:?}",
        filtered_names
    );
    assert!(
        !filtered_names.contains(&"func_fixture"),
        "func_fixture should be excluded"
    );
    assert!(
        !filtered_names.contains(&"class_fixture"),
        "class_fixture should be excluded"
    );
    assert!(
        !filtered_names.contains(&"module_fixture"),
        "module_fixture should be excluded"
    );
}

#[test]
#[timeout(30000)]
fn test_completion_scope_filtering_module_fixture() {
    use pytest_language_server::FixtureScope;
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture(scope="function")
def func_fixture():
    return "func"

@pytest.fixture(scope="class")
def class_fixture():
    return "class"

@pytest.fixture(scope="module")
def module_fixture():
    return "module"

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

    let test_content = r#"
import pytest

@pytest.fixture(scope="module")
def my_module_fixture():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    let test_path = PathBuf::from("/tmp/test/test_scope.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let ctx = db.get_completion_context(&test_path, 4, 22);
    assert!(ctx.is_some());

    let fixture_scope = match ctx.unwrap() {
        pytest_language_server::CompletionContext::FunctionSignature { fixture_scope, .. } => {
            fixture_scope
        }
        _ => panic!("Expected FunctionSignature"),
    };

    assert_eq!(fixture_scope, Some(FixtureScope::Module));

    // Simulate scope filtering: module-scoped fixture should exclude function
    // and class, but allow module, package, and session.
    let available = db.get_available_fixtures(&test_path);
    let filtered: Vec<_> = available
        .into_iter()
        .filter(|f| f.scope >= FixtureScope::Module)
        .collect();

    let filtered_names: Vec<&str> = filtered.iter().map(|f| f.name.as_str()).collect();

    assert!(
        filtered_names.contains(&"module_fixture"),
        "module_fixture should be included, got: {:?}",
        filtered_names
    );
    assert!(
        filtered_names.contains(&"session_fixture"),
        "session_fixture should be included, got: {:?}",
        filtered_names
    );
    assert!(
        !filtered_names.contains(&"func_fixture"),
        "func_fixture should be excluded"
    );
    assert!(
        !filtered_names.contains(&"class_fixture"),
        "class_fixture should be excluded"
    );
}

#[test]
#[timeout(30000)]
fn test_completion_scope_filtering_function_fixture_allows_all() {
    use pytest_language_server::FixtureScope;
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture(scope="function")
def func_fixture():
    return "func"

@pytest.fixture(scope="class")
def class_fixture():
    return "class"

@pytest.fixture(scope="module")
def module_fixture():
    return "module"

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

    let test_content = r#"
import pytest

@pytest.fixture
def my_func_fixture():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    let test_path = PathBuf::from("/tmp/test/test_scope.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let ctx = db.get_completion_context(&test_path, 4, 20);
    assert!(ctx.is_some());

    let fixture_scope = match ctx.unwrap() {
        pytest_language_server::CompletionContext::FunctionSignature { fixture_scope, .. } => {
            fixture_scope
        }
        _ => panic!("Expected FunctionSignature"),
    };

    // Function scope (narrowest) - nothing should be excluded
    assert_eq!(fixture_scope, Some(FixtureScope::Function));

    // Simulate scope filtering: function scope allows all scopes (>= Function)
    let available = db.get_available_fixtures(&test_path);
    let filtered: Vec<_> = available
        .into_iter()
        .filter(|f| f.scope >= FixtureScope::Function)
        .collect();

    // All fixtures should survive — nothing is narrower than function scope
    let filtered_names: Vec<&str> = filtered.iter().map(|f| f.name.as_str()).collect();
    assert!(
        filtered_names.contains(&"func_fixture"),
        "func_fixture should be included"
    );
    assert!(
        filtered_names.contains(&"class_fixture"),
        "class_fixture should be included"
    );
    assert!(
        filtered_names.contains(&"module_fixture"),
        "module_fixture should be included"
    );
    assert!(
        filtered_names.contains(&"session_fixture"),
        "session_fixture should be included"
    );
}

#[test]
#[timeout(30000)]
fn test_completion_scope_filtering_test_function_allows_all() {
    let db = FixtureDatabase::new();

    let conftest_content = r#"
import pytest

@pytest.fixture(scope="function")
def func_fixture():
    return "func"

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

    let test_content = r#"
import pytest

def test_something():
    pass
"#;

    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    let test_path = PathBuf::from("/tmp/test/test_scope.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let ctx = db.get_completion_context(&test_path, 3, 18);
    assert!(ctx.is_some());

    let fixture_scope = match ctx.unwrap() {
        pytest_language_server::CompletionContext::FunctionSignature { fixture_scope, .. } => {
            fixture_scope
        }
        _ => panic!("Expected FunctionSignature"),
    };

    // Test functions have None scope — all fixtures should be allowed
    assert_eq!(fixture_scope, None);

    let available = db.get_available_fixtures(&test_path);
    // With None scope, no filtering should occur — all fixtures visible
    let names: Vec<&str> = available.iter().map(|f| f.name.as_str()).collect();
    assert!(
        names.contains(&"func_fixture"),
        "func_fixture should be visible to test functions"
    );
    assert!(
        names.contains(&"session_fixture"),
        "session_fixture should be visible to test functions"
    );
}

#[test]
#[timeout(30000)]
fn test_completion_fixture_proximity_same_file_first() {
    let db = FixtureDatabase::new();

    // Same-file fixture
    let test_content = r#"
import pytest

@pytest.fixture
def local_fixture():
    return "local"

def test_something():
    pass
"#;

    // Conftest fixture
    let conftest_content = r#"
import pytest

@pytest.fixture
def conftest_fixture():
    return "conftest"
"#;

    let conftest_path = PathBuf::from("/tmp/test/conftest.py");
    let test_path = PathBuf::from("/tmp/test/test_proximity.py");

    db.analyze_file(conftest_path.clone(), conftest_content);
    db.analyze_file(test_path.clone(), test_content);

    let available = db.get_available_fixtures(&test_path);

    // Verify we have both fixtures
    let local = available.iter().find(|f| f.name == "local_fixture");
    let conftest = available.iter().find(|f| f.name == "conftest_fixture");

    assert!(local.is_some(), "Should find local fixture");
    assert!(conftest.is_some(), "Should find conftest fixture");

    let local = local.unwrap();
    let conftest = conftest.unwrap();

    // Same-file fixture should have file_path == test_path
    assert_eq!(local.file_path, test_path);
    // Conftest fixture should have file_path == conftest_path
    assert_eq!(conftest.file_path, conftest_path);
}

#[test]
#[timeout(30000)]
fn test_completion_third_party_fixture_has_flag() {
    let db = FixtureDatabase::new();

    // Simulate a third-party fixture by manually inserting one
    let third_party_path =
        PathBuf::from("/tmp/venv/lib/python3.11/site-packages/pytest_django/fixtures.py");

    db.definitions.insert(
        "tp_fixture".to_string(),
        vec![pytest_language_server::FixtureDefinition {
            name: "tp_fixture".to_string(),
            file_path: third_party_path.clone(),
            line: 10,
            end_line: 15,
            start_char: 4,
            end_char: 14,
            docstring: Some("A third-party fixture".to_string()),
            is_third_party: true,
            scope: pytest_language_server::FixtureScope::Session,
            ..Default::default()
        }],
    );

    let test_content = r#"
import pytest

@pytest.fixture
def local_fixture():
    return "local"

def test_something():
    pass
"#;

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

    let available = db.get_available_fixtures(&test_path);

    let tp = available.iter().find(|f| f.name == "tp_fixture");
    assert!(tp.is_some(), "Should find third-party fixture");

    let tp = tp.unwrap();
    assert!(tp.is_third_party, "Should be flagged as third-party");
    assert_eq!(tp.scope, pytest_language_server::FixtureScope::Session);

    let local = available.iter().find(|f| f.name == "local_fixture");
    assert!(local.is_some(), "Should find local fixture");
    assert!(
        !local.unwrap().is_third_party,
        "Local should not be third-party"
    );
}

// ============================================================================
// Feature 5: AST-based signature end detection tests
// ============================================================================

#[test]
#[timeout(30000)]
fn test_completion_context_multiline_signature() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def test_foo(
    a,
    b,
):
    pass
"#;

    let path = PathBuf::from("/tmp/test/test_multiline.py");
    db.analyze_file(path.clone(), content);

    // Line 3 (0-indexed): "def test_foo(" — should be signature
    let ctx = db.get_completion_context(&path, 3, 13);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }

    // Line 4 (0-indexed): "    a," — should still be signature
    let ctx = db.get_completion_context(&path, 4, 5);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!("Expected FunctionSignature on param line, got {:?}", other),
    }

    // Line 5 (0-indexed): "    b," — should still be signature
    let ctx = db.get_completion_context(&path, 5, 5);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!(
            "Expected FunctionSignature on second param line, got {:?}",
            other
        ),
    }

    // Line 6 (0-indexed): "):" — should still be signature
    let ctx = db.get_completion_context(&path, 6, 1);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!(
            "Expected FunctionSignature on closing line, got {:?}",
            other
        ),
    }

    // Line 7 (0-indexed): "    pass" — should be body
    let ctx = db.get_completion_context(&path, 7, 4);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!("Expected FunctionBody, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_return_type_annotation() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture(a) -> int:
    return 42
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Line 4 (0-indexed): "def my_fixture(a) -> int:" — should be signature
    let ctx = db.get_completion_context(&path, 4, 15);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "my_fixture");
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }

    // Line 5 (0-indexed): "    return 42" — should be body
    let ctx = db.get_completion_context(&path, 5, 4);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody { function_name, .. } => {
            assert_eq!(function_name, "my_fixture");
        }
        other => panic!("Expected FunctionBody, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_multiline_signature_with_return_type() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture(
    a,
    b,
) -> int:
    return 42
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Line 7 (0-indexed): ") -> int:" — should be signature
    let ctx = db.get_completion_context(&path, 7, 1);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "my_fixture");
        }
        other => panic!(
            "Expected FunctionSignature on return type line, got {:?}",
            other
        ),
    }

    // Line 8 (0-indexed): "    return 42" — should be body
    let ctx = db.get_completion_context(&path, 8, 4);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody { function_name, .. } => {
            assert_eq!(function_name, "my_fixture");
        }
        other => panic!("Expected FunctionBody, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_single_line_def() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def test_foo(a):
    pass
"#;

    let path = PathBuf::from("/tmp/test/test_single.py");
    db.analyze_file(path.clone(), content);

    // Line 3 (0-indexed): "def test_foo(a):" — signature
    let ctx = db.get_completion_context(&path, 3, 13);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }

    // Line 4 (0-indexed): "    pass" — body
    let ctx = db.get_completion_context(&path, 4, 4);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!("Expected FunctionBody, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_fixture_with_many_params_multiline() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
def my_fixture(
    param_a,
    param_b,
    param_c,
    param_d,
    param_e,
):
    return 42
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // All lines 4-10 should be signature context
    for line in 4..=10 {
        let ctx = db.get_completion_context(&path, line, 4);
        assert!(ctx.is_some(), "Expected context on line {}", line);
        match ctx.unwrap() {
            CompletionContext::FunctionSignature { function_name, .. } => {
                assert_eq!(function_name, "my_fixture", "Wrong name on line {}", line);
            }
            other => panic!(
                "Expected FunctionSignature on line {}, got {:?}",
                line, other
            ),
        }
    }

    // Line 11 (0-indexed): "    return 42" — body
    let ctx = db.get_completion_context(&path, 11, 4);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody { function_name, .. } => {
            assert_eq!(function_name, "my_fixture");
        }
        other => panic!("Expected FunctionBody, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_async_fixture() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

@pytest.fixture
async def my_async_fixture(
    a,
) -> int:
    return 42
"#;

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Line 6 (0-indexed): ") -> int:" — signature
    let ctx = db.get_completion_context(&path, 6, 1);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "my_async_fixture");
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }

    // Line 7 (0-indexed): "    return 42" — body
    let ctx = db.get_completion_context(&path, 7, 4);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody { function_name, .. } => {
            assert_eq!(function_name, "my_async_fixture");
        }
        other => panic!("Expected FunctionBody, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_empty_line_between_signature_and_body() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"
import pytest

def test_foo(a):

    pass
"#;

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

    // Line 4 (0-indexed): empty line — should be body (after the ":")
    let ctx = db.get_completion_context(&path, 4, 0);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!("Expected FunctionBody on blank line, got {:?}", other),
    }
}

// ============================================================================
// Feature 1: Text-based fallback for incomplete signatures
// ============================================================================

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_ast_test_function_open_paren() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Syntactically invalid — no closing paren
    let content = "def test_foo(";

    let path = PathBuf::from("/tmp/test/test_incomplete.py");
    db.analyze_file(path.clone(), content);

    // Line 0 (0-indexed), cursor after the opening paren
    let ctx = db.get_completion_context(&path, 0, 13);
    assert!(ctx.is_some(), "Should get context from text fallback");
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            declared_params,
            ..
        } => {
            assert_eq!(function_name, "test_foo");
            assert!(!is_fixture);
            assert!(declared_params.is_empty());
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_ast_fixture_function_open_paren() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = "@pytest.fixture\ndef bar(";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Line 1 (0-indexed), cursor after the opening paren
    let ctx = db.get_completion_context(&path, 1, 8);
    assert!(
        ctx.is_some(),
        "Should get context from text fallback for fixture"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            ..
        } => {
            assert_eq!(function_name, "bar");
            assert!(is_fixture);
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_ast_fixture_with_scope() {
    use pytest_language_server::CompletionContext;
    use pytest_language_server::FixtureScope;
    let db = FixtureDatabase::new();

    let content = "@pytest.fixture(scope=\"session\")\ndef bar(";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 1, 8);
    assert!(
        ctx.is_some(),
        "Should get context with scope from text fallback"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            fixture_scope,
            ..
        } => {
            assert_eq!(function_name, "bar");
            assert!(is_fixture);
            assert_eq!(fixture_scope, Some(FixtureScope::Session));
        }
        other => panic!(
            "Expected FunctionSignature with session scope, got {:?}",
            other
        ),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_ast_with_existing_params() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = "def test_foo(existing_fixture, ";

    let path = PathBuf::from("/tmp/test/test_params.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 0, 31);
    assert!(ctx.is_some(), "Should get context with existing params");
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            declared_params, ..
        } => {
            assert!(
                declared_params.contains(&"existing_fixture".to_string()),
                "Should contain existing_fixture in declared_params, got {:?}",
                declared_params
            );
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_ast_multiline_params() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = "def test_foo(\n    a,\n    b,\n";

    let path = PathBuf::from("/tmp/test/test_multiline.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 3 (0-indexed), after "    b,"
    let ctx = db.get_completion_context(&path, 3, 0);
    assert!(
        ctx.is_some(),
        "Should get context for multiline incomplete sig"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            declared_params, ..
        } => {
            assert!(
                declared_params.contains(&"a".to_string()),
                "Should contain 'a', got {:?}",
                declared_params
            );
            assert!(
                declared_params.contains(&"b".to_string()),
                "Should contain 'b', got {:?}",
                declared_params
            );
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_ast_async_test() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = "async def test_foo(";

    let path = PathBuf::from("/tmp/test/test_async.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 0, 19);
    assert!(ctx.is_some(), "Should get context for async test");
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!("Expected FunctionSignature for async test, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_ast_regular_function_no_completions() {
    let db = FixtureDatabase::new();

    let content = "def regular_func(";

    let path = PathBuf::from("/tmp/test/test_regular.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 0, 17);
    assert!(
        ctx.is_none(),
        "Regular function should not get completion context"
    );
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_ast_with_prior_complete_code() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"import pytest

@pytest.fixture
def existing_fixture():
    return 42

def test_new("#;

    let path = PathBuf::from("/tmp/test/test_prior.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 6 (0-indexed): "def test_new("
    let ctx = db.get_completion_context(&path, 6, 14);
    assert!(
        ctx.is_some(),
        "Should get context from text fallback with prior valid code"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "test_new");
        }
        other => panic!("Expected FunctionSignature for test_new, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_fixture_bar_exact_user_scenario() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Exact user scenario: typing a fixture with open paren
    let content = "@pytest.fixture\ndef bar(";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 1, 8);
    assert!(
        ctx.is_some(),
        "Exact user scenario should produce completions"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            ..
        } => {
            assert_eq!(function_name, "bar");
            assert!(is_fixture);
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_ast_fixture_with_existing_param() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Example 2 from the plan: fixture with existing param and trailing comma
    let content = "@pytest.fixture\ndef bar(other_fixture, ";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 1, 23);
    assert!(ctx.is_some(), "Should get context with existing param");
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            declared_params,
            ..
        } => {
            assert_eq!(function_name, "bar");
            assert!(is_fixture);
            assert!(
                declared_params.contains(&"other_fixture".to_string()),
                "Should contain other_fixture, got {:?}",
                declared_params
            );
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

// ============================================================================
// Feature 1: Text-based fallback for usefixtures/pytestmark
// ============================================================================

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_usefixtures_decorator() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Example 4: usefixtures decorator with open paren
    let content = "@pytest.mark.usefixtures(";

    let path = PathBuf::from("/tmp/test/test_usefixtures.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 0, 25);
    assert!(
        ctx.is_some(),
        "Should get usefixtures context from text fallback"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_usefixtures_with_function_below() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Example 5: usefixtures with function below
    let content = "@pytest.mark.usefixtures(\ndef test_something():";

    let path = PathBuf::from("/tmp/test/test_usefixtures.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 0, inside the usefixtures call
    let ctx = db.get_completion_context(&path, 0, 25);
    assert!(
        ctx.is_some(),
        "Should get usefixtures context with function below"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_pytestmark_usefixtures() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Example 6: pytestmark list with usefixtures open paren
    let content = "pytestmark = [\n    pytest.mark.usefixtures(";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 1, inside the usefixtures call
    let ctx = db.get_completion_context(&path, 1, 28);
    assert!(
        ctx.is_some(),
        "Should get usefixtures context in pytestmark list"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_pytestmark_usefixtures_unclosed_bracket() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Example 7: pytestmark list with usefixtures, unclosed bracket
    let content = "pytestmark = [\n    pytest.mark.usefixtures(\n]";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 1, inside the usefixtures call
    let ctx = db.get_completion_context(&path, 1, 28);
    assert!(
        ctx.is_some(),
        "Should get usefixtures context with unclosed bracket"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_pytestmark_usefixtures_closed_paren() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Example 8: pytestmark with usefixtures, cursor inside closed ()
    let content = "pytestmark = [\n    pytest.mark.usefixtures()\n]";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 1, inside the usefixtures() — between ( and )
    let ctx = db.get_completion_context(&path, 1, 28);
    assert!(
        ctx.is_some(),
        "Should get usefixtures context when cursor inside ()"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

// ============================================================================
// Feature 2: Trailing-newline cursor positioning
// ============================================================================

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_fixture_trailing_newline() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Content ends with \n — cursor on the phantom empty line
    let content = "@pytest.fixture\ndef bar(\n";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 2 (0-indexed) — the phantom empty line after trailing \n
    let ctx = db.get_completion_context(&path, 2, 0);
    assert!(
        ctx.is_some(),
        "Should get context on trailing newline phantom line"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            ..
        } => {
            assert_eq!(function_name, "bar");
            assert!(is_fixture);
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_fixture_newline_with_indent() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Content ends with newline + indent
    let content = "@pytest.fixture\ndef bar(\n    ";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 2 (0-indexed), indented
    let ctx = db.get_completion_context(&path, 2, 4);
    assert!(
        ctx.is_some(),
        "Should get context on indented line after newline"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            ..
        } => {
            assert_eq!(function_name, "bar");
            assert!(is_fixture);
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

// ============================================================================
// Feature 3: Handle signatures without an opening parenthesis
// ============================================================================

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_fixture_no_paren() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = "@pytest.fixture\ndef bar";

    let path = PathBuf::from("/tmp/test/conftest.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 1, 7);
    assert!(ctx.is_some(), "Fixture without paren should get context");
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            ..
        } => {
            assert_eq!(function_name, "bar");
            assert!(is_fixture);
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_test_no_paren() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = "def test_foo";

    let path = PathBuf::from("/tmp/test/test_noparen.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 0, 12);
    assert!(
        ctx.is_some(),
        "Test function without paren should get context"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "test_foo");
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

#[test]
#[timeout(30000)]
fn test_completion_context_incomplete_regular_func_no_paren_no_completions() {
    let db = FixtureDatabase::new();

    let content = "def regular";

    let path = PathBuf::from("/tmp/test/test_regular.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 0, 11);
    assert!(
        ctx.is_none(),
        "Regular function without paren should not get context"
    );
}

/// Test completion inside closed parens with colon but no body: def test_bla():
#[test]
fn test_completion_context_incomplete_closed_parens_with_colon_no_body() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // User scenario: "def test_bla():" with no body — parser fails
    let content = "def test_bla():";

    let path = PathBuf::from("/tmp/test/test_closed_parens.py");
    db.analyze_file(path.clone(), content);

    // Cursor inside the parens at position 13 (between '(' and ')')
    let ctx = db.get_completion_context(&path, 0, 13);
    assert!(
        ctx.is_some(),
        "Should get completion context inside closed parens with no body"
    );
    if let Some(CompletionContext::FunctionSignature {
        function_name,
        is_fixture,
        ..
    }) = ctx
    {
        assert_eq!(function_name, "test_bla");
        assert!(!is_fixture);
    } else {
        panic!("Expected FunctionSignature context");
    }
}

/// Test completion inside closed parens without colon or body: def test_bla()
#[test]
fn test_completion_context_incomplete_closed_parens_no_colon_no_body() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // User scenario: "def test_bla()" with no colon or body — parser fails
    let content = "def test_bla()";

    let path = PathBuf::from("/tmp/test/test_no_colon.py");
    db.analyze_file(path.clone(), content);

    // Cursor inside the parens at position 13 (between '(' and ')')
    let ctx = db.get_completion_context(&path, 0, 13);
    assert!(
        ctx.is_some(),
        "Should get completion context inside closed parens without colon"
    );
    if let Some(CompletionContext::FunctionSignature {
        function_name,
        is_fixture,
        ..
    }) = ctx
    {
        assert_eq!(function_name, "test_bla");
        assert!(!is_fixture);
    } else {
        panic!("Expected FunctionSignature context");
    }
}

/// Test completion with closed parens, colon, trailing newline but no body
#[test]
fn test_completion_context_incomplete_closed_parens_colon_trailing_newline() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = "def test_bla():\n";

    let path = PathBuf::from("/tmp/test/test_trailing_nl.py");
    db.analyze_file(path.clone(), content);

    // Cursor inside the parens on line 0, position 13
    let ctx = db.get_completion_context(&path, 0, 13);
    assert!(
        ctx.is_some(),
        "Should get completion context inside parens even with trailing newline"
    );
    if let Some(CompletionContext::FunctionSignature { function_name, .. }) = ctx {
        assert_eq!(function_name, "test_bla");
    } else {
        panic!("Expected FunctionSignature context");
    }
}

/// Test completion for fixture with closed parens but no body
#[test]
fn test_completion_context_incomplete_fixture_closed_parens_no_body() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = "@pytest.fixture\ndef my_fixture():";

    let path = PathBuf::from("/tmp/test/conftest_closed.py");
    db.analyze_file(path.clone(), content);

    // Cursor inside parens on line 1, position 15 (between '(' and ')')
    let ctx = db.get_completion_context(&path, 1, 15);
    assert!(
        ctx.is_some(),
        "Fixture with closed parens but no body should get context"
    );
    if let Some(CompletionContext::FunctionSignature {
        function_name,
        is_fixture,
        ..
    }) = ctx
    {
        assert_eq!(function_name, "my_fixture");
        assert!(is_fixture);
    } else {
        panic!("Expected FunctionSignature context");
    }
}

/// Test completion with closed parens and existing params but no body
#[test]
fn test_completion_context_incomplete_closed_parens_with_existing_params_no_body() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // User is adding a second fixture param, function has no body yet
    let content = "def test_bla(my_fixture, ):";

    let path = PathBuf::from("/tmp/test/test_with_params.py");
    db.analyze_file(path.clone(), content);

    // Cursor after the comma+space, position 25 (before ')')
    let ctx = db.get_completion_context(&path, 0, 25);
    assert!(
        ctx.is_some(),
        "Should get context inside closed parens with existing params"
    );
    if let Some(CompletionContext::FunctionSignature {
        function_name,
        declared_params,
        ..
    }) = ctx
    {
        assert_eq!(function_name, "test_bla");
        assert!(
            declared_params.contains(&"my_fixture".to_string()),
            "Should list existing param 'my_fixture', got: {:?}",
            declared_params
        );
    } else {
        panic!("Expected FunctionSignature context");
    }
}

/// Test that complete valid function with body does NOT trigger from text fallback
/// (This verifies we don't regress by always returning completions)
#[test]
fn test_completion_context_complete_function_with_body_no_text_fallback() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // This is valid Python that parses successfully — AST path handles it
    let content = "def test_bla():\n    pass\n";

    let path = PathBuf::from("/tmp/test/test_complete.py");
    db.analyze_file(path.clone(), content);

    // Cursor inside parens — AST path should handle this (not text fallback)
    let ctx = db.get_completion_context(&path, 0, 13);
    assert!(
        ctx.is_some(),
        "Complete function should still provide completions via AST path"
    );
    if let Some(CompletionContext::FunctionSignature { function_name, .. }) = ctx {
        assert_eq!(function_name, "test_bla");
    } else {
        panic!("Expected FunctionSignature context");
    }
}

/// Test with valid code above and incomplete function at end
#[test]
fn test_completion_context_valid_code_then_incomplete_closed_parens() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    let content = r#"import pytest

@pytest.fixture
def existing():
    return 1

def test_new():"#;

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

    // Cursor inside parens of the incomplete test_new on line 6
    let ctx = db.get_completion_context(&path, 6, 13);
    assert!(
        ctx.is_some(),
        "Should get context for incomplete function after valid code"
    );
    if let Some(CompletionContext::FunctionSignature { function_name, .. }) = ctx {
        assert_eq!(function_name, "test_new");
    } else {
        panic!("Expected FunctionSignature context");
    }
}

// ============================================================================
// Coverage gap: usefixtures text fallback depth==0 branch
// ============================================================================

/// Test that balanced usefixtures() with content does NOT offer completions
/// when the text fallback handles it (depth==0, non-empty parens).
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_usefixtures_balanced_with_content() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Invalid Python (no def body) so AST fails, but usefixtures("a") is balanced
    let content = "@pytest.mark.usefixtures(\"a\")\ndef test_foo(";

    let path = PathBuf::from("/tmp/test/test_balanced_usefixtures.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 0 (the usefixtures line) — parens are balanced with content
    // The text fallback should NOT offer usefixtures completions here
    let ctx = db.get_completion_context(&path, 0, 28);
    // Should fall through to the function signature context for the def on line 1,
    // or return None for line 0 since it's not inside usefixtures
    // Any other result (None or FunctionSignature) is acceptable
    if let Some(CompletionContext::UsefixturesDecorator) = ctx {
        panic!("Should NOT return UsefixturesDecorator for balanced usefixtures with content");
    }
}

/// Test that balanced empty usefixtures() DOES offer completions via text fallback.
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_usefixtures_empty_parens() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Invalid Python so AST fails, usefixtures() has empty parens
    let content = "@pytest.mark.usefixtures()\ndef test_foo(";

    let path = PathBuf::from("/tmp/test/test_empty_usefixtures.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 0 inside the empty usefixtures() — should offer completions
    let ctx = db.get_completion_context(&path, 0, 25);
    assert!(
        ctx.is_some(),
        "Empty usefixtures() should offer completions via text fallback"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

/// Test usefixtures with no closing paren on cursor line (depth==0 but no ')' found).
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_usefixtures_no_close_paren_on_line() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // usefixtures( on line 0, closing ) on line 1 but cursor stays on line 0
    // Since the opening line has depth 1 (only '(' seen), this hits depth > 0.
    // To hit the "no closing paren on this line" branch we need depth==0 on cursor line
    // with no ')' on that line. This requires a multiline scenario where parens balance
    // across lines but cursor is on the opening line.
    //
    // Actually, to hit the unclosed-on-line branch: depth==0, i==cursor_idx,
    // and no ')' on the line. That means we need '(' and ')' to cancel out
    // with no ')' at rfind... which can't happen if '(' was found.
    // The realistic case is: usefixtures( with content on the next line closing it,
    // but we only count the *opening* line. If the opening line has `usefixtures(` only,
    // depth is 1, hitting depth > 0. So this branch is only reachable if something
    // like `usefixtures(() )` where parens balance on the same line but rfind(')') finds
    // the outer one. Let's test a different realistic case instead.
    //
    // We can test a scenario where usefixtures has nested parens that balance:
    // e.g. `usefixtures(func())` — depth goes +1 for usefixtures(, +1 for func(, -1 for ),
    // -1 for ) = 0. And rfind(')') finds the last ')'. Not empty, so returns None.
    let content = "@pytest.mark.usefixtures(func())\ndef test_foo(";

    let path = PathBuf::from("/tmp/test/test_nested_usefixtures.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 0, 30);
    // Balanced with content — should NOT offer usefixtures completions
    if let Some(CompletionContext::UsefixturesDecorator) = ctx {
        panic!("Should NOT return UsefixturesDecorator for balanced usefixtures(func())");
    }
}

// ============================================================================
// Coverage gap: multi-line usefixtures paren counting in text fallback
// ============================================================================

/// Test usefixtures spanning multiple lines where cursor is on a subsequent line.
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_usefixtures_multiline_paren_counting() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // usefixtures( on line 0, cursor on line 1 (still inside unclosed call)
    // AST fails because there's no closing paren or valid def
    let content = "@pytest.mark.usefixtures(\n    ";

    let path = PathBuf::from("/tmp/test/test_multiline_usefixtures.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 1 — should detect we're inside unclosed usefixtures(
    let ctx = db.get_completion_context(&path, 1, 4);
    assert!(
        ctx.is_some(),
        "Should get usefixtures context on continuation line"
    );
    match ctx.unwrap() {
        CompletionContext::UsefixturesDecorator => {}
        other => panic!("Expected UsefixturesDecorator, got {:?}", other),
    }
}

/// Test usefixtures spanning multiple lines with closing paren on a later line.
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_usefixtures_multiline_closed() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // usefixtures( on line 0, ) on line 1, cursor on line 1 after closing
    let content = "@pytest.mark.usefixtures(\n)\ndef test_foo(";

    let path = PathBuf::from("/tmp/test/test_multiline_usefixtures_closed.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 1 which has ')' — parens are balanced (depth 0)
    // The usefixtures( is found on line 0 (i=0), cursor_idx=1, so i != cursor_idx.
    // depth goes: line0 has '(' → depth=1. line1 has ')' → depth=0. depth==0, not > 0.
    // i (0) != cursor_idx (1), so the depth==0 same-line branch is skipped.
    // Falls through to None for usefixtures, then tries function context.
    let ctx = db.get_completion_context(&path, 1, 1);
    // None or FunctionSignature is fine
    if let Some(CompletionContext::UsefixturesDecorator) = ctx {
        panic!("Should NOT return UsefixturesDecorator after balanced multiline usefixtures");
    }
}

// ============================================================================
// Coverage gap: signature_closed branch in text fallback
// ============================================================================

/// Test that the text fallback rejects completion when cursor is on a line
/// after the closing paren (signature_closed = true).
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_signature_closed_cursor_after() {
    let db = FixtureDatabase::new();

    // Incomplete Python: def with closed parens + colon, then a line, but no body statement.
    // AST parse fails because there's no body. The text fallback should detect
    // that the signature is closed and the cursor is on a subsequent line.
    let content = "def test_foo():\n    \ndef broken";

    let path = PathBuf::from("/tmp/test/test_sig_closed.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 1 (0-indexed) — after the signature close on line 0
    // The paren depth goes: '(' → 1, ')' → 0 on line 0 (not cursor line),
    // so signature_closed = true, cursor_inside_parens = false → returns None.
    let ctx = db.get_completion_context(&path, 1, 4);
    assert!(
        ctx.is_none(),
        "Cursor after closed signature should not get completion context, got: {:?}",
        ctx
    );
}

/// Test that the text fallback still provides completions when signature spans
/// multiple lines and cursor is inside the parens on a later line.
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_multiline_cursor_inside_parens() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Incomplete: multiline def with open paren, cursor on continuation line
    let content = "def test_foo(\n    existing,\n    ";

    let path = PathBuf::from("/tmp/test/test_multiline_inside.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 2 (0-indexed) — inside the open parens
    let ctx = db.get_completion_context(&path, 2, 4);
    assert!(
        ctx.is_some(),
        "Cursor inside multiline open parens should get context"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            declared_params,
            ..
        } => {
            assert_eq!(function_name, "test_foo");
            assert!(
                declared_params.contains(&"existing".to_string()),
                "Should contain 'existing', got {:?}",
                declared_params
            );
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

// ============================================================================
// Coverage gap: has_fixture_decorator_above — non-fixture decorator skip
// ============================================================================

/// Test that non-fixture decorators are skipped when scanning upward
/// and a @pytest.fixture further above is still found.
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_non_fixture_decorator_above() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // A fixture decorator with another decorator stacked between it and the def
    let content = "@pytest.fixture\n@some_other_decorator\ndef bar(";

    let path = PathBuf::from("/tmp/test/conftest_stacked.py");
    db.analyze_file(path.clone(), content);

    // Cursor on line 2 after the opening paren
    let ctx = db.get_completion_context(&path, 2, 8);
    assert!(
        ctx.is_some(),
        "Should find fixture decorator above non-fixture decorator"
    );
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            function_name,
            is_fixture,
            ..
        } => {
            assert_eq!(function_name, "bar");
            assert!(
                is_fixture,
                "Should be detected as fixture despite intermediate decorator"
            );
        }
        other => panic!("Expected FunctionSignature, got {:?}", other),
    }
}

/// Test that a function with only non-fixture decorators is NOT detected as a fixture.
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_only_non_fixture_decorators() {
    let db = FixtureDatabase::new();

    // Only non-fixture decorators — should not be detected as fixture
    let content = "@some_decorator\n@another_decorator\ndef helper_func(";

    let path = PathBuf::from("/tmp/test/test_non_fixture_decs.py");
    db.analyze_file(path.clone(), content);

    // Not a test_ function and not a fixture — should return None
    let ctx = db.get_completion_context(&path, 2, 16);
    assert!(
        ctx.is_none(),
        "Non-test, non-fixture function with only non-fixture decorators should not get context"
    );
}

// ============================================================================
// Coverage gap: find_signature_end_line body-line fallback
// ============================================================================

/// Test find_signature_end_line when the trailing ':' is not found by scanning.
/// This happens when the function has no colon (e.g., the parser still produces
/// an AST for certain edge cases). We test indirectly through get_completion_context.
/// Since this is hard to trigger via the AST path (AST requires valid Python with ':'),
/// we verify the primary path (colon found) is robust for multiline signatures.
#[test]
#[timeout(30000)]
fn test_completion_context_multiline_signature_colon_on_separate_line() {
    use pytest_language_server::CompletionContext;
    let db = FixtureDatabase::new();

    // Valid Python with closing paren and colon on a separate line from the params.
    // This exercises the scan-forward-for-colon logic across multiple lines.
    let content = r#"
import pytest

@pytest.fixture
def my_fixture(
    param_a,
    param_b
):
    return 42
"#;

    let path = PathBuf::from("/tmp/test/conftest_colon_separate.py");
    db.analyze_file(path.clone(), content);

    // Line 7 (0-indexed): "):" — should be signature
    let ctx = db.get_completion_context(&path, 7, 1);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionSignature { function_name, .. } => {
            assert_eq!(function_name, "my_fixture");
        }
        other => panic!("Expected FunctionSignature on '):', got {:?}", other),
    }

    // Line 8 (0-indexed): "    return 42" — should be body
    let ctx = db.get_completion_context(&path, 8, 4);
    assert!(ctx.is_some());
    match ctx.unwrap() {
        CompletionContext::FunctionBody { function_name, .. } => {
            assert_eq!(function_name, "my_fixture");
        }
        other => panic!("Expected FunctionBody, got {:?}", other),
    }
}

/// Test extract_fixture_scope_from_text with single-quoted scope.
#[test]
#[timeout(30000)]
fn test_completion_context_text_fallback_fixture_scope_single_quotes() {
    use pytest_language_server::CompletionContext;
    use pytest_language_server::FixtureScope;
    let db = FixtureDatabase::new();

    let content = "@pytest.fixture(scope='module')\ndef bar(";

    let path = PathBuf::from("/tmp/test/conftest_single_quote.py");
    db.analyze_file(path.clone(), content);

    let ctx = db.get_completion_context(&path, 1, 8);
    assert!(ctx.is_some(), "Should get context with single-quoted scope");
    match ctx.unwrap() {
        CompletionContext::FunctionSignature {
            fixture_scope,
            is_fixture,
            ..
        } => {
            assert!(is_fixture);
            assert_eq!(fixture_scope, Some(FixtureScope::Module));
        }
        other => panic!(
            "Expected FunctionSignature with module scope, got {:?}",
            other
        ),
    }
}
