import functools
import inspect
import warnings
from typing import Callable, Optional, Type, TypeVar, Union, cast

from zyplib.utils.print import print_error, warning

from .time import print_elapsed

__all__ = ['deprecated', 'auto_try_catch', 'print_elapsed']

T = TypeVar('T')


def deprecated(
    _obj: Optional[T] = None, *, message: Optional[str] = None
) -> Union[Callable[[T], T], T]:
    """标记函数或类为已弃用的装饰器

    当被装饰的函数被调用或类被实例化时，会发出一个 `DeprecationWarning`。

    Parameters
    ----------
    - `_obj` : `Optional[T]`, optional
        - 要装饰的函数或类。当装饰器带参数使用时，此参数为 `None`, by default `None`
    - `message` : `Optional[str]`, optional
        - 附加的弃用警告信息，通常用于指示替代方案, by default `None`

    Returns
    ----------
    - `Union[Callable[[T], T], T]`
        - 返回装饰器或被装饰的对象。

    Examples
    ----------
    ```python
    # 装饰函数
    @deprecated(message='使用 new_function 替代')
    def old_function():
        pass

    # 装饰类
    @deprecated(message='使用 NewClass 替代')
    class OldClass:
        pass

    # 无参数用法
    @deprecated
    def another_old_function():
        pass
    ```
    """

    def decorator(obj: T) -> T:
        if inspect.isclass(obj):
            cls = cast(Type, obj)
            original_init = cls.__init__

            @functools.wraps(original_init)
            def new_init(self, *args, **kwargs):
                warn_msg = f'类 {cls.__name__} 已弃用'
                if message:
                    warn_msg += f': {message}'
                warning(warn_msg, category=DeprecationWarning, stacklevel=2)
                original_init(self, *args, **kwargs)

            cls.__init__ = new_init
            return obj
        else:
            func = cast(Callable, obj)

            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                warn_msg = f'函数 {func.__name__} 已弃用'
                if message:
                    warn_msg += f': {message}'
                warnings.warn(warn_msg, category=DeprecationWarning, stacklevel=2)
                return func(*args, **kwargs)

            return cast(T, wrapper)

    if _obj is None:
        # @deprecated(message=...)
        return decorator
    else:
        # @deprecated
        return decorator(_obj)


def auto_try_catch(
    func=None,
    *,
    exceptions_to_catch=Exception,
    on_exception_return=None,
    reraise=False,
    callback=None,
):
    """自动捕获并处理指定异常的装饰器。

    该装饰器提供了一套灵活的机制来统一函数的异常处理逻辑，
    避免重复编写 try...except 块。

    Parameters
    ----------
    - `func` : callable, optional
        - 被装饰的函数。当装饰器不带参数使用时，此为被装饰的函数对象。
    - `exceptions_to_catch` : type or tuple of types, optional
        - 需要捕获的异常类型，默认为 `Exception`。
    - `on_exception_return` : any, optional
        - 当异常发生且没有提供 `callback` 时，函数的默认返回值。默认为 `None`。
    - `reraise` : bool, optional
        - 是否在处理后重新抛出异常。如果为 `True`，异常将在执行 `callback`（如果提供）后被重新抛出，
          此时函数将不会有返回值。默认为 `False`。
    - `callback` : callable, optional
        - 异常发生时调用的回调函数。它的返回值将作为函数的最终返回值（除非 `reraise` 为 `True`）。
          该参数的优先级高于 `on_exception_return`。
          回调函数可以有以下几种形式：
          - `callback(exception, *args, **kwargs)`: 接收异常对象和原函数的所有参数。
          - `callback(exception)`: 只接收异常对象。
          - `callback()`: 无参数。

    Returns
    -------
    - `any`
        - 根据执行情况返回以下值之一：
          - 正常执行时，返回原函数的返回值。
          - 异常发生时：
            - 如果 `reraise` 为 `True`，则重新抛出异常。
            - 如果提供了 `callback`，返回 `callback` 的返回值。
            - 否则，返回 `on_exception_return` 的值。

    Examples
    --------
    ```python
    # 示例1: 失败时返回默认值
    @auto_try_catch(exceptions_to_catch=ValueError, on_exception_return=-1)
    def parse_int(s):
        return int(s)

    result = parse_int("abc")  # result will be -1

    # 示例2: 使用回调函数动态处理
    def handle_error(e, value):
        print(f"Failed to process '{value}' due to: {e}")
        return None

    @auto_try_catch(callback=handle_error)
    def process_data(value):
        # ... some logic that might fail ...
        return value * 2

    process_data("bad_data") # Prints message and returns None

    # 示例3: 记录日志后重新抛出异常
    @auto_try_catch(reraise=True, callback=lambda e: print(f"Error logged: {e}"))
    def critical_operation():
        raise RuntimeError("Something went wrong")

    try:
        critical_operation()
    except RuntimeError as e:
        print("Caught reraised exception") # This will be printed
    ```
    """
    if func is None:
        return functools.partial(
            auto_try_catch,
            exceptions_to_catch=exceptions_to_catch,
            on_exception_return=on_exception_return,
            reraise=reraise,
            callback=callback,
        )

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except exceptions_to_catch as e:
            print_error(f'Exception caught in {func.__name__}: {e}')

            # 默认返回 on_exception_return 的值
            result = on_exception_return

            # 如果提供了 callback，它的返回值具有更高优先级
            if callback is not None:
                sig = inspect.signature(callback)
                if len(sig.parameters) == 0:
                    result = callback()
                elif len(sig.parameters) == 1:
                    result = callback(e)
                else:
                    result = callback(e, *args, **kwargs)

            # reraise 具有最高优先级，如果为 True，则重新抛出异常
            if reraise:
                raise e

            return result

    return wrapper
