"""
Suite 2 — Encoding Idempotence: Hypothesis property-based tests.

Verifies idempotence and consistency properties for the Accordance encoding
module (decode, decode_always, decode_hebrew, normalize_headword,
normalize_hebrew_headword).

Properties (10):
  P1  — decode never crashes on arbitrary bytes
  P2  — decode_always never crashes on arbitrary bytes
  P3  — decode_hebrew never crashes on arbitrary bytes
  P4  — decode_hebrew_always never crashes on arbitrary bytes
  P5  — normalize_headword is idempotent  f(f(x)) == f(x)
  P6  — normalize_hebrew_headword is idempotent  f(f(x)) == f(x)
  P7  — decode output contains no raw section-sign (0xA7) artifact
  P8  — decode_always output contains no raw section-sign artifact
  P9  — decode acts as identity on pure ASCII (no Greek/Hebrew triggers)
  P10 — decode_always(trench=True) never crashes on arbitrary bytes
"""

from hypothesis import given, strategies as st, settings

from accordance_parser.encoding import (
    decode,
    decode_always,
    decode_hebrew,
    decode_hebrew_always,
    normalize_headword,
    normalize_hebrew_headword,
    _GREEK_DIACRITIC_BYTES,
    _HEBREW_DIACRITIC_BYTES,
)

# ── Strategies ───────────────────────────────────────────────────────────

# Greek letter bytes: ASCII a-z, A-Z that map to Greek (excluding v/V which are
# special: final sigma) plus the diacritic bytes.
_greek_letter_set = {
    ord(c) for c in "abgdezqiklmncoprstufxyw"
    "ABGDEZHQIKLMNCOPRSTUFXYW"
}
_greek_letter_bytes = st.sampled_from(sorted(_greek_letter_set))
_greek_diacritic_bytes = st.sampled_from(sorted(_GREEK_DIACRITIC_BYTES))

# Hebrew letter bytes: lowercase consonants and uppercase final-form letters
_hebrew_letter_set = {
    ord(c) for c in "abgdhwzxj fklmnsopcq rvt"  # note: f=tet, x=chet
    "KMNPC"  # final forms
}
_hebrew_letter_bytes = st.sampled_from(sorted(_hebrew_letter_set))
_hebrew_diacritic_bytes = st.sampled_from(sorted(_HEBREW_DIACRITIC_BYTES))
_hebrew_vowel_bytes = st.sampled_from([ord(c) for c in "AEIOU"])

# Arbitrary bytes (full 0-255 range)
arbitrary_bytes = st.binary(max_size=200)

# Greek-like data: mixes ASCII Greek letters with 8-bit diacritics
greek_data = st.binary(
    min_size=1, max_size=80
).filter(
    lambda b: any(x in _GREEK_DIACRITIC_BYTES for x in b)
)

# Hebrew-like data: mixes Hebrew consonant letters with diacritics or vowel markers
hebrew_data = st.binary(
    min_size=1, max_size=80
).filter(
    lambda b: any(
        x in _HEBREW_DIACRITIC_BYTES or x in {0x41, 0x45, 0x49, 0x4F, 0x55}
        for x in b
    )
)

# Pure ASCII: printable ASCII, no Beta Code accent triggers (/ or \) that would
# cause Greek span detection.
_pure_ascii_alphabet = bytes(
    b for b in range(0x20, 0x7F)
    if b not in (0x2F, 0x5C, 0x7B, 0x7D)  # exclude /, \, {, }
)
pure_ascii = st.binary(
    min_size=0, max_size=80
).filter(
    lambda b: all(0x20 <= x < 0x7F and x not in (0x2F, 0x5C, 0x7B, 0x7D) for x in b)
)

# Text strings for normalization idempotence checks.
# We generate from Python's full text range but bounded to keep tests fast.
any_text = st.text(max_size=200)


# ── P1: decode never crashes ─────────────────────────────────────────────

@given(arbitrary_bytes)
@settings(max_examples=500, deadline=2000)
def test_decode_never_crashes(data: bytes) -> None:
    """decode(data) always returns a str, never raises."""
    result = decode(data)
    assert isinstance(result, str)


# ── P2: decode_always never crashes ──────────────────────────────────────

@given(arbitrary_bytes)
@settings(max_examples=500, deadline=2000)
def test_decode_always_never_crashes(data: bytes) -> None:
    """decode_always(data) always returns a str, never raises."""
    result = decode_always(data)
    assert isinstance(result, str)


# ── P3: decode_hebrew never crashes ──────────────────────────────────────

@given(arbitrary_bytes)
@settings(max_examples=500, deadline=2000)
def test_decode_hebrew_never_crashes(data: bytes) -> None:
    """decode_hebrew(data) always returns a str, never raises."""
    result = decode_hebrew(data)
    assert isinstance(result, str)


# ── P4: decode_hebrew_always never crashes ───────────────────────────────

@given(arbitrary_bytes)
@settings(max_examples=500, deadline=2000)
def test_decode_hebrew_always_never_crashes(data: bytes) -> None:
    """decode_hebrew_always(data) always returns a str, never raises."""
    result = decode_hebrew_always(data)
    assert isinstance(result, str)


# ── P5: normalize_headword is idempotent ─────────────────────────────────

@given(any_text)
@settings(max_examples=500, deadline=2000)
def test_normalize_headword_idempotent(s: str) -> None:
    """f(f(x)) == f(x) for normalize_headword."""
    once = normalize_headword(s)
    twice = normalize_headword(once)
    assert once == twice, f"Idempotence failed:\n  once={once!r}\n twice={twice!r}"


# ── P6: normalize_hebrew_headword is idempotent ──────────────────────────

@given(any_text)
@settings(max_examples=500, deadline=2000)
def test_normalize_hebrew_headword_idempotent(s: str) -> None:
    """f(f(x)) == f(x) for normalize_hebrew_headword."""
    once = normalize_hebrew_headword(s)
    twice = normalize_hebrew_headword(once)
    assert once == twice, f"Idempotence failed:\n  once={once!r}\n twice={twice!r}"


# ── P7: decode output contains no raw § (0xA7 / U+00A7) artifact ────────

@given(arbitrary_bytes)
@settings(max_examples=500, deadline=2000)
def test_decode_output_no_section_sign(data: bytes) -> None:
    """decode() replaces all 0xA7 bytes with final sigma (U+03C2)."""
    result = decode(data)
    assert "\u00a7" not in result, (
        f"Raw section sign found in decode output: {result!r}"
    )


# ── P8: decode_always output contains no raw § artifact ──────────────────

@given(arbitrary_bytes)
@settings(max_examples=500, deadline=2000)
def test_decode_always_output_no_section_sign(data: bytes) -> None:
    """decode_always() replaces all 0xA7 bytes with final sigma (U+03C2)."""
    result = decode_always(data)
    assert "\u00a7" not in result, (
        f"Raw section sign found in decode_always output: {result!r}"
    )


# ── P9: decode acts as identity on pure ASCII (no diacritic triggers) ────

@given(pure_ascii)
@settings(max_examples=500, deadline=2000)
def test_decode_identity_on_pure_ascii(data: bytes) -> None:
    r"""Pure ASCII (no / or \ triggers) passes through decode as Latin-1."""
    result = decode(data)
    expected = data.decode("latin-1")
    assert result == expected, (
        f"decode({data!r}) = {result!r} != latin-1 decode {expected!r}"
    )


# ── P10: decode_always(trench=True) never crashes ────────────────────────

@given(arbitrary_bytes)
@settings(max_examples=500, deadline=2000)
def test_decode_always_trench_never_crashes(data: bytes) -> None:
    """decode_always(data, trench=True) always returns a str, never raises."""
    result = decode_always(data, trench=True)
    assert isinstance(result, str)
