"""
Command to compute all grades for specified courses.
"""

from __future__ import absolute_import, division, print_function, unicode_literals

import logging

from django.core.management.base import BaseCommand, CommandError
import six

from openedx.core.lib.command_utils import (
    get_mutually_exclusive_required_option,
    parse_course_keys,
)
from lms.djangoapps.grades.config.models import ComputeGradesSetting
from student.models import CourseEnrollment
from xmodule.modulestore.django import modulestore

from ... import tasks


log = logging.getLogger(__name__)


class Command(BaseCommand):
    """
    Example usage:
        $ ./manage.py lms compute_grades --all_courses --settings=devstack
        $ ./manage.py lms compute_grades 'edX/DemoX/Demo_Course' --settings=devstack
    """
    args = '<course_id course_id ...>'
    help = 'Computes grade values for all learners in specified courses.'

    def add_arguments(self, parser):
        """
        Entry point for subclassed commands to add custom arguments.
        """
        parser.add_argument(
            '--courses',
            dest='courses',
            nargs='+',
            help='List of (space separated) courses that need grades computed.',
        )
        parser.add_argument(
            '--all_courses',
            help='Compute grades for all courses.',
            action='store_true',
            default=False,
        )
        parser.add_argument(
            '--from_settings',
            help='Compute grades with settings set via Django admin',
            action='store_true',
            default=False,
        )
        parser.add_argument(
            '--routing_key',
            dest='routing_key',
            help='Celery routing key to use.',
        )
        parser.add_argument(
            '--batch_size',
            help='Maximum number of students to calculate grades for, per celery task.',
            default=100,
            type=int,
        )
        parser.add_argument(
            '--start_index',
            help='Offset from which to start processing enrollments.',
            default=0,
            type=int,
        )

    def handle(self, *args, **options):
        self._set_log_level(options)

        for course_key in self._get_course_keys(options):
            self.enqueue_compute_grades_for_course_tasks(course_key, options)

    def enqueue_compute_grades_for_course_tasks(self, course_key, options):
        """
        Enqueue celery tasks to compute and persist all grades for the
        specified course, in batches.
        """
        enrollment_count = CourseEnrollment.objects.filter(course_id=course_key).count()
        if enrollment_count == 0:
            log.warning("No enrollments found for {}".format(course_key))
        for offset in six.moves.range(options['start_index'], enrollment_count, options['batch_size']):
            # If the number of enrollments increases after the tasks are
            # created, the most recent enrollments may not get processed.
            # This is an acceptable limitation for our known use cases.
            task_options = {'routing_key': options['routing_key']} if options.get('routing_key') else {}
            batch_size = self._latest_settings().batch_size if options.get('from_settings') else options['batch_size']
            kwargs = {
                'course_key': six.text_type(course_key),
                'offset': offset,
                'batch_size': batch_size,
            }
            result = tasks.compute_grades_for_course.apply_async(
                kwargs=kwargs,
                options=task_options,
            )
            log.info("Persistent grades: Created {task_name}[{task_id}] with arguments {kwargs}".format(
                task_name=tasks.compute_grades_for_course.name,
                task_id=result.task_id,
                kwargs=kwargs,
            ))

    def _get_course_keys(self, options):
        """
        Return a list of courses that need scores computed.
        """

        courses_mode = get_mutually_exclusive_required_option(options, 'courses', 'all_courses', 'from_settings')
        if courses_mode == 'all_courses':
            course_keys = [course.id for course in modulestore().get_course_summaries()]
        elif courses_mode == 'courses':
            course_keys = parse_course_keys(options['courses'])
        else:
            course_keys = parse_course_keys(self._latest_settings().course_ids.split())
        return course_keys

    def _set_log_level(self, options):
        """
        Sets logging levels for this module and the block structure
        cache module, based on the given the options.
        """
        if options.get('verbosity') == 0:
            log_level = logging.WARNING
        elif options.get('verbosity') >= 1:
            log_level = logging.INFO
        log.setLevel(log_level)

    def _latest_settings(self):
        return ComputeGradesSetting.current()
