import pprint
from abc import abstractmethod
from collections.abc import ItemsView, KeysView, ValuesView
from typing import (
    Any,
    Generic,
    Hashable,
    Iterator,
    List,
    Optional,
    Tuple,
    TypeVar,
)

K = TypeVar("K", bound=Hashable)


class _NoCallMeta(type):
    def __call__(cls, *args: Any, **kwargs: Any) -> None:
        raise TypeError("Class doesn't support instance creation")


class _NoSetAttrMeta(type):
    def __setattr__(cls, key: Any, value: Any, *args: Any) -> None:
        if "override" in args:
            type.__setattr__(cls, key, value)
        else:
            raise TypeError("Class is read-only")


# collections abc meta


class _SizedMeta(type):

    @abstractmethod
    def __len__(cls) -> int:
        raise NotImplementedError


class _IterableMeta(type):

    @abstractmethod
    def __iter__(cls) -> Iterator:
        raise NotImplementedError


class _ContainerMeta(type):

    @abstractmethod
    def __contains__(cls, item: Any) -> bool:
        raise NotImplementedError


# Mixin
class _CollectionMeta(_SizedMeta, _IterableMeta, _ContainerMeta): ...


class _MappingMeta(_CollectionMeta, Generic[K]):

    @abstractmethod
    def __getitem__(cls, key: K) -> Any:
        raise NotImplementedError

    # Default
    def __contains__(cls, key: K) -> bool:
        try:
            cls[key]
        except KeyError:
            return False
        else:
            return True

    def keys(cls) -> List[K]:
        return list(KeysView(cls))  # type: ignore[arg-type]

    def values(cls) -> List[Any]:
        return list(ValuesView(cls))  # type: ignore[arg-type]

    def items(cls) -> List[Tuple[K, Any]]:
        return list(ItemsView(cls))  # type: ignore[arg-type]

    def get(cls, key: K, default: Optional[Any] = None) -> Any:
        try:
            return cls[key]
        except KeyError:
            return default

    def __eq__(cls, other: Any) -> bool:
        if not isinstance(other, _MappingMeta):
            return NotImplemented
        return dict(cls.items()) == dict(other.items())

    def __repr__(cls) -> str:
        return pprint.pformat(cls.items())

    def __str__(cls) -> str:
        return pprint.pformat(cls.items())


class _MappingMetaExtended(_NoCallMeta, _NoSetAttrMeta, _MappingMeta[K]): ...


class _MappingMetaDocumentation:
    """
    Нужен для документации sphinx и для IntelliSense в IDE.

    Методы абстрактные, чтобы наследник переопределял docstring и type hint. При
    наследовании нужен только вызов super().
    """

    @classmethod
    @abstractmethod
    def items(cls) -> List[Tuple[str, Any]]:
        """Возвращает список кортежей ключ-значение, где ключи это строки, а значения
        это объекты класса :class:`object`."""
        return type(cls).items(cls)  # type: ignore[attr-defined]

    @classmethod
    @abstractmethod
    def keys(cls) -> List[str]:
        """Возвращает список ключей, где ключи это строки."""
        return type(cls).keys(cls)  # type: ignore[attr-defined]

    @classmethod
    @abstractmethod
    def values(cls) -> List[Any]:
        """Возвращает список значений, где значения это объекты класса :class:`object`."""
        return type(cls).values(cls)  # type: ignore[attr-defined]

    @classmethod
    @abstractmethod
    def get(cls, key: str, default_value: Optional[Any] = None) -> Any:
        """
        Возвращает значение по ключу.

        Если ключа нет в словаре, возвращает значение по умолчанию.
        """
        return type(cls).get(cls, key, default_value)  # type: ignore[attr-defined]
