"""HTTP endpoints for interacting with vouchers."""
import logging
from urlparse import urlparse

from django.http import Http404
from django.shortcuts import get_object_or_404
import django_filters
from opaque_keys.edx.keys import CourseKey
from oscar.core.loading import get_model
from requests.exceptions import ConnectionError, Timeout
from rest_framework import filters, status
from rest_framework.response import Response
from rest_framework_extensions.decorators import action
from slumber.exceptions import SlumberBaseException

from ecommerce.core.constants import DEFAULT_CATALOG_PAGE_SIZE
from ecommerce.core.url_utils import get_lms_url
from ecommerce.courses.models import Course
from ecommerce.courses.utils import get_course_info_from_lms
from ecommerce.coupons.utils import get_range_catalog_query_results
from ecommerce.extensions.api import serializers
from ecommerce.extensions.api.permissions import IsOffersOrIsAuthenticatedAndStaff
from ecommerce.extensions.api.v2.views import NonDestroyableModelViewSet


logger = logging.getLogger(__name__)
Product = get_model('catalogue', 'Product')
StockRecord = get_model('partner', 'StockRecord')
Voucher = get_model('voucher', 'Voucher')


class VoucherFilter(django_filters.FilterSet):
    """
    Filter for vouchers via query string parameters.
    Currently supports filtering via the voucher's code.
    """
    code = django_filters.CharFilter(name='code', lookup_type='exact')

    class Meta(object):
        model = Voucher
        fields = ('code', )


class VoucherViewSet(NonDestroyableModelViewSet):
    """ View set for vouchers. """
    queryset = Voucher.objects.all()
    serializer_class = serializers.VoucherSerializer
    permission_classes = (IsOffersOrIsAuthenticatedAndStaff, )
    filter_backends = (filters.DjangoFilterBackend, )
    filter_class = VoucherFilter

    @action(is_for_list=True, methods=['get'], endpoint='offers')
    def offers(self, request):
        """
        Preview the courses offered by the voucher.
        Paginated Response containing the list of course offers will be returned.
        ---
        parameters:
            - name: code
              description: Voucher code
              required: true
              type: string
              paramType: query
              multiple: false
        """
        code = request.GET.get('code', '')

        try:
            voucher = Voucher.objects.get(code=code)
        except Voucher.DoesNotExist:
            logger.error('Voucher with code %s not found.', code)
            return Response(status=status.HTTP_400_BAD_REQUEST)

        try:
            offers_data = self.get_offers(request, voucher)
        except (ConnectionError, SlumberBaseException, Timeout):
            logger.error('Could not get course information.')
            return Response(status=status.HTTP_400_BAD_REQUEST)
        except Http404:
            logger.error('Could not get product information for voucher with code %s.', code)
            return Response(status=status.HTTP_404_NOT_FOUND)

        next_page = offers_data['next']
        if next_page:
            next_page_query = urlparse(next_page).query
            offers_data['next'] = '{path}?{query}&code={code}'.format(
                code=code,
                path=request.path,
                query=next_page_query,
            )

        return Response(data=offers_data)

    def get_offers(self, request, voucher):
        """
        Get the course offers associated with the voucher.
        Arguments:
            request (HttpRequest): Request data
            voucher (Voucher): Oscar Voucher for which the offers are returned
        Returns:
            dict: Dictionary containing a link to the next page of Course Discovery results and
                  a List of course offers where each offer is represented as a dictionary
        """
        benefit = voucher.offers.first().benefit
        catalog_query = benefit.range.catalog_query
        next_page = None
        offers = []
        if catalog_query:
            response = get_range_catalog_query_results(
                limit=request.GET.get('limit', DEFAULT_CATALOG_PAGE_SIZE),
                offset=request.GET.get('offset'),
                query=catalog_query,
                site=request.site
            )
            next_page = response['next']
            course_ids = [result['key'] for result in response['results']]
            courses = Course.objects.filter(id__in=course_ids)
            products = Product.objects.filter(course__in=courses)
            stock_records = StockRecord.objects.filter(product__in=products)
            contains_verified_course = (benefit.range.course_seat_types == 'verified')

            for product in products:
                # Omit unavailable seats from the offer results so that one seat does not cause an
                # error message for every seat in the query result.
                if not request.strategy.fetch_for_product(product).availability.is_available_to_buy:
                    logger.info('%s is unavailable to buy. Omitting it from the results.', product)
                    continue
                course_id = product.course_id
                course_catalog_data = next(
                    (result for result in response['results'] if result['key'] == course_id),
                    None
                )

                try:
                    stock_record = stock_records.get(product__id=product.id)
                except StockRecord.DoesNotExist:
                    stock_record = None
                    logger.error('Stock Record for product %s not found.', product.id)

                try:
                    course = courses.get(id=course_id)
                except Course.DoesNotExist:
                    course = None
                    logger.error('Course %s not found.', course_id)

                if course_catalog_data and course and stock_record:
                    offers.append(self.get_course_offer_data(
                        benefit=benefit,
                        course=course,
                        course_info=course_catalog_data,
                        is_verified=contains_verified_course,
                        stock_record=stock_record,
                        voucher=voucher
                    ))
        else:
            product_range = voucher.offers.first().benefit.range
            products = product_range.all_products()
            if products:
                product = products[0]
            else:
                raise Http404()
            course_id = product.course_id
            course = get_object_or_404(Course, id=course_id)
            stock_record = get_object_or_404(StockRecord, product__id=product.id)
            course_info = get_course_info_from_lms(course_id)

            if course_info:
                course_info['image'] = {'src': get_lms_url(course_info['media']['course_image']['uri'])}

                offers.append(self.get_course_offer_data(
                    benefit=benefit,
                    course=course,
                    course_info=course_info,
                    is_verified=(course.type == 'verified'),
                    stock_record=stock_record,
                    voucher=voucher
                ))
        return {'next': next_page, 'results': offers}

    def get_course_offer_data(self, benefit, course, course_info, is_verified, stock_record, voucher):
        """
        Gets course offer data.
        Arguments:
            benefit (Benefit): Benefit associated with a voucher
            course (Course): Course associated with a voucher
            course_info (dict): Course info fetched from an API (LMS or Course Catalog)
            is_verified (bool): Indicated whether or not the voucher's range of products contains a verified course seat
            stock_record (StockRecord): Stock record associated with the course seat
            voucher (Voucher): Voucher for which the course offer data is being fetched
        Returns:
            dict: Course offer data
        """
        try:
            image = course_info['image']['src']
        except (KeyError, TypeError):
            image = ''
        return {
            'benefit': serializers.BenefitSerializer(benefit).data,
            'contains_verified': is_verified,
            'course_start_date': course_info.get('start', ''),
            'id': course.id,
            'image_url': image,
            'organization': CourseKey.from_string(course.id).org,
            'seat_type': course.type,
            'stockrecords': serializers.StockRecordSerializer(stock_record).data,
            'title': course.name,
            'voucher_end_date': voucher.end_datetime,
        }
