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

import os
import re
import subprocess
from glob import glob

from django.db import models
from django.db.models import Max
from django.contrib.auth import get_user_model
from django.conf import settings
from oxdjango.fields import JSONField

import ox

from archive import extract
from user.utils import update_groups
from user.models import Group

from . import managers

User = get_user_model()


def get_path(f, x):
    return f.path(x)

def get_icon_path(f, x):
    return get_path(f, 'icon.jpg')

def get_collectionview():
    return settings.CONFIG['user']['ui']['collectionView']

def get_collectionsort():
    return tuple(settings.CONFIG['user']['ui']['collectionSort'])

class Collection(models.Model):

    class Meta:
        unique_together = ("user", "name")

    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    user = models.ForeignKey(User, related_name='collections', on_delete=models.CASCADE)
    groups = models.ManyToManyField(Group, blank=True, related_name='collections')
    name = models.CharField(max_length=255)
    status = models.CharField(max_length=20, default='private')
    _status = ['private', 'public', 'featured']
    query = JSONField(default=lambda: {"static": True}, editable=False)
    type = models.CharField(max_length=255, default='static')
    description = models.TextField(default='')

    icon = models.ImageField(default=None, blank=True, upload_to=get_icon_path)

    view = models.TextField(default=get_collectionview)
    sort = JSONField(default=get_collectionsort, editable=False)

    poster_frames = JSONField(default=list, editable=False)

    #is through table still required?
    documents = models.ManyToManyField('document.Document', related_name='collections',
                                   through='CollectionDocument')

    numberofdocuments = models.IntegerField(default=0)
    subscribed_users = models.ManyToManyField(User, related_name='subscribed_collections')

    objects = managers.CollectionManager()

    def save(self, *args, **kwargs):
        if self.query.get('static', False):
            self.type = 'static'
        else:
            self.type = 'smart'
        if self.id:
            self.numberofdocuments = self.get_numberofdocuments(self.user)
        super(Collection, self).save(*args, **kwargs)

    @classmethod
    def get(cls, id):
        id = id.split(':')
        username = id[0]
        collectionname = ":".join(id[1:])
        return cls.objects.get(user__username=username, name=collectionname)

    def get_documents(self, user=None):
        if self.query.get('static', False):
            return self.documents
        from document.models import Document
        return Document.objects.find({'query': self.query}, user)

    def get_numberofdocuments(self, user=None):
        return self.get_documents(user).count()

    def add(self, document):
        q = self.documents.filter(id=document.id)
        if q.count() == 0:
            l = CollectionDocument()
            l.collection = self
            l.document = document
            l.index = CollectionDocument.objects.filter(collection=self).aggregate(Max('index'))['index__max']
            if l.index is None:
                l.index = 0
            else:
                l.index += 1
            l.save()

    def remove(self, document=None, documents=None):
        if document:
            CollectionDocument.objects.all().filter(document=document, collection=self).delete()
        if documents:
            CollectionDocument.objects.all().filter(document__id__in=documents, collection=self).delete()

    def __str__(self):
        return self.get_id()

    def get_id(self):
        return '%s:%s' % (self.user.username, self.name)

    def accessible(self, user):
        return self.user == user or self.status in ('public', 'featured')

    def editable(self, user):
        if not user or user.is_anonymous:
            return False
        if self.user == user or \
           self.groups.filter(id__in=user.groups.all()).count() > 0 or \
           user.is_staff or \
           user.profile.capability('canEditFeaturedCollections'):
            return True
        return False

    def edit(self, data, user):
        if 'groups' in data:
            groups = data.pop('groups')
            update_groups(self, groups)
        for key in data:
            if key == 'query' and not data['query']:
                setattr(self, key, {"static": True})
            elif key == 'query' and isinstance(data[key], dict):
                setattr(self, key, data[key])
            elif key == 'type':
                if data[key] == 'static':
                    self.query = {"static": True}
                    self.type = 'static'
                else:
                    self.type = 'smart'
                    if self.query.get('static', False):
                        self.query = {}
            elif key == 'status':
                value = data[key]
                if value not in self._status:
                    value = self._status[0]
                if value == 'private':
                    for user in self.subscribed_users.all():
                        self.subscribed_users.remove(user)
                    qs = Position.objects.filter(user=user, collection=self)
                    if qs.count() > 1:
                        pos = qs[0]
                        pos.section = 'personal'
                        pos.save()
                elif value == 'featured':
                    if user.profile.capability('canEditFeaturedCollections'):
                        pos, created = Position.objects.get_or_create(collection=self, user=user,
                                                                      section='featured')
                        if created:
                            qs = Position.objects.filter(user=user, section='featured')
                            pos.position = qs.aggregate(Max('position'))['position__max'] + 1
                            pos.save()
                        Position.objects.filter(collection=self).exclude(id=pos.id).delete()
                    else:
                        value = self.status
                elif self.status == 'featured' and value == 'public':
                    Position.objects.filter(collection=self).delete()
                    pos, created = Position.objects.get_or_create(collection=self,
                                                  user=self.user, section='personal')
                    qs = Position.objects.filter(user=self.user, section='personal')
                    pos.position = qs.aggregate(Max('position'))['position__max'] + 1
                    pos.save()
                    for u in self.subscribed_users.all():
                        pos, created = Position.objects.get_or_create(collection=self, user=u,
                                                                      section='public')
                        qs = Position.objects.filter(user=u, section='public')
                        pos.position = qs.aggregate(Max('position'))['position__max'] + 1
                        pos.save()

                self.status = value
            elif key == 'name':
                data['name'] = re.sub(' \[\d+\]$', '', data['name']).strip()
                if not data['name']:
                    data['name'] = "Untitled"
                name = data['name']
                num = 1
                while Collection.objects.filter(name=name, user=self.user).exclude(id=self.id).count() > 0:
                    num += 1
                    name = data['name'] + ' [%d]' % num
                self.name = name
            elif key == 'description':
                self.description = ox.sanitize_html(data['description'])

        if 'position' in data:
            pos, created = Position.objects.get_or_create(collection=self, user=user)
            pos.position = data['position']
            pos.section = 'featured'
            if self.status == 'private':
                pos.section = 'personal'
            pos.save()
        if 'posterFrames' in data:
            self.poster_frames = tuple(data['posterFrames'])
        if 'view' in data:
            self.view = data['view']
        if 'sort' in data:
            self.sort = tuple(data['sort'])
        self.save()
        if 'posterFrames' in data:
            self.update_icon()

    def json(self, keys=None, user=None):
        if not keys:
            keys = [
                'description',
                'editable',
                'groups',
                'id',
                'name',
                'posterFrames',
                'query',
                'status',
                'subscribed',
                'type',
                'user',
                'view',
            ]
        response = {}
        for key in keys:
            if key in ('items', 'documents'):
                response[key] = self.get_numberofdocuments(user)
            elif key == 'id':
                response[key] = self.get_id()
            elif key == 'user':
                response[key] = self.user.username
            elif key == 'groups':
                response[key] = [g.name for g in self.groups.all()]
            elif key == 'editable':
                response[key] = self.editable(user)
            elif key == 'query':
                if not self.query.get('static', False):
                    response[key] = self.query
            elif key == 'subscribers':
                response[key] = self.subscribed_users.all().count()
            elif key == 'subscribed':
                if user and not user.is_anonymous:
                    response[key] = self.subscribed_users.filter(id=user.id).exists()
            else:
                response[key] = getattr(self, {
                    'posterFrames': 'poster_frames'
                }.get(key, key))
        return response

    def path(self, name=''):
        h = "%07d" % self.id
        return os.path.join('collections', h[:2], h[2:4], h[4:6], h[6:], name)

    def update_icon(self):
        frames = []
        if not self.poster_frames:
            documents = self.get_documents(self.user).all()
            if documents.count():
                poster_frames = []
                for i in range(0, documents.count(), max(1, int(documents.count()/4))):
                    poster_frames.append({
                        'document': documents[int(i)].get_id(),
                    })
                self.poster_frames = tuple(poster_frames)
                self.save()
        for i in self.poster_frames:
            from document.models import Document
            if 'document' in i:
                qs = Document.objects.filter(id=ox.fromAZ(i['document']))
                if qs.count() > 0:
                    frame = qs[0].thumbnail(size=1024, page=i.get('page'))
                    frames.append(frame)
        self.icon.name = self.path('icon.jpg')
        icon = self.icon.path
        if frames:
            while len(frames) < 4:
                frames += frames
            folder = os.path.dirname(icon)
            ox.makedirs(folder)
            for f in glob("%s/icon*.jpg" % folder):
                os.unlink(f)
            cmd = [
                settings.COLLECTION_ICON,
                '-f', ','.join(frames),
                '-o', icon
            ]
            p = subprocess.Popen(cmd, close_fds=True)
            p.wait()
            self.save()

    def get_icon(self, size=16):
        path = self.path('icon%d.jpg' % size)
        path = os.path.join(settings.MEDIA_ROOT, path)
        if not os.path.exists(path):
            folder = os.path.dirname(path)
            ox.makedirs(folder)
            if self.icon and os.path.exists(self.icon.path):
                source = self.icon.path
                max_size = min(self.icon.width, self.icon.height)
            else:
                source = os.path.join(settings.STATIC_ROOT, 'jpg/list256.jpg')
                max_size = 256
            if size < max_size:
                extract.resize_image(source, path, size=size)
            else:
                path = source
        return path

class CollectionDocument(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    collection = models.ForeignKey(Collection, on_delete=models.CASCADE)
    index = models.IntegerField(default=0)
    document = models.ForeignKey('document.Document', on_delete=models.CASCADE)

    def __str__(self):
        return '%s in %s' % (self.document, self.collection)

class Position(models.Model):

    class Meta:
        unique_together = ("user", "collection", "section")

    collection = models.ForeignKey(Collection, related_name='position', on_delete=models.CASCADE)
    user = models.ForeignKey(User, related_name='collection_positions', on_delete=models.CASCADE)
    section = models.CharField(max_length=255)
    position = models.IntegerField(default=0)

    def __str__(self):
        return '%s/%s/%s' % (self.section, self.position, self.collection)

