"""
This module provides date summary blocks for the Course Info
page. Each block gives information about a particular
course-run-specific date which will be displayed to the user.
"""
import crum
import datetime

from babel.dates import format_timedelta

from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.functional import cached_property
from django.utils.translation import get_language, to_locale, ugettext_lazy
from django.utils.translation import ugettext as _
from lazy import lazy
from pytz import utc

from course_modes.models import CourseMode, get_cosmetic_verified_display_price
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
from openedx.core.djangoapps.certificates.api import can_show_certificate_available_date_field
from openedx.core.djangolib.markup import HTML, Text
from openedx.features.course_experience import CourseHomeMessages, UPGRADE_DEADLINE_MESSAGE
from student.models import CourseEnrollment

from .context_processor import user_timezone_locale_prefs


class DateSummary(object):
    """Base class for all date summary blocks."""

    # A consistent representation of the current time.
    _current_time = None

    @property
    def current_time(self):
        """
        Returns a consistent current time.
        """
        if self._current_time is None:
            self._current_time = datetime.datetime.now(utc)
        return self._current_time

    @property
    def css_class(self):
        """
        The CSS class of this summary. Indicates the type of information
        this summary block contains, and its urgency.
        """
        return ''

    @property
    def title(self):
        """The title of this summary."""
        return ''

    @property
    def description(self):
        """The detail text displayed by this summary."""
        return ''

    def register_alerts(self, request, course):
        """
        Registers any relevant course alerts given the current request.
        """
        pass

    @property
    def date(self):
        """This summary's date."""
        return None

    @property
    def date_format(self):
        """
        The format to display this date in. By default, displays like Jan
        01, 2015.
        """
        return u'%b %d, %Y'

    @property
    def link(self):
        """The location to link to for more information."""
        return ''

    @property
    def link_text(self):
        """The text of the link."""
        return ''

    def __init__(self, course, user, course_id=None):
        self.course = course
        self.user = user
        self.course_id = course_id or self.course.id

    @property
    def relative_datestring(self):
        """
        Return this block's date in a human-readable format. If the date
        is None, returns the empty string.
        """
        if self.date is None:
            return ''
        locale = to_locale(get_language())
        delta = self.date - self.current_time
        try:
            relative_date = format_timedelta(delta, locale=locale)
        # Babel doesn't have translations for Esperanto, so we get
        # a KeyError when testing translations with
        # ?preview-lang=eo. This should not happen with any other
        # languages. See https://github.com/python-babel/babel/issues/107
        except KeyError:
            relative_date = format_timedelta(delta)
        date_has_passed = delta.days < 0
        # Translators: 'absolute' is a date such as "Jan 01,
        # 2020". 'relative' is a fuzzy description of the time until
        # 'absolute'. For example, 'absolute' might be "Jan 01, 2020",
        # and if today were December 5th, 2020, 'relative' would be "1
        # month".
        date_format = _(u"{relative} ago - {absolute}") if date_has_passed else _(u"in {relative} - {absolute}")
        return date_format.format(
            relative=relative_date,
            absolute='{date}',
        )

    @property
    def is_enabled(self):
        """
        Whether or not this summary block should be shown.

        By default, the summary is only shown if its date is in the
        future.
        """
        if self.date is not None:
            return self.current_time.date() <= self.date.date()
        return False

    def deadline_has_passed(self):
        """
        Return True if a deadline (the date) exists, and has already passed.
        Returns False otherwise.
        """
        deadline = self.date
        return deadline is not None and deadline <= self.current_time

    @property
    def time_remaining_string(self):
        """
        Returns the time remaining as a localized string.
        """
        locale = to_locale(get_language())
        return format_timedelta(self.date - self.current_time, locale=locale)

    def date_html(self, date_format='shortDate'):
        """
        Returns a representation of the date as HTML.

        Note: this returns a span that will be localized on the client.
        """
        locale = to_locale(get_language())
        user_timezone = user_timezone_locale_prefs(crum.get_current_request())['user_timezone']
        return HTML(
            '<span class="date localized-datetime" data-format="{date_format}" data-datetime="{date_time}"'
            ' data-timezone="{user_timezone}" data-language="{user_language}">'
            '</span>'
        ).format(
            date_format=date_format,
            date_time=self.date,
            user_timezone=user_timezone,
            user_language=locale,
        )

    @property
    def long_date_html(self):
        """
        Returns a long representation of the date as HTML.

        Note: this returns a span that will be localized on the client.
        """
        return self.date_html(date_format='shortDate')

    @property
    def short_time_html(self):
        """
        Returns a short representation of the time as HTML.

        Note: this returns a span that will be localized on the client.
        """
        return self.date_html(date_format='shortTime')

    def __repr__(self):
        return u'DateSummary: "{title}" {date} is_enabled={is_enabled}'.format(
            title=self.title,
            date=self.date,
            is_enabled=self.is_enabled
        )


class TodaysDate(DateSummary):
    """
    Displays today's date.
    """
    css_class = 'todays-date'
    is_enabled = True

    # The date is shown in the title, no need to display it again.
    def get_context(self):
        context = super(TodaysDate, self).get_context()
        context['date'] = ''
        return context

    @property
    def date(self):
        return self.current_time

    @property
    def title(self):
        return 'current_datetime'


class CourseStartDate(DateSummary):
    """
    Displays the start date of the course.
    """
    css_class = 'start-date'
    title = ugettext_lazy('Course Starts')

    @property
    def date(self):
        return self.course.start

    def register_alerts(self, request, course):
        """
        Registers an alert if the course has not started yet.
        """
        is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id)
        if not course.start or not is_enrolled:
            return
        days_until_start = (course.start - self.current_time).days
        if course.start > self.current_time:
            if days_until_start > 0:
                CourseHomeMessages.register_info_message(
                    request,
                    Text(_(
                        "Don't forget to add a calendar reminder!"
                    )),
                    title=Text(_("Course starts in {time_remaining_string} on {course_start_date}.")).format(
                        time_remaining_string=self.time_remaining_string,
                        course_start_date=self.long_date_html,
                    )
                )
            else:
                CourseHomeMessages.register_info_message(
                    request,
                    Text(_("Course starts in {time_remaining_string} at {course_start_time}.")).format(
                        time_remaining_string=self.time_remaining_string,
                        course_start_time=self.short_time_html,
                    )
                )


class CourseEndDate(DateSummary):
    """
    Displays the end date of the course.
    """
    css_class = 'end-date'
    title = ugettext_lazy('Course End')

    @property
    def is_enabled(self):
        return self.date is not None

    @property
    def description(self):
        if self.current_time <= self.date:
            mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_id)
            if is_active and CourseMode.is_eligible_for_certificate(mode):
                return _('To earn a certificate, you must complete all requirements before this date.')
            else:
                return _('After this date, course content will be archived.')
        return _('This course is archived, which means you can review course content but it is no longer active.')

    @property
    def date(self):
        return self.course.end

    def register_alerts(self, request, course):
        """
        Registers an alert if the end date is approaching.
        """
        is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id)
        if not course.start or self.current_time < course.start or not is_enrolled:
            return
        days_until_end = (course.end - self.current_time).days
        if course.end > self.current_time and days_until_end <= settings.COURSE_MESSAGE_ALERT_DURATION_IN_DAYS:
            if days_until_end > 0:
                CourseHomeMessages.register_info_message(
                    request,
                    Text(self.description),
                    title=Text(_('This course is ending in {time_remaining_string} on {course_end_date}.')).format(
                        time_remaining_string=self.time_remaining_string,
                        course_end_date=self.long_date_html,
                    )
                )
            else:
                CourseHomeMessages.register_info_message(
                    request,
                    Text(self.description),
                    title=Text(_('This course is ending in {time_remaining_string} at {course_end_time}.')).format(
                        time_remaining_string=self.time_remaining_string,
                        course_end_time=self.short_time_html,
                    )
                )


class CertificateAvailableDate(DateSummary):
    """
    Displays the certificate available date of the course.
    """
    css_class = 'certificate-available-date'
    title = ugettext_lazy('Certificate Available')

    @property
    def active_certificates(self):
        return [
            certificate for certificate in self.course.certificates.get('certificates', [])
            if certificate.get('is_active', False)
        ]

    @property
    def is_enabled(self):
        return (
            can_show_certificate_available_date_field(self.course) and
            self.has_certificate_modes and
            self.date is not None and
            self.current_time <= self.date and
            len(self.active_certificates) > 0
        )

    @property
    def description(self):
        return _('Day certificates will become available for passing verified learners.')

    @property
    def date(self):
        return self.course.certificate_available_date

    @property
    def has_certificate_modes(self):
        return any([
            mode.slug for mode in CourseMode.modes_for_course(
                course_id=self.course.id, include_expired=True
            ) if mode.slug != CourseMode.AUDIT
        ])

    def register_alerts(self, request, course):
        """
        Registers an alert close to the certificate delivery date.
        """
        is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id)
        if not is_enrolled or not self.is_enabled or course.end > self.current_time:
            return
        if self.date > self.current_time:
            CourseHomeMessages.register_info_message(
                request,
                Text(_(
                    'If you have earned a certificate, you will be able to access it {time_remaining_string}'
                    ' from now. You will also be able to view your certificates on your {learner_profile_link}.'
                )).format(
                    time_remaining_string=self.time_remaining_string,
                    learner_profile_link=HTML(
                        '<a href="{learner_profile_url}">{learner_profile_name}</a>'
                    ).format(
                        learner_profile_url=reverse('learner_profile', kwargs={'username': request.user.username}),
                        learner_profile_name=_('Learner Profile'),
                    ),
                ),
                title=Text(_('We are working on generating course certificates.'))
            )


class VerifiedUpgradeDeadlineDate(DateSummary):
    """
    Displays the date before which learners must upgrade to the
    Verified track.
    """
    css_class = 'verified-upgrade-deadline'
    title = ugettext_lazy('Verification Upgrade Deadline')
    description = ugettext_lazy(
        'You are still eligible to upgrade to a Verified Certificate! '
        'Pursue it to highlight the knowledge and skills you gain in this course.'
    )
    link_text = ugettext_lazy('Upgrade to Verified Certificate')

    @property
    def link(self):
        return EcommerceService().upgrade_url(self.user, self.course_id)

    @cached_property
    def enrollment(self):
        return CourseEnrollment.get_enrollment(self.user, self.course_id)

    @property
    def is_enabled(self):
        """
        Whether or not this summary block should be shown.

        By default, the summary is only shown if it has date and the date is in the
        future and the user's enrollment is in upsell modes
        """
        is_enabled = super(VerifiedUpgradeDeadlineDate, self).is_enabled
        if not is_enabled:
            return False

        enrollment_mode = None
        is_active = None

        if self.enrollment:
            enrollment_mode = self.enrollment.mode
            is_active = self.enrollment.is_active

        # Return `true` if user is not enrolled in course
        if enrollment_mode is None and is_active is None:
            return True

        # Show the summary if user enrollment is in which allow user to upsell
        return is_active and enrollment_mode in CourseMode.UPSELL_TO_VERIFIED_MODES

    @lazy
    def date(self):
        deadline = None

        if self.enrollment:
            deadline = self.enrollment.upgrade_deadline

        return deadline

    def register_alerts(self, request, course):
        """
        Registers an alert if the verification deadline is approaching.
        """
        upgrade_price = get_cosmetic_verified_display_price(course)
        if not UPGRADE_DEADLINE_MESSAGE.is_enabled(course.id) or not self.is_enabled or not upgrade_price:
            return
        days_left_to_upgrade = (self.date - self.current_time).days
        if self.date > self.current_time and days_left_to_upgrade <= settings.COURSE_MESSAGE_ALERT_DURATION_IN_DAYS:
            CourseHomeMessages.register_info_message(
                request,
                Text(_(
                    'In order to qualify for a certificate, you must meet all course grading '
                    'requirements, upgrade before the course deadline, and successfully verify '
                    'your identity on {platform_name} if you have not done so already.{button_panel}'
                )).format(
                    platform_name=settings.PLATFORM_NAME,
                    button_panel=HTML(
                        '<div class="message-actions">'
                        '<a class="btn btn-upgrade"'
                        'data-creative="original_message" data-position="course_message"'
                        'href="{upgrade_url}">{upgrade_label}</a>'
                        '</div>'
                    ).format(
                        upgrade_url=self.link,
                        upgrade_label=Text(_('Upgrade ({upgrade_price})')).format(upgrade_price=upgrade_price),
                    )
                ),
                title=Text(_(
                    "Don't forget, you have {time_remaining_string} left to upgrade to a Verified Certificate."
                )).format(
                    time_remaining_string=self.time_remaining_string,
                )
            )


class VerificationDeadlineDate(DateSummary):
    """
    Displays the date by which the user must complete the verification
    process.
    """

    @property
    def css_class(self):
        base_state = 'verification-deadline'
        if self.deadline_has_passed():
            return base_state + '-passed'
        elif self.must_retry():
            return base_state + '-retry'
        else:
            return base_state + '-upcoming'

    @property
    def link_text(self):
        return self.link_table[self.css_class][0]

    @property
    def link(self):
        return self.link_table[self.css_class][1]

    @property
    def link_table(self):
        """Maps verification state to a tuple of link text and location."""
        return {
            'verification-deadline-passed': (_('Learn More'), ''),
            'verification-deadline-retry': (_('Retry Verification'), reverse('verify_student_reverify')),
            'verification-deadline-upcoming': (
                _('Verify My Identity'),
                reverse('verify_student_verify_now', args=(self.course_id,))
            )
        }

    @property
    def title(self):
        if self.deadline_has_passed():
            return _('Missed Verification Deadline')
        return _('Verification Deadline')

    @property
    def description(self):
        if self.deadline_has_passed():
            return _(
                "Unfortunately you missed this course's deadline for"
                " a successful verification."
            )
        return _(
            "You must successfully complete verification before"
            " this date to qualify for a Verified Certificate."
        )

    @lazy
    def date(self):
        return VerificationDeadline.deadline_for_course(self.course_id)

    @lazy
    def is_enabled(self):
        if self.date is None:
            return False
        (mode, is_active) = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_id)
        if is_active and mode == 'verified':
            return self.verification_status in ('expired', 'none', 'must_reverify')
        return False

    @lazy
    def verification_status(self):
        """Return the verification status for this user."""
        return SoftwareSecurePhotoVerification.user_status(self.user)[0]

    def must_retry(self):
        """Return True if the user must re-submit verification, False otherwise."""
        return self.verification_status == 'must_reverify'
