import pytest

from blends.stack.node_kinds import StackGraphNodeKind
from test.stack.test_helpers import (
    count_defs_refs,
    find_symbol_nodes,
    resolve_symbol,
    setup_stack_resolution,
)


@pytest.mark.blends_test_group("functional")
def test_self_attribute_resolves_to_class_attribute() -> None:
    view, record = setup_stack_resolution("case_self_attr.py")
    defs, refs = count_defs_refs(view, "value")
    result = resolve_symbol(view, record, "value")

    assert defs >= 1, f"Expected at least 1 def of 'value', got {defs}"
    assert refs >= 1, f"Expected at least 1 ref to 'value', got {refs}"
    assert len(result) >= 1, f"Expected self.value to resolve to class attr, got {len(result)}"


@pytest.mark.blends_test_group("functional")
def test_self_attr_does_not_resolve_to_inner_class_attribute() -> None:
    view, record = setup_stack_resolution("case_self_attr_nested_class.py")
    defs, refs = count_defs_refs(view, "value")
    result = resolve_symbol(view, record, "value")

    assert defs >= 2, (  # noqa: PLR2004
        f"Expected at least 2 defs of 'value' (outer + inner), got {defs}"
    )
    assert refs >= 1, f"Expected at least 1 ref to 'value', got {refs}"
    assert len(result) == 1, (
        f"self.value in outer method should resolve only to outer class attr, got {len(result)}"
    )

    symbol_id = view.symbols.symbol_to_id.get("value")
    assert symbol_id is not None
    def_nodes = find_symbol_nodes(view, symbol_id=symbol_id, is_definition=True)
    outer_value_def = next(
        idx for idx in def_nodes if view.node_kind[idx] == StackGraphNodeKind.POP_SYMBOL.value
    )
    assert result == [outer_value_def], (
        f"Expected self.value to resolve to Outer.value definition, "
        f"got {result}, expected [{outer_value_def}]"
    )


@pytest.mark.blends_test_group("functional")
def test_self_attr_resolves_with_typed_self_parameter() -> None:
    view, record = setup_stack_resolution("case_self_attr_typed_self.py")
    defs, refs = count_defs_refs(view, "value")
    result = resolve_symbol(view, record, "value")

    assert defs >= 1, f"Expected at least 1 def of 'value', got {defs}"
    assert refs >= 1, f"Expected at least 1 ref to 'value', got {refs}"
    assert len(result) >= 1, (
        f"Typed self parameter should not prevent self.value resolution, got {len(result)}"
    )


@pytest.mark.blends_test_group("functional")
def test_typed_param_member_access_resolves_to_class_attribute() -> None:
    view, record = setup_stack_resolution("case_typed_param_member_access.py")
    defs, refs = count_defs_refs(view, "value")

    assert defs >= 1, f"Expected at least 1 def of 'value', got {defs}"
    assert refs >= 2, f"Expected at least 2 refs to 'value', got {refs}"  # noqa: PLR2004

    results = [resolve_symbol(view, record, "value", ref_index=i) for i in range(refs)]

    assert all(len(r) >= 1 for r in results), (
        f"Every value ref should resolve to the class attribute, got {[len(r) for r in results]}"
    )
    unique_resolutions = {tuple(sorted(r)) for r in results}
    assert len(unique_resolutions) == 1, (
        f"All value refs must resolve to the same definition, got {unique_resolutions}"
    )
