import calendar
import inspect
import re
import time
from datetime import date, datetime, timezone
from functools import wraps
from typing import Iterable, Union
from unittest.mock import Mock

from google.protobuf import timestamp_pb2

__all__ = [
    "dt",
    "coro_mock",
    "Any",
    "Converter",
    "AsyncIterator",
    "AsyncContextManagerMock",
]


def dt(
    value: Union[int, str], *, as_proto: bool = False
) -> Union[datetime, date, timestamp_pb2.Timestamp]:
    if isinstance(value, int):
        res = datetime.fromtimestamp(value, tz=timezone.utc)
    elif len(value) > 10:
        res = datetime(
            *time.strptime(value, "%Y-%m-%d %H:%M:%S")[:6], tzinfo=timezone.utc
        )
    else:
        res = date(*time.strptime(value, "%Y-%m-%d")[:3])

    if as_proto:
        seconds = (
            calendar.timegm(res.timetuple())
            if isinstance(res, date)
            else int(res.timestamp())
        )
        res = timestamp_pb2.Timestamp(seconds=seconds)

    return res


def coro_mock():
    coro = Mock(name="CoroutineResult")
    corofunc = Mock(name="CoroutineFunction", side_effect=_coroutine(coro))
    corofunc.coro = coro
    return corofunc


class Any:
    def __init__(self, type_: type):
        self._type = type_
        self._len = None

    def of_len(self, len_: int):
        self._len = len_
        return self

    def __eq__(self, another):
        if not isinstance(another, self._type):
            return False

        if self._len is not None and len(another) != self._len:
            return False

        return True


class Converter:
    def __init__(self, mapping: Iterable[tuple]):
        self._forward = {el[0]: el[1] for el in mapping}
        self._reversed = {el[1]: el[0] for el in mapping}

    def forward(self, value):
        return self._forward[value]

    def reversed(self, value):
        return self._reversed[value]


class AsyncIterator:
    def __init__(self, seq):
        self.seq = seq or []
        self.iter = iter(self.seq)

    def __aiter__(self):
        return self

    def __call__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        self.iter = iter(self.seq)
        return self

    async def __anext__(self):
        try:
            val = next(self.iter)
            if isinstance(val, Exception) and not isinstance(val, StopIteration):
                raise val
            else:
                return val
        except StopIteration:
            raise StopAsyncIteration


class AsyncContextManagerMock:
    def __call__(self, *args, **kwargs):
        return self

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc, tb):
        pass


def _coroutine(func):
    if inspect.iscoroutinefunction(func):
        return func

    @wraps(func)
    async def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper


def validate_custom_page_id(page_id) -> bool:
    return not page_id or re.fullmatch(r"[a-z0-9_-]+", page_id)
