"""This module contains the saba api client"""

import json
import logging

from requests import api
from requests.exceptions import HTTPError

from django.conf import settings
from django.utils.timezone import now

logger = logging.getLogger('saba.client')  # pylint: disable=invalid-name


class SabaApiException(Exception):
    """Exception used in the ``SabaApiClient`` class

    Attributes
    ----------
    http_status: int
        The response status code
    error_message: str
        The Saba error message

    """

    def __init__(self, http_status, error_message, *args, **kwargs):
        self.http_status = http_status
        self.error_message = error_message

        super().__init__(*args, **kwargs)

    def __str__(self):
        return 'http status: %(http_status)s - %(error_message)s' % {
            'http_status': self.http_status,
            'error_message': self.error_message,
        }


class SabaApiClient:
    """SABA API client"""

    DELIVERY_TYPES = {
        'exempt': 'eqcat000000000001671'
    }

    def __init__(self, api_base_url, username, password):
        """Create a Saba client API object.

        Build the base url prefix and get the auth certificate to use.

        Parameters
        ----------
        api_base_url: str
            The base url of the api (ex:https://nissanacademysb-api.sabacloud.com)
        username: str
            The Saba username to authenticate.
        password: str
            The password for that username.

        """

        self._session = api
        self.api_base_url = api_base_url
        self._certificate = self._get_certificate(username, password)

    def _internal_call(self, method, url, headers, params=None, payload=None):
        """Call the specified endpoint of the api

        Parameters
        ----------
        method: str
            HTTP method to use ("GET", "POST", etc)
        url: str
            Endpoint to call ("/v1/login", etc)
        headers: dict
            The request's headers
        params: dict
            The request's params
        payload: dict
            The request's payload

        Returns
        -------
        json
            The json response object

        Raises
        ------
        SabaApiException
            If the response got a wrong status code

        """

        args = dict(params=params)
        url = self.api_base_url + url

        if payload:
            # If we got a payload (json), update the data and the headers accordingly
            args["data"] = json.dumps(payload)
            headers['Content-type'] = 'application/json'

        response = self._session.request(method, url, headers=headers, **args)
        logger.debug('REQUEST: [%s]%s %s', method, url, payload)

        try:
            response.raise_for_status()
        except HTTPError:

            # Do we have an error message ?
            try:
                error_message = response.json()['errorMessage']
            except (ValueError, IndexError):
                error_message = 'Empty error message'

            # Raise a nice exception with the response from Saba
            raise SabaApiException(
                response.status_code,
                error_message,
            )

        if len(response.text) > 0:
            results = response.json()

            logger.debug('RESPONSE: %s', results)

            return results
        else:
            return None

    def _authenticated_call(self, method, url, params=None, payload=None):
        """Call the specified endpoint of the api with an authenticated call

        Parameters
        ----------
        method: str
            HTTP method to use (ex: "GET", "POST", etc)
        url: str
            Endpoint to call (ex: "/v1/login")
        params: dict
            The request's params
        payload: dict
            The request's payload

        """

        headers = {
            "SabaCertificate": self._certificate,
        }

        return self._internal_call(method, url, headers, params, payload)

    def _get_certificate(self, username, password):
        """Get the certificate to authenticate our requests amongst the SABA Api

        This method is called when instantiating a new ``SabaApiClient``.

        Parameters
        ----------
        username: str
            The Saba user's username.
        password: str
            The Saba user's password.

        Returns
        -------
        str
            The Saba certificate

        """

        headers = {
            'user': username,
            'password': password
        }

        return self._internal_call('GET', '/v1/login', headers)['certificate']

    def user_details(self, username, fields, user_type='external'):
        """Get the details of the user with the given username

        Parameters
        ----------
        username: str
            The user's username
        fields: list
            The fields of the user's profile we intend to extract
        user_type: str
            The type of user, can be 'external' or 'internal', default to 'external'

        Examples
        --------
        >>> saba_client.user_details('SED3049', ['id'])
        {'id': 'persn000000000018835'}

        """

        return self._authenticated_call(
            'GET',
            '/v1/people/username=%(username)s:(%(fields)s)?type=%(user_type)s' % {
                'username': username,
                'fields': ','.join(fields),
                'user_type': user_type
            }
        )

    def get_enrollment_details(self, username, course_id, date):
        """Get the details of an enrollment for a given user

        Parameters
        ----------
        username: str
            The user's username
        course_id: str
            The course of the class
        date: date
            The expected date of the class of this enrollment.

        Notes
        -----
        - A ``Course`` is like our ``Training``.
        - A ``Class`` is like our ``TrainingSession``.
        - An ``Enrollment`` is an inscription of a user in a ``Class``.

        Raises
        ------
        SabaApiException
            If no enrollments or multiple enrollments was found for the given criteria

        Returns
        -------
        json
            A json representation of an enrollment

        Examples
        --------
        >>> d = date(2015, 1, 12)
        >>> saba_client.get_enrollment_details('persn000000000018835', 'cours000000000005983', d)
        {'Current_Count': None,
         'Max_Count': None,
         'class_id': {'displayName': 'ASSESSMENTS_2016_0027',
          'id': 'class000000000025819'},
         'delivery_id': {'displayName': 'Instructor-Led',
          'id': 'eqcat000000000000004'},
         'elements_to_complete': 0,
         'end_date': '2016-01-19',
         'id': 'regdw000000000343961',
         'is_no_show': False,
         'is_walk_in': False,
         'offering_action_id': {'displayName': '00804061',
          'id': 'ofapr000000000832402'},
         'offering_temp_id': {'displayName': 'NE Sales Assessment',
          'id': 'cours000000000005983'},
         'offering_temp_name': 'NE Sales Assessment',
         'offering_temp_no': '2015-11-sale-assess',
         'offering_temp_version': '',
         'reg_no': '00313581',
         'start_date': '2016-01-19',
         'status': '100',
         'status_description': 'Registered',
         'student_email': '',
         'student_id': {'displayName': 'Theodoros Adamidis',
          'id': 'persn000000000018835'},
         'student_number': 'SED3049',
         'student_username': 'SED3049',
         'wlist_on': None,
         'wlist_priority': 5}

        """

        response = self._authenticated_call(
            'GET',
            '/v1/learning/enroll/search',
            {
                'q': '(student_id==%s,offering_temp_id==%s,start_date==%s)' % (
                    username, course_id, str(date)
                ),
                'includeDetails': 'true'
            }
        )

        enrollments = response['results']

        # We should have only one enrollment
        if not enrollments:
            raise SabaApiException(
                404,
                'Could not found enrollment for user %s / course %s taking on %s' % (
                    username, course_id, date
                )
            )
        if len(enrollments) > 1:
            raise SabaApiException(
                404,
                'Found Multiple enrollments for user %s / course %s taking on %s' % (
                    username, course_id, date
                )
            )

        return enrollments[0]

    def mark_attendance(self, user_id, offering_id):
        """Mark the attendance of a learner

        Parameters
        ----------
        user_id: str
            The SABA user id we want to mark the attendance
        offering_id: str
            The SABA offering id we want to mark the user attendance for

        """

        self._authenticated_call(
            'POST',
            '/v1/attendance',
            payload={
                'offering': {'id': offering_id},
                'learner': {'id': user_id},
                'session_no': 1,
            }
        )

    def complete_enrollment(self, registration_id, status, score):
        """Mark an enrollment complete

        Parameters
        ----------
        registration_id: str
            The registration id
        status: int
            The status of the trainee regarding this enrollment
            Must be one of the following: 200 (Successful), 400 (Unsuccessful)
        score: int
            The score obtained by the trainee during this enrollment

        """

        self._authenticated_call(
            'PUT',
            '/v1/learning/enroll/%s/status' % registration_id,
            payload={
                '@type': 'com.saba.learning.services.registration.OfferingCompletionInfo',
                'status': status,
                'score': score
            }
        )

    def create_transcript(self, user_id, course_id, delivery_type):
        """Create a new transcript

        Parameters
        ----------
        user_id: str
            The SABA user id of this new transcript
        course_id: str
            The SABA course id of this new transcript
        delivery_type: str, one of the ``DELIVERY_TYPE``
            The delivery type of the new transcript

        """

        self._authenticated_call(
            'POST',
            '/v1/transcript/',
            payload={
                '@type': 'com.saba.offering.adhoclearning.AdHocLearningTranscriptDetail',
                'deliveryType': self.DELIVERY_TYPES[delivery_type],
                'courseId': course_id,
                'deliveredBy': settings.SABA_API_USERNAME_INTERNAL_ID,
                'learners': [
                    'java.util.ArrayList',
                    [
                        {
                            '@type': 'com.saba.offering.adhoclearning.LearnerInfo',
                            'learnerId': user_id,
                            'grade': None,
                            'score': [
                                'java.math.BigDecimal',
                                0
                            ],
                            'completedOnDate': {
                                '@type': 'date',
                                'time': str(now().date())
                            },
                            'addedToProfileOn': {
                                '@type': "date",
                                'time': str(now().date())
                            }
                        }
                    ]
                ]
            }
        )

    def course_activity(self, source_user_id, assignee_user_id, course_id):
        """Assign the given course to the given user

        Parameters
        ----------
        source_user_id: str
            The id of the Saba user the assignment come from
        assignee_user_id: str
            The id of the Saba user to assign to
        course_id: str
            The id of the Saba course to assign

        """

        self._authenticated_call(
            'POST',
            '/v1/courseactivity/',
            payload={
                'source': {
                    'id': source_user_id
                },
                'assignee': {
                    'id': assignee_user_id
                },
                'activityDefinition': {
                    'id': course_id
                }
            }
        )
