"""Tests for _matchers module."""

from __future__ import annotations

import pytest

from db_test_helpers.core._matchers import (
    AnyMatcher,
    AndMatcher,
    EqMatcher,
    GtMatcher,
    LtMatcher,
    NotMatcher,
    OrMatcher,
    is_dsl_string,
    is_var_dsl,
    resolve_var,
)


# --- is_dsl_string ---


class TestIsDslString:
    def test_valid_dsl(self):
        assert is_dsl_string("$eq(hello)") is True
        assert is_dsl_string("$lt(100)") is True
        assert is_dsl_string("$gt(0)") is True
        assert is_dsl_string("$not($eq(x))") is True
        assert is_dsl_string("$and($gt(0),$lt(10))") is True
        assert is_dsl_string("$or($eq(a),$eq(b))") is True
        assert is_dsl_string("$var(uid)") is True
        assert is_dsl_string("$any()") is True

    def test_unknown_dsl_function(self):
        assert is_dsl_string("$unknown(x)") is True
        assert is_dsl_string("$foo(bar)") is True

    def test_non_dsl_string(self):
        assert is_dsl_string("hello") is False
        assert is_dsl_string("") is False

    def test_non_string(self):
        assert is_dsl_string(123) is False
        assert is_dsl_string(None) is False
        assert is_dsl_string(True) is False


# --- is_var_dsl ---


class TestIsVarDsl:
    def test_var(self):
        assert is_var_dsl("$var(uid)") is True

    def test_not_var(self):
        assert is_var_dsl("$eq(x)") is False
        assert is_var_dsl("$any()") is False
        assert is_var_dsl("hello") is False
        assert is_var_dsl(123) is False


# --- Matchers ---


class TestAnyMatcher:
    def test_matches_anything(self):
        m = AnyMatcher()
        assert m.matches("hello") is True
        assert m.matches(123) is True
        assert m.matches(None) is True
        assert m.matches(True) is True
        assert m.matches([1, 2]) is True
        assert m.matches({"a": 1}) is True


class TestEqMatcher:
    def test_matches_same_type_and_value(self):
        assert EqMatcher("hello").matches("hello") is True
        assert EqMatcher(42).matches(42) is True

    def test_rejects_different_value(self):
        assert EqMatcher("hello").matches("world") is False

    def test_rejects_different_type(self):
        assert EqMatcher(1).matches(1.0) is False
        assert EqMatcher("1").matches(1) is False


class TestLtMatcher:
    def test_less_than(self):
        assert LtMatcher(100).matches(50) is True
        assert LtMatcher(100).matches(100) is False
        assert LtMatcher(100).matches(150) is False

    def test_rejects_non_number(self):
        assert LtMatcher(100).matches("50") is False
        assert LtMatcher(100).matches(True) is False


class TestGtMatcher:
    def test_greater_than(self):
        assert GtMatcher(0).matches(1) is True
        assert GtMatcher(0).matches(0) is False
        assert GtMatcher(0).matches(-1) is False

    def test_rejects_non_number(self):
        assert GtMatcher(0).matches("1") is False
        assert GtMatcher(0).matches(False) is False


class TestNotMatcher:
    def test_negates_inner(self):
        assert NotMatcher(EqMatcher("admin")).matches("user") is True
        assert NotMatcher(EqMatcher("admin")).matches("admin") is False


class TestAndMatcher:
    def test_all_must_match(self):
        m = AndMatcher([GtMatcher(0), LtMatcher(100)])
        assert m.matches(50) is True
        assert m.matches(0) is False
        assert m.matches(100) is False


class TestOrMatcher:
    def test_any_must_match(self):
        m = OrMatcher([EqMatcher("active"), EqMatcher("pending")])
        assert m.matches("active") is True
        assert m.matches("pending") is True
        assert m.matches("deleted") is False


# --- resolve_var ---


class TestResolveVar:
    def test_resolves_var(self):
        assert resolve_var("$var(uid)", {"uid": "abc-123"}) == "abc-123"

    def test_non_string_variable(self):
        assert resolve_var("$var(count)", {"count": 42}) == 42

    def test_undefined_variable_raises(self):
        with pytest.raises(ValueError, match="Undefined variable"):
            resolve_var("$var(missing)", {})
