"""Tests for ``mgf.apiprobe.core.matcher`` — base + composition."""

from __future__ import annotations

import pytest

from mgf.apiprobe.core.matcher import MISSING, Matcher


class _Always(Matcher):
    def matches(self, value: object) -> bool:
        return True

    def describe(self) -> str:
        return "always"


class _Never(Matcher):
    def matches(self, value: object) -> bool:
        return False

    def describe(self) -> str:
        return "never"


class TestMatcherCallSyntax:
    def test_call_invokes_matches(self) -> None:
        m = _Always()
        assert m("anything") is True
        assert m.matches("anything") is True


class TestComposition:
    def test_and_both_true(self) -> None:
        m = _Always() & _Always()
        assert m("x") is True
        assert "AND" in m.describe()

    def test_and_left_false(self) -> None:
        m = _Never() & _Always()
        assert m("x") is False

    def test_and_right_false(self) -> None:
        m = _Always() & _Never()
        assert m("x") is False

    def test_or_either_true(self) -> None:
        assert (_Always() | _Never())("x") is True
        assert (_Never() | _Always())("x") is True

    def test_or_both_false(self) -> None:
        assert (_Never() | _Never())("x") is False

    def test_invert(self) -> None:
        m = ~_Never()
        assert m("x") is True
        assert "NOT" in m.describe()

    def test_double_invert(self) -> None:
        m = ~~_Always()
        assert m("x") is True


class TestWhenPresent:
    def test_missing_passes(self) -> None:
        m = _Never().when_present()
        # Even though the underlying matcher always fails, MISSING is tolerated.
        assert m(MISSING) is True

    def test_present_delegates_true(self) -> None:
        assert _Always().when_present()("x") is True

    def test_present_delegates_false(self) -> None:
        assert _Never().when_present()("x") is False

    def test_describe_includes_when_present(self) -> None:
        assert "when present" in _Always().when_present().describe()


class TestMissingSentinel:
    def test_singleton(self) -> None:
        from mgf.apiprobe.core.matcher import _MISSING

        assert _MISSING() is MISSING

    def test_repr(self) -> None:
        assert repr(MISSING) == "MISSING"

    def test_distinguishable_from_none(self) -> None:
        assert MISSING is not None


class TestAbstractEnforcement:
    def test_cannot_instantiate_matcher_directly(self) -> None:
        with pytest.raises(TypeError):
            Matcher()  # type: ignore[abstract]
