from __future__ import unicode_literals

import logging
from optparse import make_option

from django.core.management import BaseCommand
from django.db import transaction
from oscar.core.loading import get_model
from oscar.test.utils import RequestFactory
from threadlocals.threadlocals import set_thread_variable

from ecommerce.courses.models import Course
from ecommerce.extensions.catalogue.utils import generate_sku

logger = logging.getLogger(__name__)
Line = get_model('order', 'Line')
Partner = get_model('partner', 'Partner')
StockRecord = get_model('partner', 'StockRecord')


HONOR_TO_AUDIT = 'honor_to_audit'
AUDIT_TO_HONOR = 'audit_to_honor'


class Command(BaseCommand):

    help = 'Convert a list of courses from honor to audit, or vice versa. For use with courses '
    'which already have enrollments.'

    option_list = BaseCommand.option_list + (
        make_option(
            '--access_token',
            action='store',
            dest='access_token',
            default=None,
            help='OAuth2 access token used to authenticate against the LMS APIs.'
        ),
        make_option(
            '--commit',
            action='store_true',
            dest='commit',
            default=False,
            help='Save the changes to the database. If this is not set,'
            ' migrated data will NOT be saved to the database.'
        ),
        make_option(
            '--partner',
            action='store',
            dest='partner',
            default=None,
            help='Partner code for the site whose courses should be updated.'
        ),
        make_option(
            '--direction',
            action='store',
            dest='direction',
            type='choice',
            choices=(HONOR_TO_AUDIT, AUDIT_TO_HONOR),
            default=HONOR_TO_AUDIT,
            help='Which direction to convert the courses. Options are honor_to_audit, or audit_to_honor.'
        )
    )

    def handle(self, *args, **options):
        self.options = options  # pylint: disable=attribute-defined-outside-init
        course_ids = map(unicode, args)

        self.access_token = options.get('access_token')  # pylint: disable=attribute-defined-outside-init
        if not self.access_token:
            logger.error('Cannot convert and publish a course without an access token.')
            return

        self.partner = Partner.objects.get(code__iexact=options['partner'])  # pylint: disable=attribute-defined-outside-init
        site = self.partner.siteconfiguration_set.first().site
        self._install_current_request(site)

        if options.get('direction') == HONOR_TO_AUDIT:
            conversion = self._convert_honor_to_audit
        else:
            conversion = self._convert_audit_to_honor

        for course_id in course_ids:
            try:
                with transaction.atomic():
                    course = Course.objects.get(id=course_id)
                    conversion(course)
                    if self.options.get('commit', False):
                        course.publish_to_lms(access_token=self.access_token)
                        logger.info('Course [%s] was saved to the database.', course.id)
                    else:
                        logger.info('Course [%s] was NOT saved to the database.', course.id)
                        raise Exception('Forced rollback.')
            except Exception:  # pylint: disable=broad-except
                logger.exception('Failed to convert [%s]!', course_id)

    def _convert_audit_to_honor(self, course):
        audit_seats = [
            seat for seat in course.seat_products
            if getattr(seat.attr, 'certificate_type', '') == ''
        ]
        if len(audit_seats) != 1:
            logger.error('Course [%s] has [%d] audit seats.', course.id, len(audit_seats))
            raise Exception

        audit_seat = audit_seats[0]

        audit_seat.title = course.get_course_seat_name('honor', False)
        audit_seat.attr.certificate_type = 'honor'
        audit_seat.save()

        stock_record = StockRecord.objects.get(product=audit_seat)
        stock_record.partner_sku = generate_sku(audit_seat, self.partner)
        stock_record.save()

        Line.objects.filter(stockrecord=stock_record).update(partner_sku=stock_record.partner_sku)

    def _convert_honor_to_audit(self, course):
        honor_seats = [
            seat for seat in course.seat_products
            if getattr(seat.attr, 'certificate_type', '') == 'honor'
        ]
        if len(honor_seats) != 1:
            logger.error('Course [%s] has [%d] honor seats.', course.id, len(honor_seats))
            raise Exception

        honor_seat = honor_seats[0]

        honor_seat.title = course.get_course_seat_name('', False)
        honor_seat.attr.certificate_type = ''
        honor_seat.save()

        stock_record = StockRecord.objects.get(product=honor_seat)
        stock_record.partner_sku = generate_sku(honor_seat, self.partner)
        stock_record.save()

        Line.objects.filter(stockrecord=stock_record).update(partner_sku=stock_record.partner_sku)

    def _install_current_request(self, site):
        """Install a thread-local fake request, setting its site. This is
        necessary since publishing to the LMS requires inspecting the
        'current request' and using its attached site to construct LMS
        urls. See ecommerce.core.url_utils for the implementation
        details.

        Arguments:
            site (Site): The site to set.

        Returns:
            None
        """
        request = RequestFactory()
        request.site = site
        set_thread_variable('request', request)
