# pylint: disable=invalid-name, function-redefined
from __future__ import annotations

from dataclasses import dataclass, field
import functools
from typing import (
  Any,
  Callable,
  Concatenate,
  Generic,
  Optional,
  ParamSpec,
  Self,
  TypeVar,
  cast,
  overload,
)

from pydantic import ConfigDict, SkipValidation, validate_call


S, P, Q, R = TypeVar('S'), ParamSpec('P'), ParamSpec('Q'), TypeVar('R')


_validate_int = validate_call(config=ConfigDict(
  validate_return=True,
  arbitrary_types_allowed=True
))


@dataclass
class expost(Generic[P, R]):

  deco: Callable[Callable[P, R], Callable[P, R]]

  @overload
  def __call__(self: Self, func: Callable[P, R]) -> expost_func: ...

  @_validate_int
  def __call__(self: Any, func: Callable[P, R]) -> Any:
    return functools.wraps(func)(expost_func(deco=self.deco, func=func))

  # class expost


@dataclass
class expost_func(expost[P, R]):

  func: Callable[P, R]

  __cached: Callable[P, R] = field(init=False, repr=False)

  def __post_init__(self):
    functools.update_wrapper(self, self.func)
    self.__annotations__ = None

  @overload
  def __call__(self: Self, *args: P.args, **kwargs: P.kwargs) -> R: ...

  @_validate_int
  def __call__(
    self,
    *args: SkipValidation[P.args],
    **kwargs: SkipValidation[P.kwargs]
  ) -> R:
    try:
      return self.__cached(*args, **kwargs)
    except AttributeError:
      cached = self.__cached = self.deco(self.func)
      return cached(*args, **kwargs)
    # def __call__ -> R

  @_validate_int
  def __annotate__(self, *args, **kwargs) -> dict[str, Any]:
    return self.func.__annotate__(*args, **kwargs)

  @overload
  def __set_name__(self: Self, cls: type[S], name: str) -> None: ...

  @_validate_int
  def __set_name__(self: Any, cls: type[S], name: str) -> None:
    setattr(
      cls,
      name,
      expost_desc(
        deco=cast(
          Callable[
            Callable[Concatenate[S, P], R],
            Callable[Concatenate[S, P], R]
          ],
          self.deco,
        ),
        meth=cast(
          Callable[Concatenate[S, P], R],
          self.func,
        ),
        name=name
      )
    )
    # def __set_name__ -> None

  # class expost_func


@dataclass
class expost_desc(Generic[S, P, R]):

  deco: Callable[
    Callable[Concatenate[S, P], R],
    Callable[Concatenate[S, P], R]
  ]
  meth: Callable[Concatenate[S, P], R]
  name: str

  def __post_init__(self):
    functools.update_wrapper(self, self.meth)
    self.__annotations__ = None

  @_validate_int
  def __annotate__(self, *args, **kwargs) -> dict[str, Any]:
    return self.meth.__annotate__(*args, **kwargs)

  @overload
  def __get__(
    self: Self,
    instance: S,
    cls: Optional[type[S]] = None
  ) -> Callable[P, R]: ...

  @overload
  def __get__(
    self: Self,
    instance: None,
    cls: type[S],
  ) -> Callable[Concatenate[S, P], R]: ...

  @_validate_int
  def __get__(
    self: Any,
    instance: Optional[S],
    cls: Optional[type[S]] = None
  ) -> Callable[Concatenate[S, P], R] | Callable[P, R]:
    setattr(
      cls or type(instance),
      self.name,
      self.deco(self.meth)
    )
    return getattr(instance or cls, self.name)
    # def __get__

  # class expost_desc


@validate_call(validate_return=True)
def wraps_expost(
  func: Callable[Q, Callable[P, R]]
) -> Callable[Q, expost[P, R]]:
  @functools.wraps(func)
  def func(*args: Q.args, **kwargs: Q.kwargs) -> expost[P, R]:
    return expost(func.__wrapped__(*args, **kwargs))
  return func
  # def wraps_expost -> Callable[Q, expost[P, R]]


validate_call_expost = wraps_expost(validate_call)


__all__ = ('validate_call_expost', )
