""" View definition for threats app """
# We're overriding methods here that include certain args in the function signature which we are choosing to ignore
# We're also modelling requests using a view class, yet some of the methods do not use 'self'
# pylint: disable=unused-argument,no-self-use
from __future__ import unicode_literals

import json

from django.conf import settings
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response

from threats.controller import search_artifacts, SearchContext
from threats.serializers import ThreatSerializer


def process_threat_results(matching_threats, context):
    """ prepare response from threat results """
    threats = [ThreatSerializer(threat).data for threat in matching_threats]

    response_data = {
        "id": context.id,
        "hits": threats,
    }
    status_code = status.HTTP_200_OK
    if context.pending_searches:
        response_data["retry_secs"] = 60
        status_code = status.HTTP_303_SEE_OTHER

    return Response(response_data, status_code)


class ScanView(APIView):

    """ Scan service for matching threat artifacts """

    def options(self, request):  # pylint: disable=no-self-use
        """ Get the options exposed by this service instance """
        return Response({'upload_file': settings.SUPPORT_UPLOAD_FILE})

    def post(self, request):  # pylint: disable=no-self-use
        """ receive artifact information, and locate all threats that match it """
        response = None

        def check_required_value(
                arg_name,
                dictionary=request.data,
                description_format='Required parameter "{arg_name}" was not provided'):
            """
            Check that a required value has been provided by the client

            Arguments:
                arg_name - name of the value that should be present
                description_format - format specification of descriptive message
            """
            if arg_name not in dictionary:
                raise ValueError(
                    "{} - {}".format(
                        description_format.format(arg_name=arg_name),
                        json.dumps(dictionary),
                    )
                )

        try:
            context_data = request.data
            if request.content_type.startswith('multipart/form-data'):
                check_required_value('artifact')
                context_data = json.loads(request.data['artifact'])
                check_required_value('type', context_data)
                if context_data['type'] == "file.content":
                    check_required_value('file', request.FILES)
                    context_data['value'] = request.FILES['file']
                check_required_value('value', context_data)
            else:
                check_required_value('type')
                check_required_value('value')

            # Generate a search context
            context = SearchContext(context_data)
            matching_threats = search_artifacts(context_data['type'], context_data['value'], True, context=context)
            response = process_threat_results(matching_threats, context)
            context.save()

        except ValueError as ve:
            response = Response({'error': '{}'.format(str(ve))}, status.HTTP_400_BAD_REQUEST)

        # Keep broad exception handler, as this is an entry point to the service interface
        except Exception as ex:  # pylint: disable=broad-except
            response = Response({'error': '{}'.format(str(ex))}, status.HTTP_500_INTERNAL_SERVER_ERROR)

        return response


class RetrieveView(APIView):  # pylint: disable=too-few-public-methods

    """ Retrieve next set of results for value """

    def get(self, request, request_id):
        """ Fetch results corresponding to given request id """
        request_id = request_id.rstrip('/')
        context = SearchContext.load(request_id)
        if context is None:
            # We return "no content" because we have none
            # 404 will cause the custom threat service caller to stop processing
            return Response({"error": "'{}' was not found".format(request_id)}, status.HTTP_204_NO_CONTENT)

        try:
            matching_threats = search_artifacts(context.type, context.value)
            response = process_threat_results(matching_threats, context)

        # Keep broad exception handler, as this is an entry point to the service interface
        except Exception as ex:  # pylint: disable=broad-except
            response = Response({'error': '{}'.format(str(ex))}, status.HTTP_500_INTERNAL_SERVER_ERROR)

        return response