"""Management command to change many user enrollments at once."""
import logging

from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from optparse import make_option
from six import text_type
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview

from course_modes.models import CourseMode
from student.models import CourseEnrollment
from xmodule.modulestore.django import modulestore

logger = logging.getLogger(__name__)  # pylint: disable=invalid-name


class Command(BaseCommand):
    """Management command to change many user enrollments at once."""

    help = """
    Change the enrollment status for all users enrolled in a
    particular mode for a course. Similar to the change_enrollment
    script, but more useful for bulk moves.

    Example:

    Change enrollment for all audit users to honor in the given course.
        $ ... bulk_change_enrollment -c course-v1:SomeCourse+SomethingX+2016 --from_mode audit --to_mode honor --commit

    Without the --commit option, the command will have no effect.
    """

    def add_arguments(self, parser):
        group = parser.add_mutually_exclusive_group()
        group.add_argument(
            '-c', '--course',
            help='The course to change enrollments in')
        group.add_argument(
            '-o', '--org',
            help='All courses belonging to this org will be selected for changing the enrollments')

        parser.add_argument(
            '-f', '--from_mode',
            required=True,
            help='Move from this enrollment mode')
        parser.add_argument(
            '-t', '--to_mode',
            required=True,
            help='Move to this enrollment mode')
        parser.add_argument(
            '--commit',
            action='store_true',
            help='Save the changes, without this flag only a dry run will be performed and nothing will be changed')

    def handle(self, *args, **options):
        course_id = options['course']
        org = options['org']
        from_mode = options['from_mode']
        to_mode = options['to_mode']
        commit = options['commit']
        course_keys = []

        if course_id:
            try:
                course_key = CourseKey.from_string(course_id)
            except InvalidKeyError:
                raise CommandError('Course ID {} is invalid.'.format(course_id))

            if modulestore().get_course(course_key) is None:
                raise CommandError('The given course {} does not exist.'.format(course_id))

            course_keys.append(course_key)
        else:
            course_keys = [course.id for course in CourseOverview.get_all_courses(orgs=[org])]
            if not course_keys:
                raise CommandError('No courses exist for the org "{}".'.format(org))

        for course_key in course_keys:
            self.move_users_for_course(course_key, from_mode, to_mode, commit)

        if not commit:
            logger.info('Dry run, changes have not been saved. Run again with "commit" argument to save changes')

    def move_users_for_course(self, course_key, from_mode, to_mode, commit):
        """
        Change the enrollment mode of users for a course.

        Arguments:
            course_key (CourseKey): to lookup the course.
            from_mode (str): the enrollment mode to change.
            to_mode (str): the enrollment mode to change to.
            commit (bool): required to make the change to the database. Otherwise
                                     just a count will be displayed.
        """
        unicode_course_key = text_type(course_key)
        if CourseMode.mode_for_course(course_key, to_mode) is None:
            logger.info('Mode ({}) does not exist for course ({}).'.format(to_mode, unicode_course_key))
            return

        course_enrollments = CourseEnrollment.objects.filter(course_id=course_key, mode=from_mode)
        logger.info(
            'Moving %d users from %s to %s in course %s.',
            course_enrollments.count(), from_mode, to_mode, unicode_course_key
        )
        if commit:
            # call `change_mode` which will change the mode and also emit tracking event
            for enrollment in course_enrollments:
                with transaction.atomic():
                    enrollment.change_mode(mode=to_mode)

            logger.info('Finished moving users from %s to %s in course %s.', from_mode, to_mode, unicode_course_key)
