"""Module to manage test cases defined in Python packages."""

import inspect
import logging

from asteex.core.decorator.argument import CopyError
from asteex.core.case.executable.delegate import DelegateCaseBuilder


class DecorationError(Exception):
    """Exception for errors raised on attempt to decorate callables for delegate cases."""


class CaseStorage:
    """Class to store test cases."""

    def __init__(self):
        self.__case_groups = {}

    def add_cases(self, group, cases):
        """Add test cases to the storage.

        :param group: string group name for a case.
        :param cases: iterable with :class:`Cases <asteex.abstract.case.Case>` to add.
        :raises: :class:`ValueError` if cases is not a valid iterable.
        """
        group = str(group)

        logger = logging.getLogger(__name__)
        logger.debug("Try to add cases to the group '%s' into the storage %s.", group, self)

        try:
            refined_cases = tuple(cases)
        except TypeError as error:
            logger.exception("Can't iterate over cases.")
            raise ValueError("Can't iterate over cases") from error

        self.__case_groups.setdefault(group, []).extend(refined_cases)

        logger.info("Added cases to the group '%s' into the storage %s.", group, self)

    def get_cases(self, group):
        """Get stored test cases.

        :param group: string group name with cases.
        :returns: tuple with :class:`Case <asteex.abstract.case.Case>`.
        """
        group = str(group)
        cases = tuple(self.__case_groups.get(group, ()))

        logging.getLogger(__name__).info(
            "Got cases of group '%s' from the storage %s ",
            group,
            self,
        )

        return cases


class DelegateCaseDecorator(DelegateCaseBuilder):
    """Decorator to create delegate cases from callables."""

    def __init__(self):
        super().__init__()
        self.__storages = ()

    def add_storages(self, storages, overwrite=False):
        """Add storages for new test cases.

        :param storages: iterable with :class:`CaseStorage`.
        :param overwrite: boolean whether the new storages should overwrite the existing ones.
        :returns: self.
        :raises: :class:`ValueError` if storages is not a valid iterable.
        """
        logger = logging.getLogger(__name__)
        logger.debug("Try to set case storages in %s.", self)

        try:
            refined_case_storages = tuple(storages)
        except TypeError as error:
            logger.exception("Can't iterate over case storages.")
            raise ValueError("Can't iterate over case storages") from error

        if overwrite:
            self.__storages = refined_case_storages
        else:
            self.__storages = self.__storages + refined_case_storages

        logger.info("Set case storages in %s.", self)

        return self

    def get_storages(self):
        """Get storages for new test cases.

        :returns: tuple with :class:`CaseStorage`.
        """
        return self.__storages

    def __call__(self, target):
        """Decorate a callable.

        :param target: a callable to decorate.
        :returns: unchanged target.
        :raises: :class:`DecorationError` if an error occurs on attempt to create or store a case.
        """
        logger = logging.getLogger(__name__)
        logger.debug("Try to decorate %s to build a case with %s.", target, self)

        module = inspect.getmodule(target)
        if module is None:
            logger.error("Can't get the module of the decorated object for a case.")
            raise DecorationError("Can't get the module of the decorated object for a case")

        try:
            case = self.build_from_callable(target)
        except Exception as error:
            logger.exception("Can't build a case.")
            raise DecorationError("Can't build a case") from error

        for storage in self.__storages:
            try:
                storage.add_cases(group=module.__name__, cases=(case, ))
            except Exception as error:
                logger.exception("Can't store the built case.")
                raise DecorationError("Can't store the built case") from error

        logger.info("Decorated %s to build a case with %s.", target, self)

        return target

    def _copy_data(self, other):
        """Copy data from this instance to another.

        :param other: instance of the same type.
        :raises: :class:`CopyError <asteex.core.decorator.argument.CopyError>`
            if data can't be copied.
        """
        super()._copy_data(other)

        logger = logging.getLogger(__name__)

        try:
            other.add_storages(self.get_storages(), overwrite=True)
        except Exception as error:
            logger.exception("Can't copy case storages to another instance.")
            raise CopyError("Can't copy case storages to another instance") from error
