"""Tests for ``mgf.apiprobe.factories.builtins``.

Each test confirms the factory output passes its corresponding
matcher — the contract DESIGN.md §3.4.2 + the matcher tests rely on.
"""

from __future__ import annotations

import re
from datetime import UTC, datetime

import pytest

from mgf.apiprobe.core.matchers.format import is_iso8601_z, is_uuid
from mgf.apiprobe.core.matchers.semantic import is_e164, is_iban, is_imei
from mgf.apiprobe.factories.builtins import (
    _luhn_check_digit,
    e164,
    iban,
    imei,
    iso8601_z,
    uuid,
)


class TestUuid:
    def test_passes_is_uuid(self) -> None:
        for _ in range(20):
            assert is_uuid()(uuid())

    def test_canonical_shape(self) -> None:
        # 8-4-4-4-12 hex with dashes.
        pattern = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
        assert pattern.match(uuid()) is not None

    def test_uniqueness(self) -> None:
        # Astronomically unlikely to collide across 100 v4 UUIDs.
        assert len({uuid() for _ in range(100)}) == 100


class TestIso8601Z:
    def test_passes_is_iso8601_z(self) -> None:
        assert is_iso8601_z()(iso8601_z())

    def test_default_is_now_utc(self) -> None:
        before = datetime.now(tz=UTC)
        s = iso8601_z()
        after = datetime.now(tz=UTC)
        # Parse the produced timestamp and confirm it's between before/after.
        parsed = datetime.fromisoformat(s.replace("Z", "+00:00"))
        assert before <= parsed <= after

    def test_explicit_when(self) -> None:
        moment = datetime(2026, 4, 29, 12, 0, 0, tzinfo=UTC)
        assert iso8601_z(when=moment) == "2026-04-29T12:00:00Z"

    def test_naive_datetime_treated_as_utc(self) -> None:
        # Naive datetime is "best effort" coerced to UTC rather than
        # producing a never-passes-the-matcher value.
        naive = datetime(2026, 4, 29, 12, 0, 0)  # noqa: DTZ001 — testing the naive path
        s = iso8601_z(when=naive)
        assert is_iso8601_z()(s)
        assert s.endswith("Z")


class TestIban:
    @pytest.mark.parametrize("country", ["DE", "GB", "FR", "ES", "IT", "NL"])
    def test_passes_is_iban_for_each_supported_country(self, country: str) -> None:
        for _ in range(5):
            generated = iban(country=country)
            assert generated.startswith(country)
            assert is_iban()(generated)

    def test_lowercase_country_normalised(self) -> None:
        assert iban(country="de").startswith("DE")
        assert is_iban()(iban(country="de"))

    def test_unsupported_country_raises(self) -> None:
        with pytest.raises(ValueError, match="unsupported country"):
            iban(country="ZZ")

    def test_default_is_de(self) -> None:
        assert iban().startswith("DE")


class TestImei:
    def test_passes_is_imei(self) -> None:
        for _ in range(20):
            assert is_imei()(imei())

    def test_length(self) -> None:
        assert len(imei()) == 15

    def test_all_digits(self) -> None:
        assert imei().isdigit()

    def test_uniqueness(self) -> None:
        assert len({imei() for _ in range(100)}) == 100


class TestE164:
    def test_passes_is_e164(self) -> None:
        for _ in range(20):
            assert is_e164()(e164())

    def test_default_country_code_1(self) -> None:
        result = e164()
        assert result.startswith("+1")
        assert len(result) == 12  # +1 + 10 digits

    def test_custom_country_code(self) -> None:
        result = e164(country_code=44, length=10)
        assert result.startswith("+44")
        assert is_e164()(result)

    def test_three_digit_country_code(self) -> None:
        # Saudi Arabia (966).
        result = e164(country_code=966, length=9)
        assert result.startswith("+966")
        assert is_e164()(result)

    @pytest.mark.parametrize("bad_cc", [0, -1, 1000])
    def test_country_code_bounds(self, bad_cc: int) -> None:
        with pytest.raises(ValueError, match="country_code"):
            e164(country_code=bad_cc)

    @pytest.mark.parametrize("bad_len", [0, -1])
    def test_length_must_be_positive(self, bad_len: int) -> None:
        with pytest.raises(ValueError, match="length"):
            e164(length=bad_len)

    def test_total_length_capped_at_15(self) -> None:
        # 3-digit country code + 13-digit subscriber = 16 total → reject.
        with pytest.raises(ValueError, match=r"> 15"):
            e164(country_code=999, length=13)


class TestLuhnHelper:
    @pytest.mark.parametrize(
        ("digits", "expected_check"),
        [
            # Canonical Luhn examples — these include the check digit
            # at the end, so we strip it and verify _luhn_check_digit
            # would produce it.
            ("7992739871", 3),  # commonly cited Luhn example
            ("49015420323751", 8),  # IMEI body from a known-valid IMEI
        ],
    )
    def test_known_check_digits(self, digits: str, expected_check: int) -> None:
        assert _luhn_check_digit(digits) == expected_check
