"""Tests for backend.semantic_graph.SemanticGraphService."""

from __future__ import annotations

import pytest

from backend.semantic_graph import SemanticGraphService


@pytest.fixture
def svc():
    return SemanticGraphService()


class TestDerive:
    def test_simple_equation(self, svc):
        graph = svc.latex_to_graph("F = m a")
        assert graph is not None
        assert graph.nodes is not None
        assert graph.edges is not None

    def test_returns_none_for_empty(self, svc):
        assert svc.latex_to_graph("") is None

    def test_returns_none_for_none(self, svc):
        assert svc.latex_to_graph(None) is None

    def test_domain_carried(self, svc):
        graph = svc.latex_to_graph("F = ma", domain="physics")
        assert graph is not None
        assert graph.domain == "physics"

    def test_chained_equals(self, svc):
        graph = svc.latex_to_graph("a = b = c")
        assert graph is not None
        eq_nodes = [n for n in graph.nodes if n.op == "equals"]
        assert len(eq_nodes) >= 1

    def test_statement_separator(self, svc):
        graph = svc.latex_to_graph(r"a = 1 \\ b = 2")
        assert graph is not None

    def test_relation_approx(self, svc):
        graph = svc.latex_to_graph(r"a \approx b")
        assert graph is not None

    def test_element_of(self, svc):
        graph = svc.latex_to_graph(r"x \in \mathbb{R}")
        assert graph is not None
        rel_nodes = [n for n in graph.nodes if n.op == "element_of"]
        assert len(rel_nodes) == 1

    def test_annotation_preserved(self, svc):
        graph = svc.latex_to_graph(r"F = ma \quad (v_e \text{ constant})")
        assert graph is not None
        ann_nodes = [n for n in graph.nodes
                     if n.id.startswith("__annotation_")]
        assert len(ann_nodes) >= 1

    def test_compound_symbol(self, svc):
        graph = svc.latex_to_graph(r"\Delta t = 1")
        assert graph is not None

    def test_logical_connective(self, svc):
        graph = svc.latex_to_graph(r"a = 1 \implies b = 2")
        assert graph is not None

    @pytest.mark.parametrize("wrapped", [
        r"$m a = \frac{1}{2}\rho V^2 C_d A$",
        r"$$m a = \frac{1}{2}\rho V^2 C_d A$$",
        r"\(m a = \frac{1}{2}\rho V^2 C_d A\)",
        r"\[m a = \frac{1}{2}\rho V^2 C_d A\]",
    ])
    def test_math_delimiters_stripped(self, svc, wrapped):
        # A source that wraps the expression in math-mode delimiters (e.g. an
        # LM-inferred proof start) must still parse — regression for the
        # proof_animation 400 "couldn't parse the start expression".
        graph = svc.latex_to_graph(wrapped, domain="classical_mechanics")
        assert graph is not None
        # And it caches identically to the bare form.
        bare = svc.latex_to_graph(r"m a = \frac{1}{2}\rho V^2 C_d A",
                                  domain="classical_mechanics")
        assert graph is bare


class TestCaching:
    def test_cache_hit(self, svc):
        g1 = svc.latex_to_graph("x = 1")
        g2 = svc.latex_to_graph("x = 1")
        assert g1 is g2

    def test_domain_isolates_cache(self, svc):
        g1 = svc.latex_to_graph("F = ma")
        g2 = svc.latex_to_graph("F = ma", domain="physics")
        assert g1 is not g2

    def test_none_cached(self, svc):
        r1 = svc.latex_to_graph("")
        r2 = svc.latex_to_graph("")
        assert r1 is None
        assert r2 is None

    def test_clear_cache(self, svc):
        svc.latex_to_graph("x = 1")
        svc.clear_cache()
        g2 = svc.latex_to_graph("x = 1")
        assert g2 is not None
