"""Tests for grammar round-trip with TemplateParam ($param{type}) syntax."""

from __future__ import annotations

import datetime

from therismos import F, Serializer
from therismos.expr._expr import TemplateParam
from therismos.expr.template import bind


class TestGrammarRoundTrip:
    """Serialize TemplateParam expressions → string → deserialize → structurally equal."""

    def setup_method(self) -> None:
        import datetime

        self.ser = Serializer(type_registry={datetime.date: "date", int: "int", str: "str"})

    def _roundtrip(self, expr):
        s = self.ser.serialize(expr)
        return self.ser.deserialize(s), s

    def test_simple_param_no_type(self) -> None:
        p = TemplateParam("threshold")
        expr = F("age") == p
        result, s = self._roundtrip(expr)
        assert "$threshold" in s
        assert collect_params_names(result) == {"threshold"}

    def test_simple_param_with_type(self) -> None:
        p = TemplateParam("start", datetime.date)
        expr = F("created", datetime.date) >= p
        result, s = self._roundtrip(expr)
        assert "$start{date}" in s
        assert collect_params_names(result) == {"start"}

    def test_two_params(self) -> None:
        p_start = TemplateParam("start", datetime.date)
        p_end = TemplateParam("end", datetime.date)
        field = F("created", datetime.date)
        expr = (field >= p_start) & (field <= p_end)
        result, s = self._roundtrip(expr)
        assert "$start" in s
        assert "$end" in s
        assert collect_params_names(result) == {"start", "end"}

    def test_param_in_between(self) -> None:
        lower = TemplateParam("lo", int)
        upper = TemplateParam("hi", int)
        expr = F("score", int).between(lower, upper)
        result, s = self._roundtrip(expr)
        assert "$lo" in s
        assert "$hi" in s

    def test_param_type_preserved_after_roundtrip(self) -> None:
        p = TemplateParam("start", datetime.date)
        expr = F("created", datetime.date) >= p
        result, _ = self._roundtrip(expr)
        # After deserialize, bind with a date string should cast correctly
        params_in_result = get_params(result)
        assert "start" in params_in_result
        # The type_ should be the date type from the registry
        assert params_in_result["start"].type_ is datetime.date

    def test_serialize_param_no_type_round_trips(self) -> None:
        p = TemplateParam("val")
        expr = F("x") != p
        result, s = self._roundtrip(expr)
        # Bind with a string value should work
        bound = bind(result, {"val": "active"})
        assert bound.value == "active"

    def test_in_expr_with_mixed_params_and_literals(self) -> None:
        p = TemplateParam("extra")
        expr = F("status").is_in("active", p)
        result, s = self._roundtrip(expr)
        assert "$extra" in s

    def test_or_expr_round_trip(self) -> None:
        p = TemplateParam("min_age", int)
        expr = (F("age", int) >= p) | (F("vip") == True)  # noqa: E712
        result, s = self._roundtrip(expr)
        assert "$min_age" in s

    def test_not_expr_round_trip(self) -> None:
        p = TemplateParam("val")
        expr = ~(F("x") == p)
        result, s = self._roundtrip(expr)
        assert "$val" in s

    def test_bind_after_grammar_roundtrip(self) -> None:
        p = TemplateParam("threshold", int)
        expr = F("score", int) >= p
        result, _ = self._roundtrip(expr)
        bound = bind(result, {"threshold": 80})
        assert bound.value == 80


def collect_params_names(expr) -> set[str]:
    from therismos.expr.template import collect_params

    return set(collect_params(expr))


def get_params(expr) -> dict:
    from therismos.expr.template import collect_params

    return collect_params(expr)
