"""Business logic of your application should be place in domain layer with
limited outside dependencies and based on simple plain python objects.
"""
from typing import List, Text, Any
from abc import ABCMeta, abstractmethod

from acd.libs.annotation import AnnotatedStructure, Immutable


class EntityId(tuple):
    """Tuple of entity values that are defined as unique entity id.
    """


class Entity(AnnotatedStructure):
    """Base class for application domain entities.

    :Example:

    >>> class Example(Entity, id_attributes=('num', )):
    ...     num: int
    ...     text: str
    >>> entity = Example(num=1, text='some data')
    >>> entity.num
    1
    >>> entity.text
    'some data'
    """
    def __init_subclass__(cls, id_attributes: List[Text]) -> None:
        cls._id_attributes = id_attributes

    @property
    def entity_id(self) -> EntityId:
        """Unique id for entity build from selected attributes.

        :Example:

        >>> class Example(Entity, id_attributes=('num', )):
        ...     num: int
        >>> Example(num=1).entity_id
        (1,)
        """
        return EntityId(getattr(self, attr) for attr in self._id_attributes)


class ValueObject(Immutable):
    """Base class for value objects based on annotation.
    """


class DomainService(metaclass=ABCMeta):
    """Base class for logic services in domain module.

    * Every service should inherit from this interface so we can track
      dependencies.
    * All dependencies should be passed in constructor and service should do
      one thing implemented in __call__ method.

    :Example:

    >>> from typing import Callable
    >>> class AdderService(DomainService):
    ...     def __init__(self, adder: Callable[[int, int], int]) -> None:
    ...         self._adder = adder
    ...
    ...     def __call__(self, first: int, second: int) -> int:
    ...       return self._adder(first, second)
    ...
    >>> add = AdderService(int.__add__)
    >>> add(1, 3)
    4
    """

    @abstractmethod  # pragma: no cover
    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        """Services are callable objects and here should be entry point for
        executing service code.
        """
        raise NotImplementedError


class DomainEvent(Immutable):
    """Base class for domain events.
    """
    timestamp: int
