"""
Pytest tests you can use in your package to test some Z0Z_tools functions.

Each function in this module returns a list of test functions that can be used with `pytest.parametrize`.
"""

from Z0Z_tools import defineConcurrencyLimit, intInnit, oopsieKwargsie
from typing import Any, Callable, Iterable, List, Optional, Union, Type, Tuple
from unittest.mock import patch
import pytest

def PytestFor_defineConcurrencyLimit(callableToTest: Callable[[Union[bool, float, int, None]], int] = defineConcurrencyLimit, cpuCount: int = 8) -> List[Tuple[str, Callable[[], None]]]:
    """Returns a list of test functions to validate concurrency limit behavior.

    This function provides a comprehensive test suite for validating concurrency limit parsing
    and computation, checking both valid and invalid input scenarios.

    Parameters
    ----------
    callableToTest (defineConcurrencyLimit):
        The function to test, which should accept various input types and return an integer
        representing the concurrency limit. Defaults to defineConcurrencyLimit.
    cpuCount (8):
        The number of CPUs to simulate in the test environment.

    Returns
    -------
    listOfTestFunctions:
        A list of tuples, each containing:
        - A string describing the test case
        - A callable test function that implements the test case

    Test Cases
    ----------
    - Default values (None, False, 0)
    - Direct integer inputs
    - Fractional float inputs
    - Minimum value enforcement
    - Boolean True variants
    - Invalid string inputs
    - String number parsing

    Examples
    --------
    Run each test on `Z0Z_tools.defineConcurrencyLimit`
    ```
    from Z0Z_tools.pytest_parseParameters import PytestFor_concurrencyLimit

    listOfTests = PytestFor_concurrencyLimit()
    for nameOfTest, callablePytest in listOfTests:
        callablePytest()
    ```

    Or, run each test on your function, '`functionLocal`', that has a compatible signature
    ```
    from Z0Z_tools.pytest_parseParameters import PytestFor_defineConcurrencyLimit
    from packageLocal import functionLocal as YOUR_FUNCTION_HERE

    @pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_defineConcurrencyLimit(callableToTest = YOUR_FUNCTION_HERE))
    def test_functionLocal(nameOfTest, callablePytest):
        callablePytest()

    """
    @patch('multiprocessing.cpu_count', return_value=cpuCount)
    def testDefaults(_mockCpu):
        for limitParameter in [None, False, 0]:
            assert callableToTest(limitParameter) == cpuCount

    @patch('multiprocessing.cpu_count', return_value=cpuCount)
    def testDirectIntegers(_mockCpu):
        for limitParameter in [1, 4, 16]:
            assert callableToTest(limitParameter) == limitParameter

    @patch('multiprocessing.cpu_count', return_value=cpuCount)
    def testFractionalFloats(_mockCpu):
        testCases = {
            0.5: cpuCount // 2,
            0.25: cpuCount // 4,
            0.75: int(cpuCount * 0.75)
        }
        for input, expected in testCases.items():
            assert callableToTest(input) == expected

    @patch('multiprocessing.cpu_count', return_value=cpuCount)
    def testMinimumOne(_mockCpu):
        for limitParameter in [-10, -0.99, 0.1]:
            assert callableToTest(limitParameter) >= 1

    @patch('multiprocessing.cpu_count', return_value=cpuCount)
    def testBooleanTrue(_mockCpu):
        assert callableToTest(True) == 1
        assert callableToTest('True') == 1 # type: ignore
        assert callableToTest('TRUE') == 1 # type: ignore
        assert callableToTest(' true ') == 1 # type: ignore

    @patch('multiprocessing.cpu_count', return_value=cpuCount)
    def testInvalidStrings(_mockCpu):
        for stringInput in ["invalid", "True but not quite", "None of the above"]:
            with pytest.raises(ValueError, match="must be a number, True, False, or None"):
                callableToTest(stringInput) # type: ignore

    @patch('multiprocessing.cpu_count', return_value=cpuCount)
    def testStringNumbers(_mockCpu):
        testCases = [
            ("1.5", 1),
            ("-2.5", 6),
            ("4", 4),
            ("0.5", 4),
            ("-0.5", 4),
        ]
        for stringNumber, expectedLimit in testCases:
            assert callableToTest(stringNumber) == expectedLimit # type: ignore

    return [
        ('testDefaults', testDefaults),
        ('testDirectIntegers', testDirectIntegers),
        ('testFractionalFloats', testFractionalFloats),
        ('testMinimumOne', testMinimumOne),
        ('testBooleanTrue', testBooleanTrue),
        ('testInvalidStrings', testInvalidStrings),
        ('testStringNumbers', testStringNumbers)
    ]

def PytestFor_intInnit(callableToTest: Callable[[Iterable[int], Optional[str], Optional[Type[Any]]], List[int]] = intInnit) -> List[Tuple[str, Callable[[], None]]]:
    """Returns a list of test functions to validate integer initialization behavior.

    This function provides a comprehensive test suite for validating integer parsing
    and initialization, checking both valid and invalid input scenarios.

    Parameters
        callableToTest (intInnit): The function to test. Should accept:
            - A sequence of integer-compatible values
            - An optional parameter name string
            - An optional parameter type
            Returns a list of validated integers.

    Returns
        listOfTestFunctions: A list of tuples containing:
            - A string describing the test case
            - A callable test function implementing the test case

    Examples
        Run tests on `Z0Z_tools.intInnit`:
        ```python
        from Z0Z_tools.pytest_parseParameters import PytestFor_intInnit

        listOfTests = PytestFor_intInnit()
        for nameOfTest, callablePytest in listOfTests:
            callablePytest()
        ```

        Run tests on your compatible function:
        ```python
        from Z0Z_tools.pytest_parseParameters import PytestFor_intInnit
        from packageLocal import functionLocal as YOUR_FUNCTION_HERE

        @pytest.mark.parametrize("nameOfTest,callablePytest",
            PytestFor_intInnit(callableToTest=YOUR_FUNCTION_HERE))
        def test_functionLocal(nameOfTest, callablePytest):
            callablePytest()
        ```
    """
    def testHandlesValidIntegers():
        # Using Fibonacci numbers for distinct test values
        assert callableToTest([2, 3, 5, 8], 'test', None) == [2, 3, 5, 8]
        assert callableToTest([13.0, 21.0, 34.0], 'test', None) == [13, 21, 34] # type: ignore
        assert callableToTest(['55', '89', '144'], 'test', None) == [55, 89, 144] # type: ignore
        assert callableToTest([' 233 ', '377', '-610'], 'test', None) == [233, 377, -610] # type: ignore

    def testRejectsNonWholeNumbers():
        for invalidNumber in [13.7, '21.5', ' 34.8 ', -55.9]:
            with pytest.raises(ValueError):
                callableToTest([invalidNumber], 'test', None)

    def testRejectsBooleans():
        with pytest.raises(TypeError):
            callableToTest([True, False], 'test', None)

    def testRejectsInvalidStrings():
        for invalidString in ['NW', '', ' ', 'SE.SW']:
            with pytest.raises(ValueError):
                callableToTest([invalidString], 'test', None) # type: ignore

    def testRejectsEmptyList():
        with pytest.raises(ValueError):
            callableToTest([], 'test', None)

    def testHandlesMixedValidTypes():
        assert callableToTest([13, '21', 34.0], 'test', None) == [13, 21, 34] # type: ignore

    def testHandlesBytes():
        # Test valid byte sequences
        validCases = [
            ([b'123'], '123', [123]),  # Added larger numeric string bytes
            # ([b'\x00\x00\x00\x00\x00\x00\x00\xFF'], '255', [255])
        ]
        for inputData, testName, expected in validCases:
            assert callableToTest(inputData, testName, None) == expected  # type: ignore

        # Added new test for bigger sequences
        extendedCases = [
            ([b'123456789'], '123456789', [123456789]),  # Should decode and parse
            # ([b'999999999999999999'], 'even_bigger', [999999999999999999])  # Even bigger
        ]
        for inputData, testName, expected in extendedCases:
            assert callableToTest(inputData, testName, None) == expected  # type: ignore

        # New invalid case for b'\x00'
        invalidCases = [[b'\x00']]
        for inputData in invalidCases:
            with pytest.raises(ValueError):
                callableToTest(inputData, 'test', None) # type: ignore

    def testHandlesMemoryview():
        # Test valid memoryview objects
        validCases = [
            ([memoryview(b'123')], '123', [123]),  # Added memoryview numeric string
            # ([memoryview(b'\x00\x00\x00\x00\x00\x00\x00\xFF')], '255', [255])
        ]
        for inputData, testName, expected in validCases:
            assert callableToTest(inputData, testName, None) == expected # type: ignore

        largeMemoryviewCase = [memoryview(b'9999999999')]
        assert callableToTest(largeMemoryviewCase, 'test', None) == [9999999999]  # type: ignore

        # New invalid case for memoryview(b'\x00')
        invalidMemoryviewCases = [[memoryview(b'\x00')]]
        for inputData in invalidMemoryviewCases:
            with pytest.raises(ValueError):
                callableToTest(inputData, 'test', None) # type: ignore

    def testRejectsMutableSequence():
        class MutableList(list):
            def __iter__(self):
                self.append(89)
                return super().__iter__()
        with pytest.raises(RuntimeError, match=".*modified during iteration.*"):
            callableToTest(MutableList([13, 21, 34]), 'test', None)

    def testHandlesComplexIntegers():
        testCases = [
            ([13+0j], [13]),
            ([21+0j, 34+0j], [21, 34])
        ]
        for inputData, expected in testCases:
            assert callableToTest(inputData, 'test', None) == expected # type: ignore

    def testRejectsInvalidComplex():
        for invalidComplex in [13+1j, 21+0.5j, 34.5+0j]:
            with pytest.raises(ValueError):
                callableToTest([invalidComplex], 'test', None) # type: ignore

    return [
        ('testHandlesValidIntegers', testHandlesValidIntegers),
        ('testRejectsNonWholeNumbers', testRejectsNonWholeNumbers),
        ('testRejectsBooleans', testRejectsBooleans),
        ('testRejectsInvalidStrings', testRejectsInvalidStrings),
        ('testRejectsEmptyList', testRejectsEmptyList),
        ('testHandlesMixedValidTypes', testHandlesMixedValidTypes),
        ('testHandlesBytes', testHandlesBytes),
        ('testHandlesMemoryview', testHandlesMemoryview),
        ('testRejectsMutableSequence', testRejectsMutableSequence),
        ('testHandlesComplexIntegers', testHandlesComplexIntegers),
        ('testRejectsInvalidComplex', testRejectsInvalidComplex)
    ]

def PytestFor_oopsieKwargsie(callableToTest: Callable[[str], Union[bool, None, str]] = oopsieKwargsie) -> List[Tuple[str, Callable[[], None]]]:
    """Returns a list of test functions to validate string-to-boolean/None conversion behavior.

    This function provides a comprehensive test suite for validating string parsing and conversion
    to boolean or None values, with fallback to the original string when appropriate.

    Parameters
    ----------
    callableToTest (oopsieKwargsie):
        The function to test, which should accept a string and return either a boolean, None,
        or the original input. Defaults to oopsieKwargsie.

    Returns
    -------
    listOfTestFunctions:
        A list of tuples, each containing:
        - A string describing the test case
        - A callable test function that implements the test case

    Test Cases
    ----------
    - True string variants (case-insensitive)
    - False string variants (case-insensitive)
    - None string variants (case-insensitive)
    - Non-convertible strings (returned as-is)
    - Non-string object handling
        - Numbers (converted to strings)
        - Objects with failed str() conversion (returned as-is)

    Examples
    --------
    Run each test on `Z0Z_tools.oopsieKwargsie`
    ```
    from Z0Z_tools.pytest_parseParameters import PytestFor_oopsieKwargsie

    listOfTests = PytestFor_oopsieKwargsie()
    for nameOfTest, callablePytest in listOfTests:
        callablePytest()
    ```

    Or, run each test on your function, '`functionLocal`', that has a compatible signature
    ```
    from Z0Z_tools.pytest_parseParameters import PytestFor_oopsieKwargsie
    from packageLocal import functionLocal as YOUR_FUNCTION_HERE

    @pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_oopsieKwargsie(callableToTest = YOUR_FUNCTION_HERE))
    def test_functionLocal(nameOfTest, callablePytest):
        callablePytest()
    """
    def testHandlesTrueVariants():
        for variantTrue in ['True', 'TRUE', ' true ', 'TrUe']:
            assert callableToTest(variantTrue) is True

    def testHandlesFalseVariants():
        for variantFalse in ['False', 'FALSE', ' false ', 'FaLsE']:
            assert callableToTest(variantFalse) is False

    def testHandlesNoneVariants():
        for variantNone in ['None', 'NONE', ' none ', 'NoNe']:
            assert callableToTest(variantNone) is None

    def testReturnsOriginalString():
        for stringInput in ['hello', '123', 'True story', 'False alarm']:
            assert callableToTest(stringInput) == stringInput

    def testHandlesNonStringObjects():
        class UnStringable:
            def __str__(self):
                raise TypeError("Cannot be stringified")

        # This integer should get converted to string
        assert callableToTest(123) == "123" # type: ignore

        # This custom object should be returned as-is (same object) if str() fails
        unStringableObject = UnStringable()
        result = callableToTest(unStringableObject) # type: ignore
        assert result is unStringableObject

    return [
        ('testHandlesTrueVariants', testHandlesTrueVariants),
        ('testHandlesFalseVariants', testHandlesFalseVariants),
        ('testHandlesNoneVariants', testHandlesNoneVariants),
        ('testReturnsOriginalString', testReturnsOriginalString),
        ('testHandlesNonStringObjects', testHandlesNonStringObjects)
    ]
