from __future__ import annotations

import pytest

from blends.models import SyntaxGraph
from blends.stack.edges import Edge, add_edge
from blends.stack.forward_stitcher import ForwardStitcherConfig
from blends.stack.multi_file_stitcher import MultiFileForwardStitcher
from blends.stack.node_helpers import (
    pop_symbol_node_attributes,
    push_symbol_node_attributes,
    root_node_attributes,
    scope_node_attributes,
)
from blends.stack.partial_path_db import PartialPathDb
from blends.stack.view import StackGraphView
from test.stack.test_helpers import make_path
from test.stack.test_helpers import record as make_record

_CONFIG = ForwardStitcherConfig(max_phases=5, max_total_work=100)


def _consumer_view() -> StackGraphView:
    graph = SyntaxGraph()
    graph.add_node(1, **root_node_attributes())
    graph.add_node(2, **push_symbol_node_attributes(symbol="x", is_reference=True))
    return StackGraphView.from_syntax_graph(graph, path="consumer.py")


def _source_view(*, b_exported: bool, c_exported: bool) -> StackGraphView:
    graph = SyntaxGraph()
    graph.add_node(1, **root_node_attributes())
    graph.add_node(2, **pop_symbol_node_attributes(symbol="x", is_definition=True))
    graph.add_node(3, **pop_symbol_node_attributes(symbol="x", is_definition=True))
    graph.add_node(4, **scope_node_attributes(is_exported=b_exported))
    graph.add_node(5, **scope_node_attributes(is_exported=c_exported))
    add_edge(graph, Edge(source=4, sink=2, precedence=1))
    add_edge(graph, Edge(source=5, sink=3, precedence=1))
    return StackGraphView.from_syntax_graph(graph, path="source.py")


def _combined_view() -> StackGraphView:
    graph = SyntaxGraph()
    graph.add_node(1, **root_node_attributes())
    graph.add_node(2, **push_symbol_node_attributes(symbol="x", is_reference=True))
    graph.add_node(3, **pop_symbol_node_attributes(symbol="x", is_definition=True))
    graph.add_node(4, **pop_symbol_node_attributes(symbol="x", is_definition=True))
    graph.add_node(5, **scope_node_attributes(is_exported=True))
    graph.add_node(6, **scope_node_attributes(is_exported=False))
    add_edge(graph, Edge(source=5, sink=3, precedence=1))
    add_edge(graph, Edge(source=6, sink=4, precedence=1))
    return StackGraphView.from_syntax_graph(graph, path="combined.py")


def _cross_file_results(
    consumer_view: StackGraphView,
    source_view: StackGraphView,
) -> list[tuple[int, int]]:
    db = PartialPathDb()
    ref_idx = consumer_view.nid_to_index[2]
    b_idx = source_view.nid_to_index[2]
    c_idx = source_view.nid_to_index[3]

    db.add_bridge_path(1, make_path(ref_idx, 0))
    db.add_file(
        make_record(
            (make_path(0, b_idx), make_path(0, c_idx)),
            file_handle=2,
            file_path="source.py",
            symbol_id_to_string=(),
        )
    )

    stitcher = MultiFileForwardStitcher.from_references(
        view=consumer_view,
        file_handle=1,
        db=db,
        reference_nodes=(ref_idx,),
        config=_CONFIG,
        views={1: consumer_view, 2: source_view},
    )
    found: list[tuple[int, int]] = []
    while not stitcher.is_complete():
        result = stitcher.process_next_phase()
        for fh, path in result.complete_paths:
            found.append((fh, path.end_node_index))
    return found


@pytest.mark.blends_test_group("stack_unittesting")
def test_cross_file_non_exported_def_is_rejected() -> None:
    consumer = _consumer_view()
    source = _source_view(b_exported=True, c_exported=False)

    found = _cross_file_results(consumer, source)

    b_idx = source.nid_to_index[2]
    c_idx = source.nid_to_index[3]
    assert (2, b_idx) in found
    assert (2, c_idx) not in found


@pytest.mark.blends_test_group("stack_unittesting")
def test_same_file_non_exported_def_is_kept() -> None:
    view = _combined_view()
    ref_idx = view.nid_to_index[2]
    def_exp_idx = view.nid_to_index[3]
    def_non_idx = view.nid_to_index[4]

    db = PartialPathDb()
    db.add_bridge_path(1, make_path(ref_idx, 0))
    db.add_file(
        make_record(
            (make_path(0, def_exp_idx), make_path(0, def_non_idx)),
            file_handle=1,
            file_path="combined.py",
            symbol_id_to_string=(),
        )
    )

    stitcher = MultiFileForwardStitcher.from_references(
        view=view,
        file_handle=1,
        db=db,
        reference_nodes=(ref_idx,),
        config=_CONFIG,
        views={1: view},
    )
    found: list[tuple[int, int]] = []
    while not stitcher.is_complete():
        result = stitcher.process_next_phase()
        for fh, path in result.complete_paths:
            found.append((fh, path.end_node_index))

    assert (1, def_exp_idx) in found
    assert (1, def_non_idx) in found


@pytest.mark.blends_test_group("stack_unittesting")
def test_cross_file_all_exported_defs_are_kept() -> None:
    consumer = _consumer_view()
    source = _source_view(b_exported=True, c_exported=True)

    found = _cross_file_results(consumer, source)

    b_idx = source.nid_to_index[2]
    c_idx = source.nid_to_index[3]
    assert (2, b_idx) in found
    assert (2, c_idx) in found
