"""Module for a base group generator."""

from abc import abstractmethod
import logging

from asteex.abstract.generator import Generator, GenerationError


class GroupError(GenerationError):
    """Exception class for errors raised during management of groups."""


class InstanceGenerationError(GenerationError):
    """Exception class for errors raised on attempt to get group instances."""


class GroupGenerator(Generator):
    """Class for a generator that creates instances from a hierarchy of groups."""

    def __init__(self, root):
        super().__init__()
        self.__root = str(root)

    def generate(self):
        """Generate test instances.

        :returns: async iterable with :class:`Instances <asteex.abstract.instance.Instance>`.
        :raises: :class:`GenerationError <asteex.abstract.generator.GenerationError>`.
        """
        try:
            return self.create_instances_generator()
        except Exception as error:
            logger = logging.getLogger(__name__)
            logger.exception("Failed to create an instances generator.")
            raise GenerationError("Failed to create an instances generator") from error

    async def create_instances_generator(self):
        """Async generator producing test instances for the hierarchy of the group.

        :returns: async iterable with :class:`Instances <asteex.abstract.instance.Instance>`.
        :raises: :class:`GenerationError <asteex.abstract.generator.GenerationError>`.
        """
        root = self.__root

        logger = logging.getLogger(__name__)
        logger.debug("Start generating test instances with %s for the group '%s'.", self, root)

        generation_stack = [root]
        while generation_stack:
            group = generation_stack.pop()

            try:
                async for instance in self.create_group_instances_generator(group):
                    yield instance
            except Exception as error:
                logger.exception("Failed to generate test instances for the group '%s'.", group)
                raise GenerationError(
                    f"Failed to generate test instances for the group '{group}'",
                ) from error

            try:
                subgroups = tuple(str(subgroup) for subgroup in self.get_subgroups(group))
            except Exception as error:
                logger.exception("Failed to get subgroups of the group '%s'.", group)
                raise GenerationError(
                    f"Failed to get subgroups of the group '{group}'",
                ) from error

            # We revert the order of subgroups so that the first returned group is processed first.
            generation_stack.extend(subgroups[::-1])

        logger.info("Finished generating test instances with %s for the group '%s'.", self, root)

    def __repr__(self):
        class_name = self.__class__.__name__
        root = self.__root
        return f"{class_name}(root='{root}')"

    @property
    def root(self):
        """Name of the root group."""
        return self.__root

    @abstractmethod
    def get_subgroups(self, group):
        """Get subgroups of a group.

        :param group: string name of the subgroup in the hierarchy.
        :returns: iterable with string names of the children of the group.
        :raises: :class:`GroupError`.
        """

    @abstractmethod
    def create_group_instances_generator(self, group):
        """Create an async iterator with test instances for a single group.

        :param group: string name of the group in the hierarchy.
        :returns: async iterable with :class:`Instances <asteex.abstract.instance.Instance>`.
        :raises: :class:`InstanceGenerationError` if instances can't be retrieved.
        """
