import json
from datetime import datetime
import os
from urllib.parse import urlencode

import piexif
from PIL import Image
from rest_framework import serializers
from rest_framework import status
from rest_framework.response import Response

from app.models import ImageUpload
from app.plugins import GlobalDataStore, get_site_settings, signals as plugin_signals
from app.plugins.views import TaskView
from app.plugins.worker import task

from webodm import settings
from django.dispatch import receiver

import requests

import logging

logger = logging.getLogger('app.logger')
ds = GlobalDataStore('openaerialmap')


def get_key_for(task_id, key):
    return "task_{}_{}".format(str(task_id), key)


def get_task_info(task_id):
    return ds.get_json(get_key_for(task_id, "info"), {
        'sharing': False,
        'shared': False,
        'error': ''
    })


def set_task_info(task_id, json):
    return ds.set_json(get_key_for(task_id, "info"), json)


@receiver(plugin_signals.task_removed, dispatch_uid="oam_on_task_removed")
@receiver(plugin_signals.task_completed, dispatch_uid="oam_on_task_completed")
def oam_cleanup(sender, task_id, **kwargs):
    # When a task is removed, simply remove clutter
    # When a task is re-processed, make sure we can re-share it if we shared a task previously

    logger.info("Cleaning up OAM datastore for task {}".format(str(task_id)))
    ds.del_key(get_key_for(task_id, "info"))


class Info(TaskView):
    def get(self, request, pk=None):
        task = self.get_and_check_task(request, pk)

        task_info = get_task_info(task.id)

        # Populate fields from first image in task
        img = ImageUpload.objects.filter(task=task).exclude(image__iendswith='.txt').first()
        img_path = os.path.join(settings.MEDIA_ROOT, img.path())
        im = Image.open(img_path)

        # TODO: for better data we could look over all images
        # and find actual end and start time
        # Here we're picking an image at random and assuming a one hour flight
        if not 'sensor' in task_info:
            task_info['endDate'] = datetime.utcnow().timestamp() * 1000
            task_info['sensor'] = ''
            task_info['title'] = task.name
            task_info['provider'] = get_site_settings().organization_name

            if 'exif' in im.info:
                exif_dict = piexif.load(im.info['exif'])
                if 'Exif' in exif_dict:
                    if piexif.ExifIFD.DateTimeOriginal in exif_dict['Exif']:
                        try:
                            parsed_date = datetime.strptime(exif_dict['Exif'][piexif.ExifIFD.DateTimeOriginal].decode('ascii'),
                                                          '%Y:%m:%d %H:%M:%S')
                            task_info['endDate'] = parsed_date.timestamp() * 1000
                        except ValueError:
                            # Ignore date field if we can't parse it
                            pass
                if '0th' in exif_dict:
                    if piexif.ImageIFD.Make in exif_dict['0th']:
                        task_info['sensor'] = exif_dict['0th'][piexif.ImageIFD.Make].decode('ascii').strip(' \t\r\n\0')

                    if piexif.ImageIFD.Model in exif_dict['0th']:
                        task_info['sensor'] = (task_info['sensor'] + " " + exif_dict['0th'][piexif.ImageIFD.Model].decode('ascii')).strip(' \t\r\n\0')

            task_info['startDate'] = task_info['endDate'] - 60 * 60 * 1000
            set_task_info(task.id, task_info)

        return Response(task_info, status=status.HTTP_200_OK)


class JSONSerializer(serializers.Serializer):
    oamParams = serializers.JSONField(help_text="OpenAerialMap share parameters (sensor, title, provider, etc.)")


class Share(TaskView):
    def post(self, request, pk=None):
        task = self.get_and_check_task(request, pk)

        serializer = JSONSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        oam_params = serializer['oamParams'].value

        task_info = get_task_info(task.id)
        task_info['sharing'] = True
        task_info['oam_upload_id'] = ''
        task_info['error'] = ''
        set_task_info(task.id, task_info)

        upload_orthophoto_to_oam.delay(task.id,
                                       task.get_asset_download_path('orthophoto.tif'),
                                       oam_params)

        return Response(task_info, status=status.HTTP_200_OK)


@task
def upload_orthophoto_to_oam(task_id, orthophoto_path, oam_params):
    # Upload to temporary central location since
    # OAM requires a public URL and not all WebODM
    # instances are public

    res = requests.post('https://www.webodm.org/oam/upload',
                        files=[
                            ('file', ('orthophoto.tif', open(orthophoto_path, 'rb'), 'image/tiff')),
                        ]).json()

    task_info = get_task_info(task_id)

    if 'url' in res:
        orthophoto_public_url = res['url']
        logger.info("Orthophoto uploaded to intermediary public URL " + orthophoto_public_url)

        # That's OK... we :heart: dronedeploy
        res = requests.post('https://api.openaerialmap.org/dronedeploy?{}'.format(urlencode(oam_params)),
                            json={
                                'download_path': orthophoto_public_url
                            }).json()

        if 'results' in res and 'upload' in res['results']:
            task_info['oam_upload_id'] = res['results']['upload']
            task_info['shared'] = True
        else:
            task_info['error'] = 'Could not upload orthophoto to OAM. The server replied: {}'.format(json.dumps(res))

            # Attempt to cleanup intermediate results
            requests.get('https://www.webodm.org/oam/cleanup/{}'.format(os.path.basename(orthophoto_public_url)))
    else:
        err_message = res['error'] if 'error' in res else json.dumps(res)
        task_info['error'] = 'Could not upload orthophoto to intermediate location: {}.'.format(err_message)

    task_info['sharing'] = False
    set_task_info(task_id, task_info)
