from unittest.mock import (
    patch,
)

import pytest

from blends.models import (
    Graph,
    Language,
    NId,
    SyntaxGraph,
)
from blends.stack.attributes import (
    STACK_GRAPH_KIND,
    STACK_GRAPH_SYMBOL,
)
from blends.stack.node_kinds import (
    StackGraphNodeKind,
)
from blends.syntax.builders.assignment import (
    build_assignment_node,
)
from blends.syntax.builders.for_each_statement import (
    build_for_each_statement_node,
)
from blends.syntax.builders.for_statement import (
    build_for_statement_node,
)
from blends.syntax.models import (
    SyntaxGraphArgs,
    SyntaxMetadata,
)


def _make_args(
    *, syntax_graph: SyntaxGraph, ast_graph: Graph, n_id: NId, metadata: SyntaxMetadata
) -> SyntaxGraphArgs:
    def generic(args: SyntaxGraphArgs) -> NId:
        return args.n_id

    return SyntaxGraphArgs(
        generic=generic,
        path="test.py",
        language=Language.PYTHON,
        ast_graph=ast_graph,
        syntax_graph=syntax_graph,
        n_id=n_id,
        metadata=metadata,
    )


@pytest.mark.blends_test_group("stack_unittesting")
def test_assignment_emits_pop_symbol_and_scope_edge_for_identifier_target() -> None:
    syntax_graph = SyntaxGraph()
    ast_graph = Graph()
    metadata: SyntaxMetadata = {"class_path": [], "scope_stack": ["1"]}
    syntax_graph.add_node("1", label_type="File")

    ast_graph.add_node("10", label_type="identifier", label_text="x")
    ast_graph.add_node("11", label_type="integer", label_text="1")

    args = _make_args(syntax_graph=syntax_graph, ast_graph=ast_graph, n_id="2", metadata=metadata)
    with patch("blends.ctx.ctx.has_feature_flag", side_effect=lambda flag: flag == "StackGraph"):
        build_assignment_node(args, var_id="10", val_id="11", operator=None)

    assert syntax_graph.nodes["2"][STACK_GRAPH_KIND] == StackGraphNodeKind.POP_SCOPED_SYMBOL.value
    assert syntax_graph.nodes["2"][STACK_GRAPH_SYMBOL] == "x"
    assert syntax_graph.has_edge("1", "2")


@pytest.mark.blends_test_group("stack_unittesting")
def test_for_each_emits_pop_symbol_and_scope_edge_for_identifier_target() -> None:
    syntax_graph = SyntaxGraph()
    ast_graph = Graph()
    metadata: SyntaxMetadata = {"class_path": [], "scope_stack": ["1"]}
    syntax_graph.add_node("1", label_type="File")

    ast_graph.add_node("10", label_type="identifier", label_text="x")
    ast_graph.add_node("11", label_type="identifier", label_text="xs")

    args = _make_args(syntax_graph=syntax_graph, ast_graph=ast_graph, n_id="2", metadata=metadata)
    with patch("blends.ctx.ctx.has_feature_flag", side_effect=lambda flag: flag == "StackGraph"):
        build_for_each_statement_node(args, var_node="10", iterable_item="11", block_id=None)

    assert syntax_graph.nodes["2"][STACK_GRAPH_KIND] == StackGraphNodeKind.POP_SCOPED_SYMBOL.value
    assert syntax_graph.nodes["2"][STACK_GRAPH_SYMBOL] == "x"
    assert syntax_graph.has_edge("1", "2")


@pytest.mark.blends_test_group("stack_unittesting")
def test_for_statement_emits_pop_symbol_and_scope_edge_for_identifier_initializer() -> None:
    syntax_graph = SyntaxGraph()
    ast_graph = Graph()
    metadata: SyntaxMetadata = {"class_path": [], "scope_stack": ["1"]}
    syntax_graph.add_node("1", label_type="File")

    ast_graph.add_node("10", label_type="identifier", label_text="x")
    ast_graph.add_node("11", label_type="identifier", label_text="xs")
    ast_graph.add_node("12", label_type="block", label_text="")

    args = _make_args(syntax_graph=syntax_graph, ast_graph=ast_graph, n_id="2", metadata=metadata)
    with patch("blends.ctx.ctx.has_feature_flag", side_effect=lambda flag: flag == "StackGraph"):
        build_for_statement_node(
            args, initializer_node="10", condition_node="11", update_node=None, body_node="12"
        )

    assert syntax_graph.nodes["2"][STACK_GRAPH_KIND] == StackGraphNodeKind.POP_SCOPED_SYMBOL.value
    assert syntax_graph.nodes["2"][STACK_GRAPH_SYMBOL] == "x"
    assert syntax_graph.has_edge("1", "2")


@pytest.mark.blends_test_group("stack_unittesting")
def test_multiple_assignments_get_incrementing_precedence() -> None:
    syntax_graph = SyntaxGraph()
    ast_graph = Graph()
    metadata: SyntaxMetadata = {"class_path": [], "scope_stack": ["1"]}
    syntax_graph.add_node("1", label_type="File")

    ast_graph.add_node("10", label_type="identifier", label_text="x")
    ast_graph.add_node("11", label_type="integer", label_text="1")
    ast_graph.add_node("20", label_type="identifier", label_text="x")
    ast_graph.add_node("21", label_type="integer", label_text="2")

    with patch("blends.ctx.ctx.has_feature_flag", side_effect=lambda flag: flag == "StackGraph"):
        args1 = _make_args(
            syntax_graph=syntax_graph, ast_graph=ast_graph, n_id="2", metadata=metadata
        )
        build_assignment_node(args1, var_id="10", val_id="11", operator=None)

        args2 = _make_args(
            syntax_graph=syntax_graph, ast_graph=ast_graph, n_id="3", metadata=metadata
        )
        build_assignment_node(args2, var_id="20", val_id="21", operator=None)

    assert syntax_graph.has_edge("1", "2")
    assert syntax_graph.has_edge("1", "3")

    edge_data_first = syntax_graph["1"]["2"]
    edge_data_second = syntax_graph["1"]["3"]

    assert edge_data_first["precedence"] == 1
    assert edge_data_second["precedence"] == 1
    assert edge_data_first["precedence"] == edge_data_second["precedence"]


@pytest.mark.blends_test_group("stack_unittesting")
def test_binding_sites_do_not_emit_stack_graph_when_feature_preview_disabled() -> None:
    syntax_graph = SyntaxGraph()
    ast_graph = Graph()
    metadata: SyntaxMetadata = {"class_path": [], "scope_stack": ["1"]}
    syntax_graph.add_node("1", label_type="File")

    ast_graph.add_node("10", label_type="identifier", label_text="x")
    ast_graph.add_node("11", label_type="identifier", label_text="xs")
    ast_graph.add_node("12", label_type="block", label_text="")

    with patch("blends.ctx.ctx.has_feature_flag", return_value=False):
        build_assignment_node(
            _make_args(syntax_graph=syntax_graph, ast_graph=ast_graph, n_id="2", metadata=metadata),
            var_id="10",
            val_id="11",
            operator=None,
        )
        build_for_each_statement_node(
            _make_args(syntax_graph=syntax_graph, ast_graph=ast_graph, n_id="3", metadata=metadata),
            var_node="10",
            iterable_item="11",
            block_id=None,
        )
        build_for_statement_node(
            _make_args(syntax_graph=syntax_graph, ast_graph=ast_graph, n_id="4", metadata=metadata),
            initializer_node="10",
            condition_node="11",
            update_node=None,
            body_node="12",
        )

    assert STACK_GRAPH_KIND not in syntax_graph.nodes["2"]
    assert STACK_GRAPH_KIND not in syntax_graph.nodes["3"]
    assert STACK_GRAPH_KIND not in syntax_graph.nodes["4"]
    assert not syntax_graph.has_edge("1", "2")
    assert not syntax_graph.has_edge("1", "3")
    assert not syntax_graph.has_edge("1", "4")
