from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated

from .serializers import *


class RestListBase(generics.ListAPIView):
    authentication_classes = [BasicAuthentication]
    permission_classes = [IsAuthenticated]

    # filter mechanism for all list views to
    # provide the archive view
    def get_queryset(self):
        active_flag = self.request.QUERY_PARAMS.get('a', 'True')
        active_flag = active_flag == 'True'
        return self.model.objects.all().filter(is_active=active_flag)


class SamplesRestList(RestListBase):
    def get_queryset(self):
        qs = super(SamplesRestList, self).get_queryset()
        from django.db.models import Q

        if self.request.GET.get('control', 'False') == 'True':
            # for the ctrl sw, use a different serializer, which also delivers things
            # like the reference gas concentration
            self.serializer_class = SamplesSerializerControlSW
            # base class returns filtered by active, starting from this filter out
            # those who either have no project or where the (active) project is set to analysis
            return qs.filter(Q(project__isnull=True) | (Q(project__is_active=True)))
        else:
            return qs


# ctrl sw
class DeviceWithCalibrationSetRest(generics.ListAPIView):
    serializer_class = Olfactometer
    queryset = Olfactometer.objects.all()

    authentication_classes = [BasicAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request, *args, **kwargs):
        ser = OlfactometersSerializer(self.get_queryset(), many=True)
        calib_ser = CalibrationsSerializer(many=False)
        from rest_framework.serializers import CharField

        calib_ser.fields['json_data'] = CharField()
        ser.fields['calibration'] = calib_ser
        return Response(ser.data)


# updating the existing part for a measurement, including the rounds and single results
class PutMeasurement(generics.UpdateAPIView):
    authentication_classes = []
    permission_classes = []
    serializer_class = MeasurementFullSerializer
    model = Measurement


# django rest framework does not support nested writes, hence we need to manually
# fiddle around a bit
class PostMeasurement(generics.CreateAPIView):
    authentication_classes = [BasicAuthentication]
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):
        try:
            # grab operator from auth
            current_user_pk = request.user.pk

            # deserialize and validate measurement
            mser = MeasurementSerializer(data=request.DATA)

            if not mser.is_valid():
                raise Exception("failed to validate measurement object, {0}".format(mser.errors))

            round_list = get_rounds(request.DATA['rounds'], current_user_pk)

            # putting it all together
            # keep everything in one transaction, so in case something breaks we won't
            # end up with some half valid rubbish
            from django.db import transaction
            from django.core.urlresolvers import reverse

            with transaction.atomic():

                measurement = mser.object
                measurement.save()
                for r in round_list:
                    r[0].measurement = measurement
                    r[0].save()
                    for sr in r[1]:
                        sr.round = r[0]
                        sr.save()
                    r[0].save()

                # if it was a reference measurement, we need to update the qualified status for everyone
                from panelists.qualification import update_panel_qualification
                if measurement.sample.cylinder is not None:
                    print("updating qualification for reference measurement (POST)")
                    update_panel_qualification()

                return Response("OK", status=status.HTTP_201_CREATED)

        except KeyError as key_error:
            return Response('key error: {0}'.format(str(key_error)), status=status.HTTP_400_BAD_REQUEST)
        except Exception as excep:
            return Response(str(excep), status=status.HTTP_400_BAD_REQUEST)


def get_rounds(data, user_pk):
    # same for the rounds and their single results
    round_list = list()

    for r_data in data:
        # fill in the operator per round
        r_data['operator'] = user_pk

        round_ser = MeasurementRoundSerializer(data=r_data)
        if round_ser.is_valid():

            single_results_list = list()
            # deserialize single results
            for sr_data in r_data['results']:
                single_result_ser = RoundSingleResultSerializer(data=sr_data)
                if not single_result_ser.is_valid():
                    raise Exception(
                        "failed to validate single result object, {0}".format(single_result_ser.errors))
                else:
                    single_results_list.append(single_result_ser.object)

            round_list.append((round_ser.object, single_results_list,))
        else:
            raise Exception("failed to validate round objects: {0}".format(round_ser.errors))

    return round_list


# for existing measurements, the new rounds get posted as a standalone
# request
class PostNewRounds(generics.CreateAPIView):
    authentication_classes = [BasicAuthentication]
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):
        try:

            # find measurement this new rounds belong to from query string
            m_pk = request.GET.get('m', None)

            if m_pk is None:
                raise Exception("no measurement specified in query string.")

            measurement = Measurement.objects.get(id=m_pk)

            # grab operator from auth
            current_user_pk = request.user.pk

            round_list = get_rounds(request.DATA, current_user_pk)

            # putting it all together
            # keep everything in one transaction, so in case something breaks we won't
            # end up with some half valid rubbish
            from django.db import transaction
            from django.core.urlresolvers import reverse

            transaction.set_autocommit(False)

            # lookup measurement object from query
            # NOTE: we don't allow do extend on existing reference gas
            # measurements, therefore there is no need to put stuff into
            # any history table at this point
            for r in round_list:
                r[0].measurement = measurement
                r[0].save()
                for sr in r[1]:
                    sr.round = r[0]
                    sr.save()
                r[0].save()

            # only by adding rounds, the finished_at may change, update the value now
            try:
                measurement.finished_at = round_list[-1][0].finished_at
                measurement.save()
            except:
                pass

            transaction.commit()
            transaction.set_autocommit(True)

            # if it was a reference measurement, we need to update the qualified status for everyone
            from panelists.qualification import update_panel_qualification
            if measurement.sample.cylinder is not None:
                print("updating qualification for reference measurement (PUT)")
                update_panel_qualification()

            return Response("OK", status=status.HTTP_201_CREATED)

        except Measurement.DoesNotExist:
            return Response("measurement object not found.", status=status.HTTP_400_BAD_REQUEST)
        except KeyError as key_error:
            return Response('key error: {0}'.format(str(key_error)), status=status.HTTP_400_BAD_REQUEST)
        except Exception as excep:
            return Response(str(excep), status=status.HTTP_400_BAD_REQUEST)


# continue measurements, the idea is to keep things simple (as always :)
# and only allow the user to continue a measurement for a sample which was selected
# as a session sample beforehand, so we just need to
class IncompleteMeasurements(generics.ListAPIView):
    serializer_class = MeasurementFullSerializer

    def get_queryset(self):
        sid = self.request.QUERY_PARAMS.get('sid', None)
        try:
            return Measurement.objects.filter(sample__id=sid)
        except Measurement.DoesNotExist:
            return None

