"""User-perspective tests for ``mgf.apiprobe.core.matchers``.

Surfaces exercised:
  - All 16 matcher factories importable from the public path.
  - Each returns a callable matcher that accepts a value and
    returns a bool.
  - Factory shape: equality / numeric matchers take a value
    (``equals(5)``); format matchers are zero-arg
    (``is_uuid()``).
"""

from __future__ import annotations

import pytest

from mgf.apiprobe.core.matchers import (
    between,
    equals,
    gt,
    gte,
    in_,
    is_e164,
    is_email,
    is_iban,
    is_imei,
    is_iso8601_z,
    is_url,
    is_uuid,
    lt,
    lte,
    matches,
    not_equals,
    not_in,
)


class TestEqualityMatchers:
    def test_equals(self) -> None:
        m = equals(5)
        assert m(5) is True
        assert m(6) is False

    def test_not_equals(self) -> None:
        m = not_equals(5)
        assert m(5) is False
        assert m(6) is True


class TestNumericMatchers:
    def test_gt(self) -> None:
        m = gt(10)
        assert m(11) is True
        assert m(10) is False

    def test_gte(self) -> None:
        m = gte(10)
        assert m(10) is True
        assert m(9) is False

    def test_lt(self) -> None:
        m = lt(10)
        assert m(9) is True
        assert m(10) is False

    def test_lte(self) -> None:
        m = lte(10)
        assert m(10) is True
        assert m(11) is False

    def test_between_inclusive(self) -> None:
        m = between(1, 10)
        assert m(1) is True
        assert m(10) is True
        assert m(5) is True
        assert m(0) is False
        assert m(11) is False


class TestMembershipMatchers:
    def test_in(self) -> None:
        # Varargs — pass each allowed value as a positional arg.
        m = in_("a", "b", "c")
        assert m("a") is True
        assert m("z") is False

    def test_not_in(self) -> None:
        m = not_in("a", "b", "c")
        assert m("a") is False
        assert m("z") is True


class TestRegexMatcher:
    def test_matches(self) -> None:
        m = matches(r"^foo\d+$")
        assert m("foo123") is True
        assert m("bar123") is False


class TestFormatMatchers:
    """Format matchers are zero-arg factories."""

    @pytest.mark.parametrize(
        "factory,good,bad",
        [
            (is_uuid, "550e8400-e29b-41d4-a716-446655440000", "not-a-uuid"),
            (is_email, "alice@example.com", "no-at-sign"),
            (is_url, "https://example.com/path", "::not_a_url::"),
            (is_iso8601_z, "2026-04-30T12:34:56Z", "yesterday"),
        ],
    )
    def test_format_match(
        self,
        factory: object,
        good: str,
        bad: str,
    ) -> None:
        m = factory()  # type: ignore[operator]
        assert m(good) is True, f"{factory!r} should accept {good!r}"
        assert m(bad) is False, f"{factory!r} should reject {bad!r}"


class TestSemanticMatchers:
    """IBAN / IMEI / E.164 — load-bearing for telecom + finance APIs."""

    def test_is_iban(self) -> None:
        m = is_iban()
        # MOD-97 valid IBAN (Wikipedia example).
        assert m("GB82WEST12345698765432") is True
        # Wrong checksum.
        assert m("GB00WEST12345698765432") is False

    def test_is_imei(self) -> None:
        m = is_imei()
        # Valid Luhn-checksum IMEI (synthetic).
        assert m("490154203237518") is True
        assert m("490154203237519") is False  # wrong checksum

    def test_is_e164(self) -> None:
        m = is_e164()
        assert m("+14155551234") is True
        assert m("4155551234") is False  # missing leading +
