"""
BadgeHandler object-- used to award Badges to users who have completed courses.
"""
import hashlib
import logging
import mimetypes
from eventtracking import tracker
import requests
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext as _

from django.conf import settings
from django.core.urlresolvers import reverse
from lazy import lazy
from requests.packages.urllib3.exceptions import HTTPError
from certificates.models import BadgeAssertion, BadgeImageConfiguration
from student.models import CourseEnrollment
from xmodule.modulestore.django import modulestore

LOGGER = logging.getLogger(__name__)


class BadgeHandler(object):
    """
    The only properly public method of this class is 'award'. If an alternative object is created for a different
    badging service, the other methods don't need to be reproduced.
    """
    # Global caching dict
    badges = {}

    def __init__(self, course_key):
        self.course_key = course_key
        assert settings.BADGR_API_TOKEN

    @lazy
    def base_url(self):
        """
        Base URL for all API requests.
        """
        return "{}/v1/issuer/issuers/{}".format(settings.BADGR_BASE_URL, settings.BADGR_ISSUER_SLUG)

    @lazy
    def badge_create_url(self):
        """
        URL for generating a new Badge specification
        """
        return "{}/badges".format(self.base_url)

    def badge_url(self, mode):
        """
        Get the URL for a course's badge in a given mode.
        """
        return "{}/{}".format(self.badge_create_url, self.course_slug(mode))

    def assertion_url(self, mode):
        """
        URL for generating a new assertion.
        """
        return "{}/assertions".format(self.badge_url(mode))

    def course_slug(self, mode):
        """
        Slug ought to be deterministic and limited in size so it's not too big for Badgr.

        Badgr's max slug length is 255.
        """
        # Seven digits should be enough to realistically avoid collisions. That's what git services use.
        digest = hashlib.sha256(u"{}{}".format(unicode(self.course_key), unicode(mode))).hexdigest()[:7]
        base_slug = slugify(unicode(self.course_key) + u'_{}_'.format(mode))[:248]
        return base_slug + digest

    def log_if_raised(self, response, data):
        """
        Log server response if there was an error.
        """
        try:
            response.raise_for_status()
        except HTTPError:
            LOGGER.error(
                u"Encountered an error when contacting the Badgr-Server. Request sent to %s with headers %s.\n"
                u"and data values %s\n"
                u"Response status was %s.\n%s",
                repr(response.request.url), repr(response.request.headers),
                repr(data),
                response.status_code, response.body
            )
            raise

    def get_headers(self):
        """
        Headers to send along with the request-- used for authentication.
        """
        return {'Authorization': 'Token {}'.format(settings.BADGR_API_TOKEN)}

    def ensure_badge_created(self, mode):
        """
        Verify a badge has been created for this mode of the course, and, if not, create it
        """
        if self.course_slug(mode) in BadgeHandler.badges:
            return
        response = requests.get(self.badge_url(mode), headers=self.get_headers())
        if response.status_code != 200:
            self.create_badge(mode)
        BadgeHandler.badges[self.course_slug(mode)] = True

    @staticmethod
    def badge_description(course, mode):
        """
        Returns a description for the earned badge.
        """
        if course.end:
            return _(u'Completed the course "{course_name}" ({course_mode}, {start_date} - {end_date})').format(
                start_date=course.start.date(),
                end_date=course.end.date(),
                course_name=course.display_name,
                course_mode=mode,
            )
        else:
            return _(u'Completed the course "{course_name}" ({course_mode})').format(
                course_name=course.display_name,
                course_mode=mode,
            )

    def site_prefix(self):
        """
        Get the prefix for the site URL-- protocol and server name.
        """
        scheme = u"https" if settings.HTTPS == "on" else u"http"
        return u'{}://{}'.format(scheme, settings.SITE_NAME)

    def create_badge(self, mode):
        """
        Create the badge spec for a course's mode.
        """
        course = modulestore().get_course(self.course_key)
        image = BadgeImageConfiguration.image_for_mode(mode)
        # We don't want to bother validating the file any further than making sure we can detect its MIME type,
        # for HTTP. The Badgr-Server should tell us if there's anything in particular wrong with it.
        content_type, __ = mimetypes.guess_type(image.name)
        if not content_type:
            raise ValueError(
                "Could not determine content-type of image! Make sure it is a properly named .png file."
            )
        files = {'image': (image.name, image, content_type)}
        about_path = reverse('about_course', kwargs={'course_id': unicode(self.course_key)})
        data = {
            'name': course.display_name,
            'criteria': u'{}{}'.format(self.site_prefix(), about_path),
            'slug': self.course_slug(mode),
            'description': self.badge_description(course, mode)
        }
        result = requests.post(self.badge_create_url, headers=self.get_headers(), data=data, files=files)
        self.log_if_raised(result, data)

    def send_assertion_created_event(self, user, assertion):
        """
        Send an analytics event to record the creation of a badge assertion.
        """
        tracker.emit(
            'edx.badge.assertion.created', {
                'user_id': user.id,
                'course_id': unicode(self.course_key),
                'enrollment_mode': assertion.mode,
                'assertion_id': assertion.id,
                'assertion_image_url': assertion.data['image'],
                'assertion_json_url': assertion.data['json']['id'],
                'issuer': assertion.data['issuer'],
            }
        )

    def create_assertion(self, user, mode):
        """
        Register an assertion with the Badgr server for a particular user in a particular course mode for
        this course.
        """
        data = {
            'email': user.email,
            'evidence': self.site_prefix() + reverse(
                'cert_html_view', kwargs={'user_id': user.id, 'course_id': unicode(self.course_key)}
            ) + '?evidence_visit=1'
        }
        response = requests.post(self.assertion_url(mode), headers=self.get_headers(), data=data)
        self.log_if_raised(response, data)
        assertion, __ = BadgeAssertion.objects.get_or_create(course_id=self.course_key, user=user, mode=mode)
        assertion.data = response.json()
        assertion.save()
        self.send_assertion_created_event(user, assertion)

    def award(self, user):
        """
        Award a user a badge for their work on the course.
        """
        mode = CourseEnrollment.objects.get(user=user, course_id=self.course_key).mode
        self.ensure_badge_created(mode)
        self.create_assertion(user, mode)
