"""
Views containing APIs for Canvas integrated channel
"""

import requests
from rest_framework import generics
from rest_framework.exceptions import APIException, NotFound, ParseError
from rest_framework.response import Response
from six.moves.urllib.parse import urljoin

from django.apps import apps
from django.conf import settings
from django.core.exceptions import ValidationError

from enterprise.utils import get_enterprise_customer
from integrated_channels.canvas.models import CanvasEnterpriseCustomerConfiguration


class CanvasCompleteOAuthView(generics.ListAPIView):
    """
        **Use Cases**

            Retrieve and save a Canvas OAuth refresh token after an enterprise customer
            authorizes to integrated courses.

        **Example Requests**

            GET /canvas/oauth-complete?code=123abc&state=abc123

        **Query Parameters for GET**

            * code: The one time use string generated by the Canvas API used to fetch the
            access and refresh tokens for integrating with Canvas.

            * state: The user's enterprise customer uuid used to associate the incoming
            code with an enterprise configuration model.

        **Response Values**

            If the request for information about the course list is successful, an HTTP 200 "OK" response
            is returned.

            If code is not provided, a 400 error is returned.

            If state is not provided, a 400 error is returned.

            If the specified state is not valid or contained in the set of registered enterprise customers
            a 404 error is returned.

    """
    def get(self, request, *args, **kwargs):
        app_config = apps.get_app_config('canvas')
        canvas_oauth_token_path = app_config.oauth_token_auth_path

        # Check if Canvas encountered an error when generating the oauth code.
        canvas_request_error = request.GET.get('error')
        if canvas_request_error:
            raise APIException(
                'Canvas OAuth API encountered an error when generating client code- error: {} description: {}'.format(
                    canvas_request_error,
                    request.GET.get('error_description')
                )
            )

        # Retrieve the newly generated code and state (Enterprise user's ID)
        client_code = request.GET.get('code')
        state_uuid = request.GET.get('state')

        if not state_uuid:
            raise ParseError("Canvas Configuration uuid required to integrate with Canvas.")

        if not client_code:
            raise ParseError("Client code required to integrate with Canvas.")

        try:
            enterprise_config = CanvasEnterpriseCustomerConfiguration.objects.get(uuid=state_uuid)
        except (CanvasEnterpriseCustomerConfiguration.DoesNotExist, ValidationError):
            enterprise_config = None

        # old urls may use the enterprise customer uuid in place of the config uuid, so lets fallback
        if not enterprise_config:
            enterprise_customer = get_enterprise_customer(state_uuid)

            if not enterprise_customer:
                raise NotFound(f"No state data found for given uuid: {state_uuid}.")

            try:
                enterprise_config = CanvasEnterpriseCustomerConfiguration.objects.get(
                    enterprise_customer=enterprise_customer
                )
            except CanvasEnterpriseCustomerConfiguration.DoesNotExist as error:
                raise NotFound(
                    f"No Canvas configuration found for state: {state_uuid}"
                ) from error

        access_token_request_params = {
            'grant_type': 'authorization_code',
            'client_id': enterprise_config.client_id,
            'client_secret': enterprise_config.client_secret,
            'redirect_uri': settings.LMS_INTERNAL_ROOT_URL + "/canvas/oauth-complete",
            'code': client_code,
        }

        auth_token_url = urljoin(
            enterprise_config.canvas_base_url,
            canvas_oauth_token_path
        )

        auth_response = requests.post(auth_token_url, access_token_request_params)

        try:
            data = auth_response.json()
            refresh_token = data['refresh_token']
        except (KeyError, ValueError) as error:
            raise requests.RequestException(response=auth_response) from error

        enterprise_config.refresh_token = refresh_token
        enterprise_config.save()

        return Response()
