from typing import Any
from typing import Iterable
from typing import Iterator
from typing import MutableMapping

from redbeanpython.bean.id import Id
from redbeanpython.bean.validator import NameValidator
from redbeanpython.bean.validator import ValueValidator
from redbeanpython.const import ID_TYPE
from redbeanpython.const import TYPE
from redbeanpython.errors import IncorrectValueError


class Bean(MutableMapping[str, TYPE]):
    INTERNALS = ["id", "_data", "_bean_type", "_name_validator", "_value_validator"]

    def __init__(self, bean_type: str, data: dict[str, TYPE] = None):
        """
        All operations in RedBeanPython are done on Beans objects.
        Beans works similar, but not identical, as ORM objects in other ORM libraries.

        Bean object makes use of the corresponding (autogenerated) SQLAlchemy Model
        (if you want to know more, see hybrid mode)

        Beans can be created just by passing their type to the Bean class.

        > user = Bean('user')

        And their property can be added just as an object property:

        > user.name = "Adam"
        > user.age = 42

        or via a dictionary-like interface

        > user['name'] = "Adam"
        > user['age'] = 42
        """
        self._data = {}
        self._name_validator = NameValidator()
        self._value_validator = ValueValidator()

        self._bean_type = bean_type
        self._name_validator.validate_bean_type(bean_type)
        self.id = Id.next_id()

        self._init_data(data)

    def _init_data(self, data: dict[str, TYPE]) -> None:
        data = data or {}
        for key, value in data.items():
            self._name_validator.validate_property_name(key)
            self._data[key] = value

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, type(self)):
            return False
        if self.bean_type != other.bean_type:
            return False
        return dict(self) == dict(other)

    @property
    def id(self) -> Id:
        return self._data["id"]

    @id.setter
    def id(self, id_: ID_TYPE) -> None:
        if not isinstance(id_, (str, Id)):
            raise IncorrectValueError(f"Id must be str or Id, not {type(id_)}")
        self._data["id"] = Id(id_)

    def keys(self) -> Iterable[str]:
        return self._data.keys()

    @property
    def bean_type(self) -> str:
        return self._bean_type

    def __contains__(self, item: str):
        return item in self._data

    def __getitem__(self, item: str):
        return self._data.get(item, None)

    def __setitem__(self, item: str, value: TYPE):
        if item in self.INTERNALS:
            return super().__setattr__(item, value)
        self._name_validator.validate_property_name(item)
        self._value_validator.validate(value)
        self._data[item] = value

    def __delitem__(self, item: str):
        if item in self.INTERNALS:
            raise IncorrectValueError(f"Can not delete {item}")
        self._name_validator.validate_property_name(item)
        if item in self._data:
            del self._data[item]

    def __getattr__(self, item: str):
        return self.__getitem__(item)

    def __setattr__(self, item: str, value: TYPE):
        if item in self.INTERNALS:
            return super().__setattr__(item, value)

        self._name_validator.validate_property_name(item)
        self.__setitem__(item, value)

    def __delattr__(self, item: str):
        if item in self.INTERNALS:
            raise IncorrectValueError(f"Can not delete {item}")
        self._name_validator.validate_property_name(item)
        self.__delitem__(item)

    def __iter__(self) -> Iterator[str]:
        return iter(self._data)

    def __len__(self) -> int:
        return len(self._data)

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        return f"Bean(bean_type='{self.bean_type}', data={self._data})"
