"""This module holds the exporters for the ``saba`` discrete_app."""

import csv
import logging
from collections import defaultdict
from datetime import timedelta
from io import StringIO

from django.utils import timezone

from constance import config
from pinax.notifications.models import send

from mtp.apps.accounts.models import MTPUser
from mtp.apps.common.views_utils import ExportFileMixin

from .models import CertificationProgramUserStatusDjangoTask

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


class SabaResultsExporter(ExportFileMixin):
    """Exporter to export saba sync results.

    Attributes
    ----------
    CSV_WRITER_KWARGS: dict
        The dict of kwargs to pass to ``csv.writer``.
    tasks: list(``CertificationProgramUserStatusDjangoTask``)
        The tasks which results we want to export.
    tasks_data: dict
        The data needed to send recap email grouped by user, like so:
        {
            <MTPUser>: [
                # csv rows
            ]
        }

    """

    CSV_WRITER_KWARGS = {
        'quoting': csv.QUOTE_ALL,
    }

    def __init__(self, end_date=None):
        """Initialize the class with the tasks we need to work on.

        Parameters
        ----------
        end_date : datetime
            We will fetch ``CertificationProgramUserStatusDjangoTask`` finished at most 24h
            before ``end_date``. We do not use ``datetime.now`` as default value as this would
            catch the datetime at the time the code is loaded, not the time the class is
            instanciated.

        """

        if not end_date:
            end_date = timezone.now()

        start_date = end_date - timedelta(days=1)
        self.tasks = CertificationProgramUserStatusDjangoTask.objects.filter(
            finished_at__gte=start_date,
            finished_at__lt=end_date,
        )
        self.tasks_data = defaultdict(list)

    def _get_exception_step(self, result):
        """Extract the name of the step that failed from a result

        Parameters
        ----------
        result: dict
            The result to parse

        Returns
        -------
        str
            The name of the step that failed or empty string

        """

        exception_step = ''

        for step_name, step_result in result.get('steps', {}).items():
            if not step_result.get('success'):
                exception_step = step_name
                break

        return exception_step

    @staticmethod
    def _get_executed_steps_details(result):
        """Extract the step names and their status for a given result

        Parameters
        ----------
        result: dict
            The result to parse

        Returns
        -------
        str
            The different name of the steps and their respective state
            Example:
            parse_certification: OK
            get_trainee_details: OK
            get_enrollment_details: KO

        """

        try:
            executed_steps = result['executed_steps']
        except KeyError:
            return ''

        return '\n'.join(executed_steps)

    def _build_csv_line_for_task(self, task):
        """Build a result line to be inserted into a CSV global result file

        Parameters
        ----------
        task: CertificationProgramUserStatusDjangoTask instance
            The task to parse

        Returns
        -------
        string
            The CSV line

        """

        result = task.redis_instance.results or {}

        training = task.certification.session.training

        return [
            # end-user focused data
            task.certification.user.original_or_self_external_id,
            task.certification.user.first_name,
            task.certification.user.last_name,
            training.external_id,
            str(task.certification.session.start_date.date()),
            training.business_unit.name if training.business_unit else '',
            training.job_profile.name if training.job_profile else '',
            result.get('status', 'N/A'),
            result.get('exception_user_message', ''),
            # technical focused data
            self._get_executed_steps_details(result),
            result.get('exception', ''),
            task.pk,
            task.certification.pk,
        ]

    def _build_csv_string(self, rows):
        """Build a CSV file as string from given rows.

        Parameters
        ----------
        rows: list
            The list of data rows to use in the CSV

        Returns
        -------
        string
            The CSV file

        """

        csv_headers = [
            # end-user focused data
            'user_id_nao',
            'user_first',
            'user_last',
            'course_saba_no',
            'session_start_date',
            'training_business_unit',
            'training_job_profile',
            'status',
            'error',
            # technical focused data
            'error_step',
            'technical_error',
            'task_id',
            'certification_id',
        ]

        rows = [csv_headers] + rows

        output = StringIO()
        output.write("sep=,\r\n")
        csv_writer = csv.writer(output, **self.CSV_WRITER_KWARGS)
        for row in rows:
            csv_writer.writerow(row)

        return output.getvalue()

    def _extract_data(self):
        """Extract needed data from ``self.tasks``"""

        for task in self.tasks:
            csv_line = self._build_csv_line_for_task(task)
            self.tasks_data[task.user].append(csv_line)

    def get_filename(self):
        """Get the filename of csv file to attach to the email.

        Returns
        -------
        str

        """

        return 'nao-results-%s' % timezone.now().date()

    def send_recap_email(self):
        """Build a CSV from ``self.tasks_data`` and send a recap email to specified users."""

        # get people who are systematically CCed the email
        base_recipients = []
        usernames = config.SABA_RECAP_EMAIL_RECIPIENTS
        if usernames:
            for username in usernames.split(','):
                try:
                    base_recipients.append(MTPUser.objects.get(username=username))
                except MTPUser.DoesNotExist:
                    logger.warning('Username %s does not map to an existing user.', username)

        # generate the data we need
        self._extract_data()

        # send the recap email to each user who asked for a sync
        for user, rows in self.tasks_data.items():
            if user not in base_recipients:
                recipients = [user] + base_recipients
            else:
                recipients = base_recipients

            csv_attachment = (
                '%s.csv' % self.get_slugified_filename(),
                self._build_csv_string(rows),
                'text/csv'
            )

            send(recipients, 'saba_results', {
                'sync_trigger_user_fullname': user.get_full_name(),
                'sync_trigger_user_bus': user.mgroups.non_deleted().business_units(),
                'attachments': [csv_attachment],
                'sync_date': timezone.now().date(),
            })
