"""Module for a composite asteex reporter."""

import logging
import asyncio

from asteex.abstract.reporter import Reporter, ReportError


class CompositeReporter(Reporter):
    # pylint: disable=too-few-public-methods
    """Class for a reporter that propagates results to its children."""

    def __init__(self, children=()):
        self.__children = tuple(children)

    async def report(self, results):
        """Report test results.

        :param results: async iterable with :class:`Results <asteex.abstract.result.Result>`.
        :raises: :class:`ReportError <asteex.abstract.reporter.ReportError>`.
        """
        logger = logging.getLogger(__name__)
        logger.debug("Start reporting test results with %s.", self)

        sentinel = object()

        tasks = []
        try:
            queues = []
            for child in self.__children:
                queue = asyncio.Queue()
                child_results = _generate_from_queue(queue=queue, sentinel=sentinel)

                try:
                    task = asyncio.create_task(child.report(child_results))
                except Exception as error:
                    logger.exception("Failed to schedule reporting for %s.", child)
                    raise ReportError(f"Failed to schedule reporting for {child}") from error

                tasks.append(task)
                queues.append(queue)

            try:
                async for result in results:
                    for queue in queues:
                        queue.put_nowait(result)
            except Exception as error:
                logger.exception("Failed to iterate over results.")
                raise ReportError("Failed to iterate over results") from error

            for queue in queues:
                queue.put_nowait(sentinel)

            try:
                for completed in asyncio.as_completed(tasks):
                    await completed
            except Exception as error:
                logger.exception("One of the child reporters failed to report the results.")
                raise ReportError(
                    "One of the child reporters failed to report the results",
                ) from error

            logger.info("Reported all test results with %s.", self)

        finally:
            for task in tasks:
                task.cancel()

            await asyncio.gather(*tasks, return_exceptions=True)


async def _generate_from_queue(queue, sentinel):
    """Async generator to produce values from the queue.

    :param queue: asyncio.Queue with the values for the generator.
    :param sentinel: sentinel object at which the generation would stop.
        The sentinel is not produced.
    """
    while True:
        item = await queue.get()
        if item is sentinel:
            break

        yield item
