from collections.abc import Callable
from contextlib import AsyncExitStack, asynccontextmanager, contextmanager
from enum import Enum
import inspect
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Literal,
    cast,
    get_args,
    get_origin,
)
from typing_extensions import Self, override

import anyio
from exceptiongroup import BaseExceptionGroup, catch
from pydantic.fields import FieldInfo as PydanticFieldInfo

from nonebot.compat import FieldInfo, ModelField, PydanticUndefined
from nonebot.consts import ARG_KEY, REJECT_PROMPT_RESULT_KEY
from nonebot.dependencies import Dependent, Param
from nonebot.dependencies.utils import check_field_type
from nonebot.exception import SkippedException
from nonebot.typing import (
    _STATE_FLAG,
    T_DependencyCache,
    T_Handler,
    T_State,
    origin_is_annotated,
)
from nonebot.utils import (
    generic_check_issubclass,
    get_name,
    is_async_gen_callable,
    is_coroutine_callable,
    is_gen_callable,
    run_sync,
    run_sync_ctx_manager,
)

if TYPE_CHECKING:
    from nonebot.adapters import Bot, Event, Message
    from nonebot.matcher import Matcher


class DependsInner:
    def __init__(
        self,
        dependency: T_Handler | None = None,
        *,
        use_cache: bool = True,
        validate: bool | PydanticFieldInfo = False,
    ) -> None:
        self.dependency = dependency
        self.use_cache = use_cache
        self.validate = validate

    def __repr__(self) -> str:
        dep = get_name(self.dependency)
        cache = "" if self.use_cache else ", use_cache=False"
        validate = f", validate={self.validate}" if self.validate else ""
        return f"DependsInner({dep}{cache}{validate})"


def Depends(
    dependency: T_Handler | None = None,
    *,
    use_cache: bool = True,
    validate: bool | PydanticFieldInfo = False,
) -> Any:
    """子依赖装饰器

    参数:
        dependency: 依赖函数。默认为参数的类型注释。
        use_cache: 是否使用缓存。默认为 `True`。
        validate: 是否使用 Pydantic 类型校验。默认为 `False`。

    用法:
        ```python
        def depend_func() -> Any:
            return ...

        def depend_gen_func():
            try:
                yield ...
            finally:
                ...

        async def handler(
            param_name: Any = Depends(depend_func),
            gen: Any = Depends(depend_gen_func),
        ):
            ...
        ```
    """
    return DependsInner(dependency, use_cache=use_cache, validate=validate)


class CacheState(str, Enum):
    """子依赖缓存状态"""

    PENDING = "PENDING"
    FINISHED = "FINISHED"


class DependencyCache:
    """子依赖结果缓存。

    用于缓存子依赖的结果，以避免重复计算。
    """

    def __init__(self):
        self._state = CacheState.PENDING
        self._result: Any = None
        self._exception: BaseException | None = None
        self._waiter = anyio.Event()

    def done(self) -> bool:
        return self._state == CacheState.FINISHED

    def result(self) -> Any:
        """获取子依赖结果"""

        if self._state != CacheState.FINISHED:
            raise RuntimeError("Result is not ready")

        if self._exception is not None:
            raise self._exception
        return self._result

    def exception(self) -> BaseException | None:
        """获取子依赖异常"""

        if self._state != CacheState.FINISHED:
            raise RuntimeError("Result is not ready")

        return self._exception

    def set_result(self, result: Any) -> None:
        """设置子依赖结果"""

        if self._state != CacheState.PENDING:
            raise RuntimeError(f"Cache state invalid: {self._state}")

        self._result = result
        self._state = CacheState.FINISHED
        self._waiter.set()

    def set_exception(self, exception: BaseException) -> None:
        """设置子依赖异常"""

        if self._state != CacheState.PENDING:
            raise RuntimeError(f"Cache state invalid: {self._state}")

        self._exception = exception
        self._state = CacheState.FINISHED
        self._waiter.set()

    async def wait(self):
        """等待子依赖结果"""
        await self._waiter.wait()
        if self._state != CacheState.FINISHED:
            raise RuntimeError("Invalid cache state")

        if self._exception is not None:
            raise self._exception

        return self._result


class DependParam(Param):
    """子依赖注入参数。

    本注入解析所有子依赖注入，然后将它们的返回值作为参数值传递给父依赖。

    本注入应该具有最高优先级，因此应该在其他参数之前检查。
    """

    def __init__(
        self, *args, dependent: Dependent[Any], use_cache: bool, **kwargs: Any
    ) -> None:
        super().__init__(*args, **kwargs)
        self.dependent = dependent
        self.use_cache = use_cache

    def __repr__(self) -> str:
        return f"Depends({self.dependent}, use_cache={self.use_cache})"

    @classmethod
    def _from_field(
        cls,
        sub_dependent: Dependent[Any],
        use_cache: bool,
        validate: bool | PydanticFieldInfo,
    ) -> Self:
        return cls._inherit_construct(
            validate if isinstance(validate, PydanticFieldInfo) else None,
            dependent=sub_dependent,
            use_cache=use_cache,
            validate=bool(validate),
        )

    @classmethod
    @override
    def _check_param(
        cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
    ) -> Self | None:
        type_annotation, depends_inner = param.annotation, None
        # extract type annotation and dependency from Annotated
        if get_origin(param.annotation) is Annotated:
            type_annotation, *extra_args = get_args(param.annotation)
            depends_inner = next(
                (x for x in reversed(extra_args) if isinstance(x, DependsInner)), None
            )

        # param default value takes higher priority
        depends_inner = (
            param.default if isinstance(param.default, DependsInner) else depends_inner
        )
        # not a dependent
        if depends_inner is None:
            return

        dependency: T_Handler
        # sub dependency is not specified, use type annotation
        if depends_inner.dependency is None:
            assert type_annotation is not inspect.Signature.empty, (
                "Dependency cannot be empty"
            )
            dependency = type_annotation
        else:
            dependency = depends_inner.dependency
        # parse sub dependency
        sub_dependent = Dependent[Any].parse(
            call=dependency,
            allow_types=allow_types,
        )

        return cls._from_field(
            sub_dependent, depends_inner.use_cache, depends_inner.validate
        )

    @classmethod
    @override
    def _check_parameterless(
        cls, value: Any, allow_types: tuple[type[Param], ...]
    ) -> "Param | None":
        if isinstance(value, DependsInner):
            assert value.dependency, "Dependency cannot be empty"
            dependent = Dependent[Any].parse(
                call=value.dependency, allow_types=allow_types
            )
            return cls._from_field(dependent, value.use_cache, value.validate)

    @override
    async def _solve(
        self,
        stack: AsyncExitStack | None = None,
        dependency_cache: T_DependencyCache | None = None,
        **kwargs: Any,
    ) -> Any:
        use_cache: bool = self.use_cache
        dependency_cache = {} if dependency_cache is None else dependency_cache

        sub_dependent = self.dependent
        call = cast(Callable[..., Any], sub_dependent.call)

        # solve sub dependency with current cache
        exc: BaseExceptionGroup[SkippedException] | None = None

        def _handle_skipped(exc_group: BaseExceptionGroup[SkippedException]):
            nonlocal exc
            exc = exc_group

        with catch({SkippedException: _handle_skipped}):
            sub_values = await sub_dependent.solve(
                stack=stack,
                dependency_cache=dependency_cache,
                **kwargs,
            )

        if exc is not None:
            raise exc

        # run dependency function
        if use_cache and call in dependency_cache:
            return await dependency_cache[call].wait()

        if is_gen_callable(call) or is_async_gen_callable(call):
            assert isinstance(stack, AsyncExitStack), (
                "Generator dependency should be called in context"
            )
            if is_gen_callable(call):
                cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
            else:
                cm = asynccontextmanager(call)(**sub_values)

            target = stack.enter_async_context(cm)
        elif is_coroutine_callable(call):
            target = call(**sub_values)
        else:
            target = run_sync(call)(**sub_values)

        dependency_cache[call] = cache = DependencyCache()
        try:
            result = await target
        except Exception as e:
            cache.set_exception(e)
            raise
        except BaseException as e:
            cache.set_exception(e)
            # remove cache when base exception occurs
            # e.g. CancelledError
            dependency_cache.pop(call, None)
            raise
        else:
            cache.set_result(result)
            return result

    @override
    async def _check(self, **kwargs: Any) -> None:
        # run sub dependent pre-checkers
        await self.dependent.check(**kwargs)


class BotParam(Param):
    """{ref}`nonebot.adapters.Bot` 注入参数。

    本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Bot` 及其子类或 `None` 的参数。

    为保证兼容性，本注入还会解析名为 `bot` 且没有类型注解的参数。
    """

    def __init__(self, *args, checker: ModelField | None = None, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self.checker = checker

    def __repr__(self) -> str:
        return (
            "BotParam("
            + (repr(self.checker.annotation) if self.checker is not None else "")
            + ")"
        )

    @classmethod
    @override
    def _check_param(
        cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
    ) -> Self | None:
        from nonebot.adapters import Bot

        # param type is Bot(s) or subclass(es) of Bot or None
        if generic_check_issubclass(param.annotation, Bot):
            checker: ModelField | None = None
            if param.annotation is not Bot:
                checker = ModelField.construct(
                    name=param.name, annotation=param.annotation, field_info=FieldInfo()
                )
            return cls(checker=checker)
        # legacy: param is named "bot" and has no type annotation
        elif param.annotation == param.empty and param.name == "bot":
            return cls()

    @override
    async def _solve(  # pyright: ignore[reportIncompatibleMethodOverride]
        self, bot: "Bot", **kwargs: Any
    ) -> Any:
        return bot

    @override
    async def _check(  # pyright: ignore[reportIncompatibleMethodOverride]
        self, bot: "Bot", **kwargs: Any
    ) -> None:
        if self.checker is not None:
            check_field_type(self.checker, bot)


class EventParam(Param):
    """{ref}`nonebot.adapters.Event` 注入参数

    本注入解析所有类型为且仅为 {ref}`nonebot.adapters.Event` 及其子类或 `None` 的参数。

    为保证兼容性，本注入还会解析名为 `event` 且没有类型注解的参数。
    """

    def __init__(self, *args, checker: ModelField | None = None, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self.checker = checker

    def __repr__(self) -> str:
        return (
            "EventParam("
            + (repr(self.checker.annotation) if self.checker is not None else "")
            + ")"
        )

    @classmethod
    @override
    def _check_param(
        cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
    ) -> Self | None:
        from nonebot.adapters import Event

        # param type is Event(s) or subclass(es) of Event or None
        if generic_check_issubclass(param.annotation, Event):
            checker: ModelField | None = None
            if param.annotation is not Event:
                checker = ModelField.construct(
                    name=param.name, annotation=param.annotation, field_info=FieldInfo()
                )
            return cls(checker=checker)
        # legacy: param is named "event" and has no type annotation
        elif param.annotation == param.empty and param.name == "event":
            return cls()

    @override
    async def _solve(  # pyright: ignore[reportIncompatibleMethodOverride]
        self, event: "Event", **kwargs: Any
    ) -> Any:
        return event

    @override
    async def _check(  # pyright: ignore[reportIncompatibleMethodOverride]
        self, event: "Event", **kwargs: Any
    ) -> Any:
        if self.checker is not None:
            check_field_type(self.checker, event)


class StateParam(Param):
    """事件处理状态注入参数

    本注入解析所有类型为 `T_State` 的参数。

    为保证兼容性，本注入还会解析名为 `state` 且没有类型注解的参数。
    """

    def __repr__(self) -> str:
        return "StateParam()"

    @classmethod
    @override
    def _check_param(
        cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
    ) -> Self | None:
        # param type is T_State
        if origin_is_annotated(
            get_origin(param.annotation)
        ) and _STATE_FLAG in get_args(param.annotation):
            return cls()
        # legacy: param is named "state" and has no type annotation
        elif param.annotation == param.empty and param.name == "state":
            return cls()

    @override
    async def _solve(  # pyright: ignore[reportIncompatibleMethodOverride]
        self, state: T_State, **kwargs: Any
    ) -> Any:
        return state


class MatcherParam(Param):
    """事件响应器实例注入参数

    本注入解析所有类型为且仅为 {ref}`nonebot.matcher.Matcher` 及其子类或 `None` 的参数。

    为保证兼容性，本注入还会解析名为 `matcher` 且没有类型注解的参数。
    """

    def __init__(self, *args, checker: ModelField | None = None, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self.checker = checker

    def __repr__(self) -> str:
        return (
            "MatcherParam("
            + (repr(self.checker.annotation) if self.checker is not None else "")
            + ")"
        )

    @classmethod
    @override
    def _check_param(
        cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
    ) -> Self | None:
        from nonebot.matcher import Matcher

        # param type is Matcher(s) or subclass(es) of Matcher or None
        if generic_check_issubclass(param.annotation, Matcher):
            checker: ModelField | None = None
            if param.annotation is not Matcher:
                checker = ModelField.construct(
                    name=param.name, annotation=param.annotation, field_info=FieldInfo()
                )
            return cls(checker=checker)
        # legacy: param is named "matcher" and has no type annotation
        elif param.annotation == param.empty and param.name == "matcher":
            return cls()

    @override
    async def _solve(  # pyright: ignore[reportIncompatibleMethodOverride]
        self, matcher: "Matcher", **kwargs: Any
    ) -> Any:
        return matcher

    @override
    async def _check(  # pyright: ignore[reportIncompatibleMethodOverride]
        self, matcher: "Matcher", **kwargs: Any
    ) -> Any:
        if self.checker is not None:
            check_field_type(self.checker, matcher)


class ArgInner:
    def __init__(
        self, key: str | None, type: Literal["message", "str", "plaintext", "prompt"]
    ) -> None:
        self.key: str | None = key
        self.type: Literal["message", "str", "plaintext", "prompt"] = type

    def __repr__(self) -> str:
        return f"ArgInner(key={self.key!r}, type={self.type!r})"


def Arg(key: str | None = None) -> Any:
    """Arg 参数消息"""
    return ArgInner(key, "message")


def ArgStr(key: str | None = None) -> str:
    """Arg 参数消息文本"""
    return ArgInner(key, "str")  # type: ignore


def ArgPlainText(key: str | None = None) -> str:
    """Arg 参数消息纯文本"""
    return ArgInner(key, "plaintext")  # type: ignore


def ArgPromptResult(key: str | None = None) -> Any:
    """`arg` prompt 发送结果"""
    return ArgInner(key, "prompt")


class ArgParam(Param):
    """Arg 注入参数

    本注入解析事件响应器操作 `got` 所获取的参数。

    可以通过 `Arg`、`ArgStr`、`ArgPlainText` 等函数参数 `key` 指定获取的参数，
    留空则会根据参数名称获取。
    """

    def __init__(
        self,
        *args,
        key: str,
        type: Literal["message", "str", "plaintext", "prompt"],
        **kwargs: Any,
    ) -> None:
        super().__init__(*args, **kwargs)
        self.key = key
        self.type = type

    def __repr__(self) -> str:
        return f"ArgParam(key={self.key!r}, type={self.type!r})"

    @classmethod
    @override
    def _check_param(
        cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
    ) -> Self | None:
        if isinstance(param.default, ArgInner):
            return cls(key=param.default.key or param.name, type=param.default.type)
        elif get_origin(param.annotation) is Annotated:
            for arg in get_args(param.annotation)[:0:-1]:
                if isinstance(arg, ArgInner):
                    return cls(key=arg.key or param.name, type=arg.type)

    async def _solve(  # pyright: ignore[reportIncompatibleMethodOverride]
        self, matcher: "Matcher", **kwargs: Any
    ) -> Any:
        if self.type == "message":
            return self._solve_message(matcher)
        elif self.type == "str":
            return self._solve_str(matcher)
        elif self.type == "plaintext":
            return self._solve_plaintext(matcher)
        elif self.type == "prompt":
            return self._solve_prompt(matcher)
        else:
            raise ValueError(f"Unknown Arg type: {self.type}")

    def _solve_message(self, matcher: "Matcher") -> "Message | None":
        return matcher.get_arg(self.key)

    def _solve_str(self, matcher: "Matcher") -> str | None:
        message = matcher.get_arg(self.key)
        return str(message) if message is not None else None

    def _solve_plaintext(self, matcher: "Matcher") -> str | None:
        message = matcher.get_arg(self.key)
        return message.extract_plain_text() if message is not None else None

    def _solve_prompt(self, matcher: "Matcher") -> Any | None:
        return matcher.state.get(
            REJECT_PROMPT_RESULT_KEY.format(key=ARG_KEY.format(key=self.key))
        )


class ExceptionParam(Param):
    """{ref}`nonebot.message.run_postprocessor` 的异常注入参数

    本注入解析所有类型为 `Exception` 或 `None` 的参数。

    为保证兼容性，本注入还会解析名为 `exception` 且没有类型注解的参数。
    """

    def __repr__(self) -> str:
        return "ExceptionParam()"

    @classmethod
    @override
    def _check_param(
        cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
    ) -> Self | None:
        # param type is Exception(s) or subclass(es) of Exception or None
        if generic_check_issubclass(param.annotation, Exception):
            return cls()
        # legacy: param is named "exception" and has no type annotation
        elif param.annotation == param.empty and param.name == "exception":
            return cls()

    @override
    async def _solve(self, exception: Exception | None = None, **kwargs: Any) -> Any:
        return exception


class DefaultParam(Param):
    """默认值注入参数

    本注入解析所有剩余未能解析且具有默认值的参数。

    本注入参数应该具有最低优先级，因此应该在所有其他注入参数之后使用。
    """

    def __repr__(self) -> str:
        return f"DefaultParam(default={self.default!r})"

    @classmethod
    @override
    def _check_param(
        cls, param: inspect.Parameter, allow_types: tuple[type[Param], ...]
    ) -> Self | None:
        if param.default != param.empty:
            return cls(default=param.default)

    @override
    async def _solve(self, **kwargs: Any) -> Any:
        return PydanticUndefined


__autodoc__ = {
    "DependsInner": False,
    "StateInner": False,
    "ArgInner": False,
}
