"""
Module containing API functions for the CCXCon
"""

import logging
import urlparse

from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
from django.http import Http404
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
from rest_framework.status import (
    HTTP_200_OK,
    HTTP_201_CREATED,
)

from lms.djangoapps.courseware.courses import get_course_by_id
from lms.djangoapps.instructor.access import list_with_level
from openedx.core.djangoapps.models.course_details import CourseDetails
from student.models import anonymous_id_for_user
from .models import CCXCon

log = logging.getLogger(__name__)


CCXCON_COURSEXS_URL = '/api/v1/coursexs/'
CCXCON_TOKEN_URL = '/o/token/'
CCXCON_REQUEST_TIMEOUT = 30


class CCXConnServerError(Exception):
    """
    Custom exception to be raised in case there is any
    issue with the request to the server
    """


def is_valid_url(url):
    """
    Helper function used to check if a string is a valid url.

    Args:
        url (str): the url string to be validated

    Returns:
        bool: whether the url is valid or not
    """
    validate = URLValidator()
    try:
        validate(url)
        return True
    except ValidationError:
        return False


def get_oauth_client(server_token_url, client_id, client_secret):
    """
    Function that creates an oauth client and fetches a token.
    It intentionally doesn't handle errors.

    Args:
        server_token_url (str): server URL where to get an authentication token
        client_id (str): oauth client ID
        client_secret (str): oauth client secret

    Returns:
        OAuth2Session: an instance of OAuth2Session with a token
    """
    if not is_valid_url(server_token_url):
        return
    client = BackendApplicationClient(client_id=client_id)
    oauth_ccxcon = OAuth2Session(client=client)
    oauth_ccxcon.fetch_token(
        token_url=server_token_url,
        client_id=client_id,
        client_secret=client_secret,
        timeout=CCXCON_REQUEST_TIMEOUT
    )
    return oauth_ccxcon


def course_info_to_ccxcon(course_key):
    """
    Function that gathers informations about the course and
    makes a post request to a CCXCon with the data.

    Args:
        course_key (CourseLocator): the master course key
    """

    try:
        course = get_course_by_id(course_key)
    except Http404:
        log.error('Master Course with key "%s" not found', unicode(course_key))
        return
    if not course.enable_ccx:
        log.debug('ccx not enabled for course key "%s"', unicode(course_key))
        return
    if not course.ccx_connector:
        log.debug('ccx connector not defined for course key "%s"', unicode(course_key))
        return
    if not is_valid_url(course.ccx_connector):
        log.error(
            'ccx connector URL "%s" for course key "%s" is not a valid URL.',
            course.ccx_connector, unicode(course_key)
        )
        return
    # get the oauth credential for this URL
    try:
        ccxcon = CCXCon.objects.get(url=course.ccx_connector)
    except CCXCon.DoesNotExist:
        log.error('ccx connector Oauth credentials not configured for URL "%s".', course.ccx_connector)
        return

    # get an oauth client with a valid token

    oauth_ccxcon = get_oauth_client(
        server_token_url=urlparse.urljoin(course.ccx_connector, CCXCON_TOKEN_URL),
        client_id=ccxcon.oauth_client_id,
        client_secret=ccxcon.oauth_client_secret
    )

    # get the entire list of instructors
    course_instructors = list_with_level(course, 'instructor')
    # get anonymous ids for each of them
    course_instructors_ids = [anonymous_id_for_user(user, course_key) for user in course_instructors]
    # extract the course details
    course_details = CourseDetails.fetch(course_key)

    payload = {
        'course_id': unicode(course_key),
        'title': course.display_name,
        'author_name': None,
        'overview': course_details.overview,
        'description': course_details.short_description,
        'image_url': course_details.course_image_asset_path,
        'instructors': course_instructors_ids
    }
    headers = {'content-type': 'application/json'}

    # make the POST request
    add_course_url = urlparse.urljoin(course.ccx_connector, CCXCON_COURSEXS_URL)
    resp = oauth_ccxcon.post(
        url=add_course_url,
        json=payload,
        headers=headers,
        timeout=CCXCON_REQUEST_TIMEOUT
    )

    if resp.status_code >= 500:
        raise CCXConnServerError('Server returned error Status: %s, Content: %s', resp.status_code, resp.content)
    if resp.status_code >= 400:
        log.error("Error creating course on ccxcon. Status: %s, Content: %s", resp.status_code, resp.content)
    # this API performs a POST request both for POST and PATCH, but the POST returns 201 and the PATCH returns 200
    elif resp.status_code != HTTP_200_OK and resp.status_code != HTTP_201_CREATED:
        log.error('Server returned unexpected status code %s', resp.status_code)
    else:
        log.debug('Request successful. Status: %s, Content: %s', resp.status_code, resp.content)
