import re
from abc import ABC, abstractmethod
from collections.abc import Callable, Generator, Hashable, Iterable, KeysView, ValuesView
from dataclasses import dataclass
from pathlib import Path
from types import UnionType
from typing import Any, Protocol, Self, TypeVar, cast

from pydantic_core import core_schema

T = TypeVar("T")
H = TypeVar("H", bound=Hashable)


type Atomic = int | float | str | bool | None
type ClassInfo = type | UnionType | tuple["ClassInfo"]
type JSONPrimitive = int | float | str | None
type JSONDict = dict[str, JSONPrimitive | "JSONDict" | list["JSONDict | JSONPrimitive"]]
# alternative: JSONDict = dict[str, Union["JSONDict", str, list[str], tuple[str, ...], set[str]]]
type JSONList = list[JSONPrimitive | JSONDict]
type Endofunction[T] = Callable[[T], T]
type SequenceAlias[T] = (
    list[T]
    | tuple[T]
    | set[T]
    | map[T]
    | filter[T]
    | dict[T, Any]
    | KeysView[T]
    | ValuesView[T]
    | range
    | Generator[T]
)
_SequenceAliasVar = (
    list | tuple | set | map | filter | dict | KeysView | ValuesView | range | Generator
)

_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)
_T_contra = TypeVar("_T_contra", contravariant=True)


def areinstances(iterable_instance: Iterable[Any], class_or_tuple: ClassInfo) -> bool:
    return all(map(lambda inst: isinstance(inst, class_or_tuple), iterable_instance))


def fallback_if_none(orig: T | None, alt: T) -> T:
    return alt if (orig is None) else orig


def call_fallback_if_none(orig: T | None, alt: Callable[[], T]) -> T:
    return alt() if (orig is None) else orig


def call_if_callable(maybe_callable: T | Callable[[], T]) -> T:
    if hasattr(maybe_callable, "__call__"):
        return cast(Callable[[], T], maybe_callable)()
    return cast(T, maybe_callable)


maybe_call = call_if_callable


class SupportsGe(Protocol):
    def __ge__(self: T, __other: T) -> bool: ...


class SupportsGt(Protocol):
    def __gt__(self: T, __other: T) -> bool: ...


class SupportsLe(Protocol):
    def __le__(self: T, __other: T) -> bool: ...


class SupportsLt(Protocol):
    def __lt__(self: T, __other: T) -> bool: ...


@dataclass(frozen=True)
class _BaseMetadata:
    """Base class for all metadata.

    This exists mainly so that implementers
    can do `isinstance(..., BaseMetadata)` while traversing field annotations.
    """

    __slots__ = ()


@dataclass(frozen=True)
class Ge(_BaseMetadata):
    """Ge(ge=x) implies that the value must be greater than or equal to x.

    It can be used with any type that supports the ``>=`` operator,
    including numbers, dates and times, strings, sets, and so on.
    """

    ge: SupportsGe

    def __get_pydantic_core_schema__(
        self, source_type, _handler
    ) -> core_schema.WrapValidatorFunctionSchema:
        string_schema = core_schema.any_schema()

        return core_schema.no_info_wrap_validator_function(
            cast(core_schema.NoInfoWrapValidatorFunction, self._validate), string_schema
        )

    def _validate(
        self, value: SupportsGe, handler: core_schema.ValidatorFunctionWrapHandler
    ) -> SupportsGe:
        if not value >= self.ge:
            raise ValueError(f"Value {value} is not less than or equal to {self.ge}.")
        return value


@dataclass(frozen=True)
class Gt(_BaseMetadata):
    """Gt(gt=x) implies that the value must be greater than x.

    It can be used with any type that supports the ``>`` operator,
    including numbers, dates and times, strings, sets, and so on.
    """

    gt: SupportsGt

    def __get_pydantic_core_schema__(
        self, source_type, _handler
    ) -> core_schema.WrapValidatorFunctionSchema:
        string_schema = core_schema.any_schema()

        return core_schema.no_info_wrap_validator_function(
            cast(core_schema.NoInfoWrapValidatorFunction, self._validate), string_schema
        )

    def _validate(
        self, value: SupportsGt, handler: core_schema.ValidatorFunctionWrapHandler
    ) -> SupportsGt:
        if not value > self.gt:
            raise ValueError(f"Value {value} is not less than or equal to {self.gt}.")
        return value


@dataclass(frozen=True)
class Lt(_BaseMetadata):
    """Lt(lt=x) implies that the value must be less than x.

    It can be used with any type that supports the ``<`` operator,
    including numbers, dates and times, strings, sets, and so on.
    """

    lt: SupportsLt

    def __get_pydantic_core_schema__(
        self, source_type, _handler
    ) -> core_schema.WrapValidatorFunctionSchema:
        string_schema = core_schema.any_schema()

        return core_schema.no_info_wrap_validator_function(
            cast(core_schema.NoInfoWrapValidatorFunction, self._validate), string_schema
        )

    def _validate(
        self, value: SupportsLt, handler: core_schema.ValidatorFunctionWrapHandler
    ) -> SupportsLt:
        if not value < self.lt:
            raise ValueError(f"Value {value} is not less than or equal to {self.lt}.")
        return value


@dataclass(frozen=True)
class Le(_BaseMetadata):
    """Le(le=x) implies that the value must be less than x.

    It can be used with any type that supports the ``<=`` operator,
    including numbers, dates and times, strings, sets, and so on.
    """

    le: SupportsLe

    def __get_pydantic_core_schema__(
        self, source_type, _handler
    ) -> core_schema.WrapValidatorFunctionSchema:
        string_schema = core_schema.any_schema()

        return core_schema.no_info_wrap_validator_function(
            cast(core_schema.NoInfoWrapValidatorFunction, self._validate), string_schema
        )

    def _validate(
        self, value: SupportsLe, handler: core_schema.ValidatorFunctionWrapHandler
    ) -> SupportsLe:
        if not value <= self.le:
            raise ValueError(f"Value {value} is not less than or equal to {self.le}.")
        return value


# @dataclass(frozen=True)
# class Re(_BaseMetadata):
#     """Re(pattern=s) implies that the value must be a string matching `s`.

#     It can be used with any string.
#     """

#     pattern: str | re.Pattern[str]


@dataclass(frozen=True)
class Re(_BaseMetadata):
    pattern: str | re.Pattern[str]

    def __post_init__(self) -> None:
        if isinstance(self.pattern, str):
            object.__setattr__(self, "pattern", re.compile(self.pattern))

    def __get_pydantic_core_schema__(
        self, source_type, _handler
    ) -> core_schema.WrapValidatorFunctionSchema:
        assert source_type is str, "Re may only be used on strings"

        string_schema = core_schema.str_schema()

        return core_schema.no_info_wrap_validator_function(
            cast(core_schema.NoInfoWrapValidatorFunction, self._validate), string_schema
        )

    def _validate(self, value: str, handler: core_schema.ValidatorFunctionWrapHandler) -> str:
        if not re.search(self.pattern, value):
            raise ValueError(f"String {value} does not match regex '{self.pattern!r}'.")
        return value


class SupportsIO(ABC):
    @classmethod
    @abstractmethod
    def read(cls, read_path: Path) -> Self: ...

    @abstractmethod
    def write(self, write_path: Path) -> None: ...


class SupportsJSONIO(ABC):
    @classmethod
    @abstractmethod
    def read_json_file(cls, read_path: Path) -> Self: ...

    @abstractmethod
    def write_json_file(self, write_path: Path) -> None: ...


def ensure_set(x: H | SequenceAlias[H]) -> set[H]:
    return set(cast(Iterable[H], x)) if isinstance(x, _SequenceAliasVar) else {x}


def ensure_tuple(x: T | SequenceAlias[T]) -> tuple[T, ...]:
    return tuple(cast(Iterable[T], x)) if isinstance(x, _SequenceAliasVar) else (x,)


def ensure_list(x: T | SequenceAlias[T]) -> list[T]:
    return list(cast(Iterable[T], x)) if isinstance(x, _SequenceAliasVar) else [x]


# ==============


class IdentityFunction[T](Protocol):
    def __call__(self, x: _T, /) -> _T: ...


# stable
class SupportsNext(Protocol[_T_co]):
    def __next__(self) -> _T_co: ...


class SupportsBool(Protocol):
    def __bool__(self) -> bool: ...


# Comparison protocols
class SupportsDunderLT(Protocol[_T_contra]):
    def __lt__(self, other: _T_contra, /) -> SupportsBool: ...


class SupportsDunderGT(Protocol[_T_contra]):
    def __gt__(self, other: _T_contra, /) -> SupportsBool: ...


class SupportsDunderLE(Protocol[_T_contra]):
    def __le__(self, other: _T_contra, /) -> SupportsBool: ...


class SupportsDunderGE(Protocol[_T_contra]):
    def __ge__(self, other: _T_contra, /) -> SupportsBool: ...


class SupportsAllComparisons(
    SupportsDunderLT[Any],
    SupportsDunderGT[Any],
    SupportsDunderLE[Any],
    SupportsDunderGE[Any],
    Protocol,
): ...


type SupportsRichComparison = SupportsDunderLT[Any] | SupportsDunderGT[Any]
SupportsRichComparisonT = TypeVar("SupportsRichComparisonT", bound=SupportsRichComparison)

C = TypeVar("C", bound=SupportsRichComparison)
