# -*- coding: utf-8 -*-

from glob import glob

from django.conf import settings
from django.db import transaction
from django.db.models import Q

from item.models import Item
from item.tasks import update_poster, update_timeline
from taskqueue.models import Task
from app.celery import app

from . import models
from . import extract
from . import external

_INSTANCE_KEYS = ('mtime', 'path')


def get_or_create_file(volume, f, user, item=None):
    try:
        file = models.File.objects.get(oshash=f['oshash'])
    except models.File.DoesNotExist:
        file = models.File()
        file.oshash = f['oshash']
        file.path = f['path']
        if item:
            file.item = item
        else:
            file.item = None  # gets pupulated later via update_info
        file.save()
    return file

def update_or_create_instance(volume, f):
    # instance with oshash exists
    instance = models.Instance.objects.filter(file__oshash=f['oshash'], volume=volume)
    if instance.count():
        instance = instance[0]
        updated = False
        for key in _INSTANCE_KEYS:
            if f[key] != getattr(instance, key):
                value = f[key]
                if key == 'path' and models.Instance.objects.filter(path=f[key], volume=volume).count():
                    value = f['oshash']
                setattr(instance, key, value)
                updated = True
        if updated:
            instance.ignore = False
            instance.save()
            instance.file.save()
    else:
        instance = models.Instance.objects.filter(path=f['path'], volume=volume)
        if instance.count():
            # same path, other oshash, keep path/item mapping, remove instance
            item = instance[0].file.item
            instance.delete()
        else:  # new instance
            item = None

        instance = models.Instance()
        instance.volume = volume
        instance.file = get_or_create_file(volume, f, volume.user, item) 
        for key in _INSTANCE_KEYS:
            setattr(instance, key, f[key])
        instance.save()
        instance.file.save()
        if instance.file.item:
            instance.file.item.update_wanted()
    return instance

@app.task(ignore_results=True, queue='default')
def update_files(user, volume, files):
    user = models.User.objects.get(username=user)
    volume, created = models.Volume.objects.get_or_create(user=user, name=volume)
    all_files = [f['oshash'] for f in files]

    # remove deleted files
    removed = models.Instance.objects.filter(volume=volume).exclude(file__oshash__in=all_files)
    ids = [i['public_id'] for i in Item.objects.filter(
           files__instances__in=removed.filter(file__selected=True)).distinct().values('public_id')]
    removed.delete()
    fix_path = []
    rebuild_timeline = set()
    for f in files:
        instance = update_or_create_instance(volume, f)
        if instance.path == f['oshash'] and f['path'] != f['oshash']:
            fix_path.append([instance, f['path']])
    for instance, path in fix_path:
        instance.path = path
        instance.save()
        instance.file.save()
        if instance.file.item:
            instance.file.item.update_wanted()
            rebuild_timeline.add(instance.file.item.public_id)
    for i in ids:
        i = Item.objects.get(public_id=i)
        i.update_selected()
    for i in rebuild_timeline:
        i = Item.objects.get(public_id=i)
        Task.start(i, user)
        update_timeline.delay(i.public_id)

@app.task(ignore_results=True, queue='default')
def update_info(user, info):
    user = models.User.objects.get(username=user)
    files = models.File.objects.filter(oshash__in=list(info))
    for f in files:
        f.update_info(info[f.oshash], user)
        f.save()
    for i in Item.objects.filter(files__in=files).distinct():
        i.update_selected()
        i.update_wanted()
        if not i.rendered:
            Task.start(i, user)
            update_timeline.delay(i.public_id)

@app.task(queue="encoding")
def process_stream(fileId):
    '''
        process uploaded stream
    '''
    file = models.File.objects.get(id=fileId)
    streams = file.streams.filter(source=None)
    models.File.objects.filter(id=fileId).update(encoding=True, queued=False, failed=False)
    if streams.count() > 0:
        stream = streams[0]
        stream.make_timeline()
        stream.extract_derivatives()
        file.refresh_from_db()
        file.encoding = False
        file.save()
    file.item.update_selected()
    if not file.item.rendered \
            and not file.item.files.exclude(id=fileId).filter(Q(queued=True) | Q(encoding=True)).count():
        file.item.update_timeline()
        update_poster(file.item.public_id)
    if file.item.rendered:
        file.item.save()
    models.File.objects.filter(id=fileId).update(encoding=False)
    Task.finish(file.item)
    return True

@app.task(queue="encoding")
def extract_stream(fileId):
    '''
        extract stream from direct upload
    '''
    models.File.objects.filter(id=fileId).update(encoding=True, queued=False, failed=False)
    file = models.File.objects.get(id=fileId)
    if file.data:
        config = settings.CONFIG['video']
        resolution = file.stream_resolution()
        stream, created = models.Stream.objects.get_or_create(
            file=file, resolution=resolution, format=config['formats'][0]
        )
        if created:
            file.extract_frames()
            stream.media.name = stream.path(stream.name())
            stream.encode()
            if stream.available:
                stream.make_timeline()
                stream.extract_derivatives()
                file.extract_tracks()
                file.refresh_from_db()
                if not file.item.rendered \
                        and not file.item.files.exclude(id=fileId).filter(Q(queued=True) | Q(encoding=True)).count():
                    file.item.update_timeline()
                    update_poster(file.item.public_id)
    models.File.objects.filter(id=fileId).update(encoding=False)
    Task.finish(file.item)

@app.task(queue="encoding")
def extract_derivatives(fileId, rebuild=False):
    file = models.File.objects.get(id=fileId)
    streams = file.streams.filter(source=None)
    if streams.count():
        streams[0].extract_derivatives(rebuild)
    return True

@app.task(queue="encoding")
def update_stream(id):
    s = models.Stream.objects.get(pk=id)
    if not glob("%s*" % s.timeline_prefix):
        s.make_timeline()
    if not s.color:
        s.cuts = tuple(extract.cuts(s.timeline_prefix))
        s.color = tuple(extract.average_color(s.timeline_prefix))
        s.save()

    s.file.selected = True
    s.file.save()
    s.file.item.update_timeline()
    # make sure all derivatives exist
    s.extract_derivatives()
    s.file.item.save()

    # update clips
    for c in s.file.item.clips.all():
        c.update_calculated_values()
        c.save()

@app.task(queue="encoding")
def download_media(item_id, url, referer=None):
    return external.download(item_id, url, referer)

@app.task(queue='default')
def move_media(data, user):
    from changelog.models import add_changelog
    from item.models import get_item, Item, ItemSort
    from item.utils import is_imdb_id
    from annotation.models import Annotation

    user = models.User.objects.get(username=user)

    if Item.objects.filter(public_id=data['item']).count() == 1:
        i = Item.objects.get(public_id=data['item'])
    else:
        data['public_id'] = data.pop('item').strip()
        if not is_imdb_id(data['public_id']):
            del data['public_id']
            if 'director' in data and isinstance(data['director'], str):
                if data['director'] == '':
                    data['director'] = []
                else:
                    data['director'] = data['director'].split(', ')
            i = get_item(data, user=user)
        else:
            i = get_item({'imdbId': data['public_id']}, user=user)
    try:
        i.sort
    except ItemSort.DoesNotExist:
        i.update_sort()

    changed = [i.public_id]
    old_item = None
    for f in models.File.objects.filter(oshash__in=data['ids']):
        if f.item.public_id != i.public_id and f.editable(user):
            if f.item.public_id not in changed:
                changed.append(f.item.public_id)
            old_item = f.item
            f.item = i
            f.save()

    if old_item:
        data['from'] = old_item.public_id

    # If all files are moved to a new item, keep annotations
    if old_item and old_item.files.count() == 0 and i.files.count() == len(data['ids']):
        for a in old_item.annotations.all().order_by('id'):
            a.item = i
            with transaction.atomic():
                a.set_public_id()
            Annotation.objects.filter(id=a.id).update(item=i, public_id=a.public_id)
        old_item.clips.all().update(item=i, sort=i.sort)

    for public_id in changed:
        c = Item.objects.get(public_id=public_id)
        if c.files.count() == 0 and settings.CONFIG['itemRequiresVideo']:
            c.delete()
        else:
            c.rendered = False
            c.save()
            if c.files.count():
                Task.start(c, user)
                update_timeline.delay(public_id)
    add_changelog({
        'user': user,
        'action': 'moveMedia'
    }, data, i.public_id)
    return {
        'item': i.public_id
    }
