from __future__ import annotations

from typing import Callable, TypeVar

from speclike.dispatch.mod import _MARK_FOR_DISPATCHING

T = TypeVar("T")

def setup(fn: Callable[[], T]) -> T:
    """Marker for an operation that creates the state required for a test."""
    try:
        return fn()
    except Exception as e:
        message =\
            "Setup function failed: " +\
            f"{str(fn.__name__)} raises error -> "+\
            f"{e.__class__.__name__}: {e}"
        assert False, message

def invariant(fn: Callable[[], bool]) -> None:
    """Marker for an operation that checks an invariant condition."""
    try:
        fullfill = fn()
    except Exception as e:
        message =\
            "Invariant condition checker failed: " +\
            f"{str(fn.__name__)} raises error -> "+\
            f"{e.__class__.__name__}: {e}"
        assert False, message
    
    if not fullfill:
        assert False, "Invariant condition not established."

def before(fn: Callable[[], bool]) -> None:
    """Marker for an operation that checks a precondition."""
    try:
        fullfill = fn()
    except Exception as e:
        message =\
            "Before condition checker failed: " +\
            f"{str(fn.__name__)} raises error -> "+\
            f"{e.__class__.__name__}: {e}"
        assert False, message
    
    if not fullfill:
        assert False, "Before condition not established."

def after(fn: Callable[[], bool]) -> None:
    """Marker for an operation that checks a postcondition."""
    try:
        fullfill = fn()
    except Exception as e:
        message =\
            "After condition checker failed: " +\
            f"{str(fn.__name__)} raises error -> "+\
            f"{e.__class__.__name__}: {e}"
        assert False, message
    
    if not fullfill:
        assert False, "After condition not established."


def spec(method):
    """Method decorator to mark a function to be executed as a test.

    Methods marked with this decorator are recognized and executed as tests.  
    The generated test name will appear as ``test_<method-name>``.

    Unlike ``pytest.mark.customtest``, this decorator allows the invocation
    signature of the test method to be customized not through pytest hooks,
    but by overriding :meth:`Test.dispatch` or :meth:`Test.dispatch_async`.

    Raises:
        TypeError: If the method name starts with ``test`` or ``_test``.  
        TypeError: If the method name is not a valid Python identifier.
    """
    method_name = str(method.__name__)
    if not method_name.isidentifier():
        raise TypeError(
            f"Method name must be valid python identifier." +
            f"but recieves -> {method_name}"
        )
    if method_name.startswith("test") or method_name.startswith("_test"):
        raise TypeError(
            f"Method name must not start \"test\" or \"_test\"." +
            f"but recieves -> {method_name}"
        )
    setattr(method, _MARK_FOR_DISPATCHING, 0)
    return method
