"""This module contains the mixins for the ``saba`` discrete_app"""

import logging

from .errors import ExemptOrRecommendedCourseError, MissingKeyInDataError
from .models import CertificationProgramUserStatusDjangoTask

logger = logging.getLogger('mtp.discrete_apps.saba')


class SabaStepsMixin:
    """This class provide helpers to handle the steps in ``AssessmentsResultsToSabaTask``"""

    def _persist_results(self):
        """Persist the task results into Redis

        We do not want to do this each time ``self.data`` is accessed because it
        involves a json (de)serialization process.

        """

        try:
            django_task = self.get_django_task()
        # when on CLI we do not have an attached django task
        except CertificationProgramUserStatusDjangoTask.DoesNotExist:
            logger.debug('SabaStepsMixin would have stored data:\n %s', self.data)
            return

        django_task.redis_instance.results = self.data

    def _execute_step(self, step):
        """Wrapper to execute a step, needed for tests.

        Parameters
        ----------
        step: string
            Name of the step

        """

        # let's try that first, in case it fails
        step_method = getattr(self, step)
        # we may want to skip this step if already completed
        if self._is_step_completed(step):
            return
        self._init_step(step)
        try:
            step_data = step_method()
        except Exception:  # pylint: disable=broad-except
            self._fill_step_data(step, {}, success=False)
            raise

        # we have two special steps that need a special treatment
        # we need to parse the output to see if an error occured and raise an error if so
        has_course_error = False
        if step in ['set_exempt_courses', 'set_mandatory_courses']:
            has_course_error = bool(sum([
                not course.get('success')
                for course in step_data
            ]))

        self._fill_step_data(step, step_data, success=(not has_course_error))

        if has_course_error:
            raise ExemptOrRecommendedCourseError(step, step_data)

    def _is_step_completed(self, step):
        """Helper method to check if a step has been previously completed successfully

        Parameters
        ----------
        step: string
            Name of the step

        Returns
        -------
        bool
            True if the step is already completed, otherwise False

        """

        if 'steps' not in self.data or step not in self.data['steps']:
            return False

        if 'success' not in self.data['steps'][step]:
            return False

        return self.data['steps'][step]['success']

    def _init_step(self, step):
        """Helper method to init the structure of a step in `self.data`

        Parameters
        ----------
        step: string
            The name of the step

        """

        if 'steps' not in self.data:
            self.data['steps'] = {}

        if step not in self.data['steps']:
            self.data['steps'][step] = {
                'success': False
            }

    def _fill_step_data(self, step, data, success=True):
        """Helper method to fill the results of a step in `self.data`

        Parameters
        ----------
        step: string
            Name of the step
        data: dict
            Data to store as `out` for this step
        success: bool, optionnal
            If the step completed successfully

        """

        if 'executed_steps' not in self.data:
            self.data['executed_steps'] = []

        self.data['executed_steps'].append(
            '%(step_name)s: %(step_status)s' % {
                'step_name': step,
                'step_status': 'OK' if success else 'KO'
            }
        )

        if 'steps' not in self.data:
            self.data['steps'] = {}

        self.data['steps'][step] = {
            'success': success,
        }

        if data:
            self.data['steps'][step]['out'] = data

        self._persist_results()

    def _get_step_data(self, step, step_key=None, subkey=None):
        """Helper method to get value of specific `data['steps']['out']` key for a step.

        Parameters
        ----------
        step: string
            Name of the step
        step_key: string, optionnal
            The key to fetch value for.
        subkey: string, optionnal
            The sub key under `step_key` to fetch value for.

        Raises
        ------
        MissingKeyInDataError
            If asked key/subkey is missing.

        """

        out_data = self.data['steps'][step]['out']

        if not step_key:
            return out_data

        try:
            value = out_data[step_key]
            if subkey is not None:
                value = value[subkey]
        except KeyError:
            raise MissingKeyInDataError(
                'Missing key for step %(step_name)s (%(key)s%(subkey)s): data was %(data)s' % (
                    {
                        'step_name': step,
                        'key': step_key,
                        'subkey': '/%s' % subkey if subkey else '',
                        'data': out_data,
                    }
                )
            )

        return value
