"""This module contains the views of the ``sync`` app"""

import datetime
import json
import logging
import uuid

from django.contrib.auth import get_user_model
from django.core import serializers
from django.db.models import Q
from django.http import (
    HttpResponse, HttpResponseBadRequest,
    HttpResponseForbidden, JsonResponse
)
from django.utils.decorators import method_decorator
from django.utils.encoding import force_str
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View

from waffle.models import Switch

from mtp.apps.accounts.models import GroupRole
from mtp.apps.common.utils import *
from mtp.apps.documents.models import (
    Document,
    Note,
)
from mtp.apps.trainings.models import *
from mtp.apps.workshops.models import (
    Workshop
)
from .models import (
    ClientVersion, LocalServer, LocalServerConnection,
    ServerVersion, Syncable, Update,
)


logger = logging.getLogger('mtp.apps.sync')


@csrf_exempt
def download(request):
    # send all data
    try:
        LocalServer.objects.get(currently_update=True)
        return HttpResponseForbidden('')
    except:
        pass

    if request.method == 'POST' and 'updates_id' in request.POST:

        Update.synchronize()

        objects = serializers.deserialize('json', request.POST['updates_id'])
        update_ids = [update.object.pk for update in objects]
        ups = Update.objects.filter(synchronized=True).exclude(pk__in=update_ids).order_by('date')

        data = list(ups)
        for syncable_model in Syncable.get_syncable_models():
            objs = syncable_model.objects.filter(
                Q(last_update__in=ups) |
                Q(first_update__in=ups)
            )
            if hasattr(syncable_model, 'original'):
                objs = objs.order_by('-original')
            for obj in objs:
                data = data + obj.get_sync_objects()

        json_data = serializers.serialize('json', data)

        return HttpResponse(json_data)
    return HttpResponseBadRequest('')


def download_check(request):
    u = Update.objects.all().count()
    json = '%d' % u
    return HttpResponse(json)

@csrf_exempt
def ping(request):
    if request.method == 'POST':
        if 'mac' in request.POST:
            mac = request.POST['mac']
            update_count = int(request.POST['update_count'])
            try:
                ls = LocalServer.objects.get(mac=mac)
            except:
                ls = LocalServer(mac=mac)
                ls.save()

            try:
                lsc = ls.localserverconnection_set.filter(
                    date__gte=datetime.datetime.now() -
                    datetime.timedelta(
                        minutes=1,
                        seconds=40)).order_by('-date')[0]
                if lsc.update_count != update_count:
                    lsc = LocalServerConnection(
                        local_server=ls,
                        time=lsc.time + 1,
                        update_count=update_count)
                else:
                    lsc.time = lsc.time + 1
                    lsc.date = datetime.datetime.now()
            except:
                lsc = LocalServerConnection(local_server=ls, update_count=update_count)

            lsc.save()
    return HttpResponse('')


def save_db(request):
    if request.method == 'POST':
        filename = os.path.join(settings.PGDUMP_SAVE_ROOT, request.POST['filename'])
        handle_uploaded_file(request.FILES['file'], filename)

        return HttpResponse('')
    return HttpResponseBadRequest('')


def save_revision(request):
    if request.method == 'POST':
        filename = os.path.join(settings.REV_SAVE_ROOT, request.POST['filename'])
        handle_uploaded_file(request.FILES['file'], filename)

        return HttpResponse('')
    return HttpResponseBadRequest('')


def update_check(request):
    if request.method == 'POST':

        RESPONSE_CORRUPT = HttpResponse(
            json.dumps({'status': 'corrupt'}), content_type='application/json')
        RESPONSE_HEALTHY = HttpResponse(
            json.dumps({'status': 'corrupt'}), content_type='application/json')

        up = Update.objects.get(id=request.POST['update_id'])
        clientversion = ClientVersion.objects.filter(last_update=up).count()
        if clientversion != int(request.POST['clientversion']):
            return RESPONSE_CORRUPT

        serverversion = ServerVersion.objects.filter(last_update=up).count()
        if serverversion != int(request.POST['serverversion']):
            return RESPONSE_CORRUPT

        users = get_user_model().objects.filter(last_update=up).count()
        if users != int(request.POST['users']):
            return RESPONSE_CORRUPT

        down = DownloadableFile.objects.filter(last_update=up).count()
        if down != int(request.POST['down']):
            return RESPONSE_CORRUPT

        trainings = Training.objects.filter(last_update=up).count()
        if trainings != int(request.POST['trainings']):
            return RESPONSE_CORRUPT

        localservers = LocalServer.objects.filter(last_update=up).count()
        if localservers != int(request.POST['localservers']):
            return RESPONSE_CORRUPT

        trainingsessions = TrainingSession.objects.filter(last_update=up).count()
        if trainingsessions != int(request.POST['trainingsessions']):
            return RESPONSE_CORRUPT

        documentsfiles = DocumentFile.objects.filter(last_update=up).count()
        if documentsfiles != int(request.POST['documentsfiles']):
            return RESPONSE_CORRUPT

        documentscommentfiles = DocumentCommentFile.objects.filter(last_update=up).count()
        if documentscommentfiles != int(request.POST['documentscommentfiles']):
            return RESPONSE_CORRUPT

        documents = Document.objects.filter(last_update=up).count()
        if documents != int(request.POST['documents']):
            return RESPONSE_CORRUPT

        notes = Note.objects.filter(last_update=up).count()
        if notes != int(request.POST['notes']):
            return RESPONSE_CORRUPT

        # current_session = CurrentSession.objects.filter(last_update=up).count()

        workp = WorkshopProperties.objects.filter(last_update=up).count()
        if workp != int(request.POST['workp']):
            return RESPONSE_CORRUPT

        work = Workshop.objects.filter(last_update=up).count()
        if work != int(request.POST['work']):
            return RESPONSE_CORRUPT

        works = WorkshopStep.objects.filter(last_update=up)
        workt = WorkshopText.objects.filter(workshopstep_ptr__in=works).count()
        if workt != int(request.POST['workt']):
            return RESPONSE_CORRUPT

        worki = WorkshopImage.objects.filter(workshopstep_ptr__in=works).count()
        if worki != int(request.POST['worki']):
            return RESPONSE_CORRUPT

        workq = WorkshopQuestion.objects.filter(workshopstep_ptr__in=works).count()
        if workq != int(request.POST['workq']):
            return RESPONSE_CORRUPT

        if works.count() != int(request.POST['works']):
            return RESPONSE_CORRUPT

        workqa = WorkshopQuestionAnswer.objects.filter(last_update=up).count()
        if workqa != int(request.POST['workqa']):
            return RESPONSE_CORRUPT

        return RESPONSE_HEALTHY

@csrf_exempt
def upload(request):
    # send all updated note
    if request.method != 'POST':
        return HttpResponseBadRequest('Bad')

    mac = request.GET.get('mac', 'undefined')

    Update.synchronize()
    last_update = Update.new_update_for_sync()

    up_objects = serializers.deserialize('json', request.POST['json_stream'])
    for deserialized_object in up_objects:

        if isinstance(deserialized_object.object, Syncable):
            try:
                deserialized_object.object.last_update = last_update
                deserialized_object.object.update_locally = False
                if not deserialized_object.object.first_update:
                    deserialized_object.object.first_update = last_update

                if isinstance(deserialized_object.object, MTPUser):

                    object_pk = deserialized_object.object.pk
                    username = deserialized_object.object.username

                    test_qs = MTPUser.objects.filter(username=username).exclude(pk=object_pk)

                    if test_qs.exists():
                        # We have a new user with an existing username
                        # So we'll use a UUID as the username and save the wanted one in the
                        # ``old_username`` field
                        deserialized_object.object.old_username = username
                        deserialized_object.object.username = force_str(uuid.uuid4())

                        # Log this to be able to find this information
                        logger.warning(
                            'User %(username)s from mac=%(mac)s already existed, so was '
                            'renamed to %(uuid)s', {
                                'username': username,
                                'mac': mac,
                                'uuid': deserialized_object.object.username,
                            }
                        )

                elif isinstance(deserialized_object.object, GroupRole):

                    object_pk = deserialized_object.object.pk
                    user = deserialized_object.object.user
                    group = deserialized_object.object.group

                    try:
                        existing_grouprole = GroupRole.objects.exclude(pk=object_pk).get(
                            user=user,
                            group=group
                        )
                    except ObjectDoesNotExist:
                        pass  # The ``GroupRole`` don't exists yet, don't need to do anything
                    else:
                        # We have a new ``GroupRole``, but another one exists with the
                        # same user/group couple
                        # So we replace the deserialized object pk with the existing one,
                        # updating the existing grouprole instead of creating a new one
                        # (creating a new one won't work because of the unique keys)
                        deserialized_object.object.pk = existing_grouprole.pk

                deserialized_object.save()
            except:
                from django.db import connection
                connection._rollback()
        else:
            deserialized_object.save()

    combined = (last_update,)
    json_data = serializers.serialize('json', combined)

    return HttpResponse(json_data)


def upload_file(request):
    if request.method == 'POST':
        folder = os.path.join(settings.MEDIA_ROOT, request.POST['folder'])

        if not os.path.exists(folder):
            os.makedirs(folder)

        filename = os.path.join(folder, request.POST['filename'])
        handle_uploaded_file(request.FILES['file'], filename)

        return HttpResponse('')
    return HttpResponseBadRequest('')


class DownloadWaffle(View):
    """A view to download data related to the ``waffle`` app.

    Currently, only switches are returned.

    Request
    -------
    This view must be called in POST mode with post-data:
        waffle-download = 1

    Response
    --------
    json
        The response of this view is json formatted. It's a dict following this schema:

        {
            'waffle': {  # name of the "waffle" app
                'switch': [  # name of the "switch" model
                    {  # one dict per switch
                        "model": "waffle.switch",
                        "pk": 1,
                        "fields": {
                            "active": false,
                            "note": "",
                            "modified": "2015-06-20T13:51:48.812Z",
                            "name": "test1",
                            "created": "2015-06-02T12:12:12Z"
                        }
                    }
                ]
            }
        }

    """

    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        """Override the ``dispatch`` method to apply the ``csrf_exempt`` decorator."""

        return super().dispatch(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        """Check request validity and return a response with json data."""

        if 'download-waffle' not in request.POST:
            return HttpResponseForbidden('')

        return self.render_to_response(self.get_data())

    def get_data(self):
        """Get, convert to json, and return all switches."""

        switches = Switch.objects.all().order_by('name')
        json_switches = serializers.serialize('json', switches)
        return {'waffle': {'switch': json_switches}}

    def render_to_response(self, data, **response_kwargs):
        """Return a ``JsonResponse`` object with given data as content."""

        return JsonResponse(
            data,
            **response_kwargs
        )
