"""Property-based tests using Hypothesis for robust edge case discovery.

This module demonstrates property-based testing best practices using the Hypothesis library.
Property-based testing generates random inputs to discover edge cases that might be missed
in traditional example-based testing.

Key concepts demonstrated:
- String generation strategies for testing text processing functions
- Unicode handling and edge cases
- Property testing patterns that complement example-based tests
"""

from contextlib import redirect_stdout
from io import StringIO

import pytest
from hypothesis import given
from hypothesis import strategies as st

from augint_library import print_hi


@pytest.mark.unit
@given(st.text())
def test_print_hi_handles_any_string(name: str) -> None:
    """Property: print_hi should handle any string input without errors.

    This property test verifies that print_hi is robust against:
    - Empty strings
    - Unicode characters
    - Very long strings
    - Special characters and whitespace
    - Control characters

    Args:
        name: Randomly generated string from Hypothesis
    """
    # The function should never raise an exception
    try:
        # Capture output to verify it produces some result
        output = StringIO()
        with redirect_stdout(output):
            print_hi(name)

        # Basic invariant: should always produce output
        result = output.getvalue()
        assert isinstance(result, str)
        assert "Hi " in result
        assert name in result

    except Exception as e:
        pytest.fail(f"print_hi raised {type(e).__name__}: {e} for input: {name!r}")


@pytest.mark.unit
@given(
    st.text(
        alphabet=st.characters(min_codepoint=33, max_codepoint=126),
        min_size=1,
        max_size=20,
    )
)
def test_print_hi_output_format(name: str) -> None:
    """Property: print_hi output should always follow expected format.

    Args:
        name: Non-empty string with printable ASCII (no spaces or control chars)
    """
    output = StringIO()
    with redirect_stdout(output):
        print_hi(name)

    result = output.getvalue()

    # The output should be exactly "Hi {name}\n"
    expected = f"Hi {name}\n"
    assert result == expected


@pytest.mark.unit
@given(
    st.text(
        alphabet=st.characters(whitelist_categories=("Lu", "Ll", "Lt", "Lm", "Lo")),
        min_size=1,
        max_size=20,
    )
)
def test_print_hi_unicode_letters(name: str) -> None:
    """Property: print_hi should handle Unicode letter characters correctly.

    Tests with various Unicode letter categories:
    - Lu: Uppercase letters
    - Ll: Lowercase letters
    - Lt: Titlecase letters
    - Lm: Modifier letters
    - Lo: Other letters (e.g., Chinese, Arabic)

    Args:
        name: String containing only Unicode letters
    """
    output = StringIO()
    with redirect_stdout(output):
        print_hi(name)

    result = output.getvalue().strip()
    assert result == f"Hi {name}"


@pytest.mark.unit
@given(
    st.text(
        alphabet=st.characters(min_codepoint=33, max_codepoint=126),  # Printable ASCII, no space
        min_size=1,
        max_size=50,
    )
)
def test_print_hi_ascii_range(name: str) -> None:
    """Property: print_hi should handle printable ASCII character range robustly.

    Args:
        name: String with printable ASCII characters (no spaces)
    """
    output = StringIO()
    with redirect_stdout(output):
        print_hi(name)

    result = output.getvalue()
    expected = f"Hi {name}\n"
    assert result == expected


@pytest.mark.unit
@given(
    st.text(
        alphabet=st.characters(whitelist_categories=("Lu", "Ll", "Nd")),
        min_size=1,
        max_size=20,
    )
)
def test_print_hi_alphanumeric_names(name: str) -> None:
    """Property: print_hi works correctly with alphanumeric names.

    Args:
        name: String containing letters and numbers only
    """
    output = StringIO()
    with redirect_stdout(output):
        print_hi(name)

    result = output.getvalue()
    expected = f"Hi {name}\n"
    assert result == expected


@pytest.mark.unit
@given(
    st.lists(
        st.text(
            alphabet=st.characters(min_codepoint=33, max_codepoint=126),
            min_size=1,
            max_size=8,
        ),
        min_size=1,
        max_size=3,
    )
)
def test_print_hi_multiple_calls(names: list[str]) -> None:
    """Property: print_hi should work correctly when called multiple times.

    Args:
        names: List of simple names to test in sequence
    """
    outputs = []

    for name in names:
        output = StringIO()
        with redirect_stdout(output):
            print_hi(name)
        outputs.append(output.getvalue())

    # Each output should be independent and correct
    for i, name in enumerate(names):
        expected = f"Hi {name}\n"
        assert outputs[i] == expected

    # All outputs should be different if names are different
    if len(set(names)) == len(names):  # All names unique
        assert len(set(outputs)) == len(outputs)  # All outputs unique
