from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
    from blends.stack.view import StackGraphView

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

_FH_PROVIDER = 1
_FH_CONSUMER = 2

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


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


@pytest.mark.blends_test_group("functional")
def test_star_import_resolves_exported_symbol() -> None:
    files = {"mf_star_provider.py": _FH_PROVIDER, "mf_star_consumer.py": _FH_CONSUMER}
    views, database = setup_multi_file_database(files)
    view = views[_FH_CONSUMER]
    symbol_id = view.symbols.symbol_to_id.get("ALPHA")
    assert symbol_id is not None
    ref_nodes = find_symbol_nodes(view, symbol_id=symbol_id, is_definition=False)
    assert ref_nodes, "consumer must contain a reference to ALPHA"

    result = resolve_definitions_multi_file(
        view,
        database,
        request=MultiFileResolutionRequest(
            file_handle=_FH_CONSUMER,
            ref_node_index=ref_nodes[0],
            config=_CONFIG,
        ),
        views=views,
    )

    assert isinstance(result, list)
    assert len(result) >= 1, "ALPHA via star import must resolve to at least one definition"
    _assert_resolves_to_provider("ALPHA", result, views)


@pytest.mark.blends_test_group("functional")
def test_explicit_import_unaffected_by_star_import() -> None:
    files = {"mf_star_provider.py": _FH_PROVIDER, "mf_mixed_consumer.py": _FH_CONSUMER}
    views, database = setup_multi_file_database(files)
    view = views[_FH_CONSUMER]
    symbol_id = view.symbols.symbol_to_id.get("ALPHA")
    assert symbol_id is not None
    ref_nodes = find_symbol_nodes(view, symbol_id=symbol_id, is_definition=False)
    assert ref_nodes, "mixed consumer must contain a reference to ALPHA"

    result = resolve_definitions_multi_file(
        view,
        database,
        request=MultiFileResolutionRequest(
            file_handle=_FH_CONSUMER,
            ref_node_index=ref_nodes[0],
            config=_CONFIG,
        ),
        views=views,
    )

    assert isinstance(result, list)
    assert len(result) >= 1, "explicit import of ALPHA must still resolve"
    _assert_resolves_to_provider("ALPHA", result, views)


@pytest.mark.blends_test_group("functional")
def test_star_import_without_provider_in_db_produces_no_crash() -> None:
    files = {"mf_star_consumer.py": _FH_CONSUMER}
    views, database = setup_multi_file_database(files)
    view = views[_FH_CONSUMER]
    symbol_id = view.symbols.symbol_to_id.get("ALPHA")
    assert symbol_id is not None
    ref_nodes = find_symbol_nodes(view, symbol_id=symbol_id, is_definition=False)
    assert ref_nodes, "consumer must contain a reference to ALPHA"

    result = resolve_definitions_multi_file(
        view,
        database,
        request=MultiFileResolutionRequest(
            file_handle=_FH_CONSUMER,
            ref_node_index=ref_nodes[0],
            config=_CONFIG,
        ),
        views=views,
    )

    assert result == [], "star import with missing provider must return empty, not crash"
