import pytest

from blends.stack import (
    MultiFileResolutionRequest,
    resolve_definitions_multi_file,
)
from blends.stack.bridge_paths import _resolve_relative_stem
from blends.stack.forward_stitcher import ForwardStitcherConfig
from blends.stack.view import StackGraphView
from test.stack.test_helpers import (
    QUERY_MAX_PHASES,
    QUERY_MAX_WORK,
    find_symbol_nodes,
    setup_multi_file_database,
)

_FH_DEF = 1
_FH_CONSUMER = 2

_CONFIG = ForwardStitcherConfig(
    max_phases=QUERY_MAX_PHASES,
    max_total_work=QUERY_MAX_WORK,
)


def _resolve_relative(
    symbol: str,
    files: dict[str, int],
) -> tuple[dict[int, StackGraphView], list[tuple[int, int]]]:
    views, database = setup_multi_file_database(files)
    view = views[_FH_CONSUMER]
    symbol_id = view.symbols.symbol_to_id.get(symbol)
    assert symbol_id is not None, f"symbol {symbol!r} not in consumer view"
    ref_nodes = find_symbol_nodes(view, symbol_id=symbol_id, is_definition=False)
    assert ref_nodes, f"no reference nodes for {symbol!r}"
    raw = resolve_definitions_multi_file(
        view,
        database,
        request=MultiFileResolutionRequest(
            file_handle=_FH_CONSUMER,
            ref_node_index=ref_nodes[0],
            config=_CONFIG,
        ),
        views=views,
    )
    assert isinstance(raw, list)
    return views, raw


def _assert_resolves_to(
    views: dict[int, StackGraphView],
    result: list[tuple[int, int]],
    symbol: str,
    def_fh: int,
) -> None:
    def_view = views[def_fh]
    sym_id = def_view.symbols.symbol_to_id.get(symbol)
    assert sym_id is not None, f"symbol {symbol!r} not interned in def view"
    def_nodes = find_symbol_nodes(def_view, symbol_id=sym_id, is_definition=True)
    assert def_nodes, f"no definition nodes for {symbol!r} in fh={def_fh}"
    resolved_in_def = [ni for fh, ni in result if fh == def_fh]
    assert resolved_in_def, f"expected resolution to fh={def_fh}, got {result}"
    assert any(n in def_nodes for n in resolved_in_def), (
        f"resolved {resolved_in_def} in fh={def_fh}, none are definitions of {symbol!r}"
    )


@pytest.mark.blends_test_group("functional")
def test_relative_dot_import_resolves_to_package() -> None:
    views, result = _resolve_relative(
        "EXPORTED_VALUE",
        {"pkg/__init__.py": _FH_DEF, "pkg/rel_dot_consumer.py": _FH_CONSUMER},
    )
    _assert_resolves_to(views, result, "EXPORTED_VALUE", _FH_DEF)


@pytest.mark.blends_test_group("functional")
def test_relative_dot_sub_import_resolves_to_subpackage() -> None:
    views, result = _resolve_relative(
        "SUB_VALUE",
        {"pkg/sub/__init__.py": _FH_DEF, "pkg/rel_sub_consumer.py": _FH_CONSUMER},
    )
    _assert_resolves_to(views, result, "SUB_VALUE", _FH_DEF)


@pytest.mark.blends_test_group("functional")
def test_relative_dotdot_import_resolves_to_parent_package() -> None:
    views, result = _resolve_relative(
        "EXPORTED_VALUE",
        {"pkg/__init__.py": _FH_DEF, "pkg/sub/rel_dotdot_consumer.py": _FH_CONSUMER},
    )
    _assert_resolves_to(views, result, "EXPORTED_VALUE", _FH_DEF)


@pytest.mark.blends_test_group("functional")
def test_relative_import_in_init_file_resolves() -> None:
    views, result = _resolve_relative(
        "PKG_HELPER",
        {"initpkg/helper.py": _FH_DEF, "initpkg/__init__.py": _FH_CONSUMER},
    )
    _assert_resolves_to(views, result, "PKG_HELPER", _FH_DEF)


@pytest.mark.blends_test_group("functional")
def test_resolve_relative_stem_init_dot_sub() -> None:
    assert _resolve_relative_stem("initpkg", 1, "helper", is_package=True) == "initpkg.helper"


@pytest.mark.blends_test_group("functional")
def test_resolve_relative_stem_init_pure_dot() -> None:
    assert _resolve_relative_stem("pkg", 1, None, is_package=True) == "pkg"


@pytest.mark.blends_test_group("functional")
def test_resolve_relative_stem_level_beyond_root_returns_none() -> None:
    assert _resolve_relative_stem("pkg.consumer", 3, None) is None
    assert _resolve_relative_stem("pkg.consumer", 3, "foo") is None


@pytest.mark.blends_test_group("functional")
def test_resolve_relative_stem_pure_dot() -> None:
    assert _resolve_relative_stem("pkg.consumer", 1, None) == "pkg"


@pytest.mark.blends_test_group("functional")
def test_resolve_relative_stem_dot_sub() -> None:
    assert _resolve_relative_stem("pkg.consumer", 1, "sub") == "pkg.sub"


@pytest.mark.blends_test_group("functional")
def test_resolve_relative_stem_dotdot() -> None:
    assert _resolve_relative_stem("pkg.sub.consumer", 2, None) == "pkg"
