"""Tests for voicetest.exporters.graph_viz module."""

import pytest

from voicetest.exporters.graph_viz import export_mermaid
from voicetest.models.agent import AgentGraph
from voicetest.models.agent import AgentNode
from voicetest.models.agent import EquationClause
from voicetest.models.agent import GlobalNodeSetting
from voicetest.models.agent import GoBackCondition
from voicetest.models.agent import ToolDefinition
from voicetest.models.agent import Transition
from voicetest.models.agent import TransitionCondition


@pytest.fixture
def graph_with_end_call() -> AgentGraph:
    """Create a graph with end_call tools for testing."""
    return AgentGraph(
        nodes={
            "greeting": AgentNode(
                id="greeting",
                state_prompt="Greet the user warmly.",
                transitions=[
                    Transition(
                        target_node_id="closing",
                        condition=TransitionCondition(type="llm_prompt", value="User satisfied"),
                    )
                ],
                tools=[
                    ToolDefinition(
                        name="end_call",
                        description="End the call when done",
                        parameters={},
                    )
                ],
            ),
            "closing": AgentNode(
                id="closing",
                state_prompt="Close the conversation.",
                transitions=[],
                tools=[
                    ToolDefinition(
                        name="end_call",
                        description="End the call politely",
                        parameters={},
                    )
                ],
            ),
        },
        entry_node_id="greeting",
        source_type="custom",
    )


class TestExportMermaid:
    """Tests for export_mermaid function."""

    def test_basic_graph_export(self, simple_graph):
        """Test basic mermaid export."""
        result = export_mermaid(simple_graph)

        assert "flowchart TD" in result
        assert "greeting" in result
        assert "farewell" in result

    def test_includes_node_labels(self, simple_graph):
        """Test that node labels include truncated instructions."""
        result = export_mermaid(simple_graph)

        # Node IDs should be present
        assert "greeting" in result
        assert "farewell" in result
        # Unique instructions should be shown (common prefix stripped)
        assert "Greet the user" in result
        assert "Say goodbye" in result

    def test_includes_transitions(self, simple_graph):
        """Test that transitions are included."""
        result = export_mermaid(simple_graph)

        assert "greeting -->" in result
        assert "farewell" in result

    def test_entry_node_styled_green(self, simple_graph):
        """Test that entry node has green styling."""
        result = export_mermaid(simple_graph)

        assert "style greeting fill:#16a34a" in result

    def test_quotes_escaped_in_labels(self):
        """Test that quotes in node IDs are escaped in labels."""
        graph = AgentGraph(
            nodes={
                'node_with_"quotes"': AgentNode(
                    id='node_with_"quotes"',
                    state_prompt="Test instructions",
                    transitions=[],
                )
            },
            entry_node_id='node_with_"quotes"',
            source_type="custom",
        )

        result = export_mermaid(graph)

        # Quotes should be escaped to single quotes
        assert "node_with_'quotes'" in result

    def test_underscores_preserved_in_labels(self):
        """Test that underscores in node IDs are preserved correctly."""
        graph = AgentGraph(
            nodes={
                "step_one_start": AgentNode(
                    id="step_one_start",
                    state_prompt="Begin the first step",
                    transitions=[
                        Transition(
                            target_node_id="step_two_continue",
                            condition=TransitionCondition(type="llm_prompt", value="user confirms"),
                        )
                    ],
                ),
                "step_two_continue": AgentNode(
                    id="step_two_continue",
                    state_prompt="Continue to next step",
                    transitions=[],
                ),
            },
            entry_node_id="step_one_start",
            source_type="custom",
        )

        result = export_mermaid(graph)

        # Node IDs with underscores should appear in the output
        assert "step_one_start" in result
        assert "step_two_continue" in result
        # The label inside quotes should contain the full node ID
        assert '["step_one_start<br/>' in result
        assert '["step_two_continue<br/>' in result


class TestMermaidEndCallNode:
    """Tests for end_call node in mermaid export."""

    def test_end_call_node_added(self, graph_with_end_call):
        """Test that end_call node is added when tools exist."""
        result = export_mermaid(graph_with_end_call)

        assert 'end_call(("End Call"))' in result

    def test_end_call_edges_from_nodes_with_tool(self, graph_with_end_call):
        """Test that edges to end_call are added from nodes with end_call tool."""
        result = export_mermaid(graph_with_end_call)

        assert "greeting -->" in result
        assert "end_call" in result
        # Both greeting and closing have end_call tool
        assert result.count("| end_call") == 2

    def test_end_call_node_styled_red(self, graph_with_end_call):
        """Test that end_call node has red styling."""
        result = export_mermaid(graph_with_end_call)

        assert "style end_call fill:#dc2626" in result

    def test_no_end_call_node_without_tools(self, simple_graph):
        """Test that end_call node is not added when no end_call tools exist."""
        result = export_mermaid(simple_graph)

        assert "end_call" not in result

    def test_end_call_edge_includes_description(self, graph_with_end_call):
        """Test that end_call edge includes tool description."""
        result = export_mermaid(graph_with_end_call)

        # Description should be in the edge label
        assert "End the call" in result


class TestMermaidLogicNode:
    """Tests for logic/branch node rendering in mermaid export."""

    @pytest.fixture
    def logic_graph(self) -> AgentGraph:
        """Graph with a logic split node that routes based on equations."""
        return AgentGraph(
            nodes={
                "start": AgentNode(
                    id="start",
                    state_prompt="Welcome the caller.",
                    transitions=[
                        Transition(
                            target_node_id="router",
                            condition=TransitionCondition(
                                type="llm_prompt",
                                value="User identified",
                            ),
                        )
                    ],
                ),
                "router": AgentNode(
                    id="router",
                    state_prompt="",
                    transitions=[
                        Transition(
                            target_node_id="premium",
                            condition=TransitionCondition(
                                type="equation",
                                value="account_type == premium",
                                equations=[
                                    EquationClause(
                                        left="account_type",
                                        operator="==",
                                        right="premium",
                                    )
                                ],
                            ),
                        ),
                        Transition(
                            target_node_id="standard",
                            condition=TransitionCondition(type="always", value="Else"),
                        ),
                    ],
                    metadata={"retell_type": "logic_split", "name": "Account Router"},
                ),
                "premium": AgentNode(
                    id="premium",
                    state_prompt="Handle premium customer.",
                    transitions=[],
                ),
                "standard": AgentNode(
                    id="standard",
                    state_prompt="Handle standard customer.",
                    transitions=[],
                ),
            },
            entry_node_id="start",
            source_type="retell",
        )

    def test_logic_node_rendered_as_diamond(self, logic_graph):
        """Logic split nodes use Mermaid diamond shape syntax."""
        result = export_mermaid(logic_graph)
        assert 'router{"Logic Split<br/>Account Router"}' in result

    def test_logic_node_not_rendered_as_rectangle(self, logic_graph):
        """Logic split nodes should not use the standard rectangle format."""
        result = export_mermaid(logic_graph)
        assert 'router["' not in result

    def test_regular_nodes_still_rectangles(self, logic_graph):
        """Non-logic nodes should still use rectangle format."""
        result = export_mermaid(logic_graph)
        assert 'start["start<br/>' in result
        assert 'premium["premium<br/>' in result

    def test_logic_node_uses_id_when_no_name(self):
        """Logic node without a name in metadata falls back to node ID."""
        graph = AgentGraph(
            nodes={
                "branch1": AgentNode(
                    id="branch1",
                    state_prompt="",
                    transitions=[
                        Transition(
                            target_node_id="a",
                            condition=TransitionCondition(
                                type="equation",
                                value="x == 1",
                                equations=[EquationClause(left="x", operator="==", right="1")],
                            ),
                        ),
                        Transition(
                            target_node_id="b",
                            condition=TransitionCondition(type="always", value="Else"),
                        ),
                    ],
                    metadata={"retell_type": "branch"},
                ),
                "a": AgentNode(id="a", state_prompt="A", transitions=[]),
                "b": AgentNode(id="b", state_prompt="B", transitions=[]),
            },
            entry_node_id="branch1",
            source_type="retell",
        )
        result = export_mermaid(graph)
        assert 'branch1{"Logic Split<br/>branch1"}' in result


class TestMermaidGlobalNode:
    """Tests for global node styling in mermaid export."""

    @pytest.fixture
    def global_graph(self) -> AgentGraph:
        """Graph with a global node."""
        return AgentGraph(
            nodes={
                "greeting": AgentNode(
                    id="greeting",
                    state_prompt="Greet the caller.",
                    transitions=[
                        Transition(
                            target_node_id="order",
                            condition=TransitionCondition(
                                type="llm_prompt", value="Caller wants to order"
                            ),
                        )
                    ],
                ),
                "order": AgentNode(
                    id="order",
                    state_prompt="Take the order.",
                    transitions=[],
                ),
                "cancel": AgentNode(
                    id="cancel",
                    state_prompt="Ask if they want to cancel.",
                    transitions=[],
                    global_node_setting=GlobalNodeSetting(
                        condition="Caller wants to cancel",
                        go_back_conditions=[
                            GoBackCondition(
                                id="gb-1",
                                condition=TransitionCondition(
                                    type="llm_prompt",
                                    value="Caller changes mind",
                                ),
                            ),
                        ],
                    ),
                ),
            },
            entry_node_id="greeting",
            source_type="retell",
        )

    def test_global_node_has_stroke_style(self, global_graph):
        """Global nodes get a colored border via stroke style."""
        result = export_mermaid(global_graph)
        assert "style cancel" in result
        assert "stroke:" in result

    def test_global_node_stroke_is_purple(self, global_graph):
        """Global nodes use a purple stroke to indicate global reachability."""
        result = export_mermaid(global_graph)
        # Find the style line for cancel
        style_lines = [line for line in result.split("\n") if "style cancel" in line]
        assert len(style_lines) == 1
        assert "stroke:#7c3aed" in style_lines[0]

    def test_global_node_has_thick_stroke(self, global_graph):
        """Global nodes use a thicker border to stand out."""
        result = export_mermaid(global_graph)
        style_lines = [line for line in result.split("\n") if "style cancel" in line]
        assert "stroke-width:3px" in style_lines[0]

    def test_non_global_nodes_no_stroke_style(self, global_graph):
        """Non-global nodes don't get the global stroke style."""
        result = export_mermaid(global_graph)
        style_lines = [line for line in result.split("\n") if "style order" in line]
        assert len(style_lines) == 0

    def test_global_node_keeps_rectangle_shape(self, global_graph):
        """Global conversation nodes still use rectangle shape."""
        result = export_mermaid(global_graph)
        assert 'cancel["cancel<br/>' in result

    def test_multiple_global_nodes_styled(self):
        """All global nodes get the stroke style."""
        graph = AgentGraph(
            nodes={
                "main": AgentNode(id="main", state_prompt="Main."),
                "faq": AgentNode(
                    id="faq",
                    state_prompt="FAQ.",
                    global_node_setting=GlobalNodeSetting(condition="User asks FAQ"),
                ),
                "cancel": AgentNode(
                    id="cancel",
                    state_prompt="Cancel.",
                    global_node_setting=GlobalNodeSetting(condition="User cancels"),
                ),
            },
            entry_node_id="main",
            source_type="retell",
        )
        result = export_mermaid(graph)
        faq_styles = [line for line in result.split("\n") if "style faq" in line]
        cancel_styles = [line for line in result.split("\n") if "style cancel" in line]
        assert len(faq_styles) == 1
        assert len(cancel_styles) == 1
        assert "stroke:#7c3aed" in faq_styles[0]
        assert "stroke:#7c3aed" in cancel_styles[0]

    def test_no_global_nodes_no_stroke_styles(self, simple_graph):
        """Graph without global nodes has no stroke styles."""
        result = export_mermaid(simple_graph)
        assert "stroke:#7c3aed" not in result
