import os.path
import io
import datetime
from pathlib import Path
from PIL import Image

from django.core.exceptions import ValidationError 
from django.contrib.gis.db import models
from django.contrib.sitemaps import Sitemap
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.db.models.signals import m2m_changed
from django.utils.encoding import force_text
from django.utils.functional import cached_property
from django.urls import reverse
from django.apps import apps
from django.utils.html import format_html
from django.conf import settings
from django import forms

from resizeimage.imageexceptions import ImageSizeError

from taggit.managers import TaggableManager

from .utils import resize_image
from locations.models import Location


def get_upload_path(self, filename):
    return "images/original/%s/%s" % (datetime.datetime.today().strftime("%Y"), filename)


def get_vid_upload_path(self, filename):
    return "images/videos/%s/%s" % (datetime.datetime.today().strftime("%Y"), filename)


def get_audio_upload_path(self, filename):
    return "audio/%s/%s" % (datetime.datetime.today().strftime("%Y"), filename)


class LuxImageSize(models.Model):
    name = models.CharField(null=True, blank=True, max_length=30)
    slug = models.SlugField(null=True, blank=True)
    width = models.IntegerField(null=True, blank=True)
    height = models.IntegerField(null=True, blank=True)
    quality = models.IntegerField()

    class Meta:
        ordering = ('-name', 'id')
        verbose_name_plural = 'Image Sizes'

    def __str__(self):
        if self.width:
            size = self.width
        if self.height:
            size = self.height
        return "%s - %s" %(self.name, str(size))


class LuxImage(models.Model):
    image = models.FileField(blank=True, null=True, upload_to=get_upload_path)
    title = models.CharField(null=True, blank=True, max_length=300)
    alt = models.CharField(null=True, blank=True, max_length=300)
    photo_credit_source = models.CharField(null=True, blank=True, max_length=300)
    photo_credit_url = models.CharField(null=True, blank=True, max_length=300)
    caption = models.TextField(blank=True, null=True)
    pub_date = models.DateTimeField(default=datetime.datetime.now)
    exif_raw = models.TextField(blank=True, null=True)
    exif_aperture = models.CharField(max_length=50, blank=True, null=True)
    exif_make = models.CharField(max_length=50, blank=True, null=True)
    exif_model = models.CharField(max_length=50, blank=True, null=True)
    exif_exposure = models.CharField(max_length=50, blank=True, null=True)
    exif_iso = models.CharField(max_length=50, blank=True, null=True)
    exif_focal_length = models.CharField(max_length=50, blank=True, null=True)
    exif_lens = models.CharField(max_length=50, blank=True, null=True)
    exif_date = models.DateTimeField(blank=True, null=True)
    height = models.CharField(max_length=6, blank=True, null=True)
    width = models.CharField(max_length=6, blank=True, null=True)
    point = models.PointField(null=True, blank=True)
    location = models.ForeignKey("locations.Location", on_delete=models.CASCADE, null=True, blank=True)
    is_public = models.BooleanField(default=True)
    sizes = models.ManyToManyField(LuxImageSize, blank=True, related_name='sizes')
    sizes_cache = models.CharField(null=True, blank=True, max_length=300)

    class Meta:
        ordering = ('-pub_date', 'id')
        verbose_name_plural = 'Images'
        get_latest_by = 'pub_date'

    def __str__(self):
        if self.title:
            return "%s" % self.title
        else:
            return "%s" % self.pk

    def get_type(self):
        return str(self.__class__.__name__)

    def get_admin_image(self):
        for size in self.sizes.all():
            if size.width and size.width <= 820 or size.height and size.height <= 800:
                return self.get_image_url_by_size(size.name)

    def get_largest_image(self):
        t = []
        for size in self.sizes.all():
            t.append(size.width)
        t.sort(key=float)
        t.reverse()
        return self.get_image_path_by_size(t[0])

    @cached_property
    def image_name(self):
        return os.path.basename(self.image.path)[:-4]

    @cached_property
    def image_ext(self):
        return self.image.url[-3:]

    @cached_property
    def get_image_filename(self):
        return os.path.basename(self.image.path)

    @cached_property
    def get_srcset(self):
        srcset = ""
        length = len(self.sizes.all())
        print(length)
        loopnum = 1
        for size in self.sizes.all():
            srcset += "%s%s/%s_%s.%s %sw" % (settings.IMAGES_URL, self.pub_date.strftime("%Y"), self.image_name, size.name, self.image_ext, size.width)
            if loopnum < length:
                srcset += ", "
                loopnum = loopnum+1
        return srcset

    @cached_property
    def get_src(self):
        src = ""
        if self.sizes.all().count() > 1:
            src += "%s%s/%s_%s.%s" % (settings.IMAGES_URL, self.pub_date.strftime("%Y"), self.image_name, 'picwide-med', self.image_ext)
        else:
            src += "%s%s/%s_%s.%s" % (settings.IMAGES_URL, self.pub_date.strftime("%Y"), self.image_name, [size.name for size in self.sizes.all()], self.image_ext)
        return src

    def get_image_url_by_size(self, size="original"):
        if size == "original":
            return self.image.url
        if size == "admin_insert":
            return "images/%s/%s.%s" % (self.pub_date.strftime("%Y"), self.image_name, self.image_ext)
        else:
            luximagesize = LuxImageSize.objects.get(slug=size)
            #if luximagesize not in self.get_sizes:
                #self.sizes.add(luximagesize)
            return "%s%s/%s_%s.%s" % (settings.IMAGES_URL, self.pub_date.strftime("%Y"), self.image_name, luximagesize.slug, self.image_ext)

    def get_image_path_by_size(self, size="original"):
        if size == "original":
            return self.image.path
        else:
            luximagesize = LuxImageSize.objects.get(slug=size)
            return "%s%s/%s_%s.%s" % (settings.IMAGES_ROOT, self.pub_date.strftime("%Y"), self.image_name, luximagesize.slug, self.image_ext)

    @cached_property
    def get_thumbnail_url(self):
        return self.get_image_url_by_size("tn")

    def admin_thumbnail(self):
        return format_html('<a href="%s"><img src="%s"></a>' % (self.get_image_url_by_size(), self.get_image_url_by_size("tn")))
    admin_thumbnail.short_description = 'Thumbnail'

    @property
    def get_sizes(self):
        return self.sizes_cache.split(",")

    @property
    def get_previous_published(self):
        return self.get_previous_by_pub_date()

    @property
    def get_next_published(self):
        return self.get_next_by_pub_date()

    @property
    def get_previous_admin_url(self):
        n = self.get_previous_by_pub_date()
        return reverse('admin:%s_%s_change' %(self._meta.app_label,  self._meta.model_name),  args=[n.id] )

    @property
    def get_next_admin_url(self):
        model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name)
        try:
            return reverse('admin:%s_%s_change' %(self._meta.app_label,  self._meta.model_name),  args=[self.get_next_by_pub_date().pk] )
        except model.DoesNotExist:
            return ''

    @property
    def is_portait(self):
        if int(self.height) > int(self.width):
            return True
        else:
            return False 

    def save(self, *args, **kwargs):
        created = self.pk is None
        if created:
            self.sizes.add(LuxImageSize.objects.get(slug="tn"))
            img = Image.open(self.image.path)
            self.height = img.height
            self.width = img.width
        self.sizes_cache = ",".join(s.slug for s in self.sizes.all())
        super(LuxImage, self).save()


class LuxGallery(models.Model):
    title = models.CharField(blank=True, max_length=300)
    description = models.TextField(blank=True, null=True)
    slug = models.CharField(blank=True, max_length=300)
    thumb = models.ForeignKey(LuxImage, on_delete=models.CASCADE, related_name="gallery_thumb", null=True, blank=True)
    images = models.ManyToManyField(LuxImage)
    pub_date = models.DateTimeField(null=True)
    is_public = models.BooleanField(default=True)
    caption_style = models.CharField(blank=True, null=True, max_length=400)

    class Meta:
        ordering = ('-pub_date', 'id')
        verbose_name_plural = 'Galleries'
        get_latest_by = 'pub_date'

    def __str__(self):
        return self.title

    def get_main_image(self):
        return "%sgallery_thumbs/%s.jpg" % (settings.IMAGES_URL, self.id)

    def get_absolute_url(self):
        if self.is_public:
            return "/photos/galleries/%s" % (self.slug)
        else:
            return "/photos/galleries/private/%s" % (self.slug)

    def thumbs(self):
        lst = [x.image.name for x in self.images.all()]
        lst = ["<a href='/media/%s'>%s</a>" % (x, x.split('/')[-1]) for x in lst]
        return ', '.join(item for item in lst if item)
    thumbs.allow_tags = True


class LuxVideo(models.Model):
    video_mp4 = models.FileField(blank=True, null=True, upload_to=get_vid_upload_path)
    video_webm = models.FileField(blank=True, null=True, upload_to=get_vid_upload_path)
    video_poster = models.FileField(blank=True, null=True, upload_to=get_vid_upload_path)
    title = models.CharField(null=True, blank=True, max_length=300)
    pub_date = models.DateTimeField(default=datetime.datetime.now)
    youtube_url = models.CharField(null=True, blank=True, max_length=80)
    vimeo_url = models.CharField(null=True, blank=True, max_length=300)
    point = models.PointField(blank=True, null=True)
    location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True, null=True)

    def __str__(self):
        if self.title:
            return self.title
        else:
            return str(self.pk)

    def get_type(self):
        return str(self.__class__.__name__)

    class Meta:
        ordering = ('-pub_date', 'id')
        verbose_name_plural = 'Videos'
        verbose_name = 'Video'
        get_latest_by = 'pub_date'

    def save(self, *args, **kwargs):
        if not self.point:
            self.point = CheckIn.objects.latest().point
        try:
            self.location = Location.objects.filter(geometry__contains=self.point).get()
        except Location.DoesNotExist:
            raise forms.ValidationError("There is no location associated with that point, add it:  %sadmin/locations/location/add/" % (settings.BASE_URL))
        super(LuxVideo, self).save(*args, **kwargs)


class LuxAudio(models.Model):
    title = models.CharField(max_length=200)
    subtitle = models.CharField(max_length=200, blank=True)
    slug = models.SlugField(unique_for_date='pub_date', blank=True)
    body_html = models.TextField(blank=True)
    body_markdown = models.TextField(blank=True)
    pub_date = models.DateTimeField(default=datetime.datetime.now)
    mp3 = models.FileField(blank=True, null=True, upload_to=get_audio_upload_path)
    ogg = models.FileField(blank=True, null=True, upload_to=get_audio_upload_path)
    point = models.PointField(blank=True, null=True)
    location = models.ForeignKey(Location, on_delete=models.CASCADE, blank=True, null=True)

    class Meta:
        ordering = ('-pub_date',)
        verbose_name_plural = 'Audio'
        verbose_name = 'Audio'
        get_latest_by = 'pub_date'

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("prompt:detail", kwargs={"slug": self.slug})

    def get_image_url_by_size(self, size="original"):
        pass

    @property
    def get_previous_published(self):
        return self.get_previous_by_pub_date(status__exact=1)

    @property
    def get_previous_admin_url(self):
        n = self.get_previous_by_pub_date()
        return reverse('admin:%s_%s_change' % (self._meta.app_label,  self._meta.model_name),  args=[n.id])

    @property
    def get_next_published(self):
        return self.get_next_by_pub_date(status__exact=1)

    @property
    def get_next_admin_url(self):
        model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name)
        try:
            return reverse('admin:%s_%s_change' % (self._meta.app_label,  self._meta.model_name),  args=[self.get_next_by_pub_date().pk])
        except model.DoesNotExist:
            return ''

    def get_type(self):
        return str(self.__class__.__name__)

    def save(self, *args, **kwargs):
        md = render_images(self.body_markdown)
        self.body_html = markdown_to_html(md)
        if not self.point:
            self.point = CheckIn.objects.latest().point
        try:
            self.location = Location.objects.filter(geometry__contains=self.point).get()
        except Location.DoesNotExist:
            raise forms.ValidationError("There is no location associated with that point, add it:  %sadmin/locations/location/add/" % (settings.BASE_URL))
        super(LuxAudio, self).save(*args, **kwargs)



@receiver(m2m_changed, sender=LuxImage.sizes.through)
def update_photo_sizes(sender, instance, **kwargs):
    # update the local cache of sizes
    sizes = instance.sizes.all()
    if sizes:
        instance.sizes_cache = ",".join(s.slug for s in sizes)
        print(instance.sizes.all())
        for size in instance.get_sizes:
            print("SIZE is: %s" % size)
            # check each size and see if there's an image there already
            my_file = Path(instance.get_image_path_by_size(size))
            if not my_file.is_file():
                #file doesn't exist, so create it
                new_size = LuxImageSize.objects.get(slug=size)
                if new_size.width:
                    img = Image.open(instance.image.path)
                    try:
                        if new_size.width <= img.width:
                            resize_image(img, new_size.width, None, new_size.quality, instance.get_image_path_by_size(size))
                        else:
                            raise ValidationError({'items': ["Size is larger than source image"]})
                    except ImageSizeError:
                        m2m_changed.disconnect(update_photo_sizes, sender=LuxImage.sizes.through)
                        instance.sizes.remove(new_size)
                        m2m_changed.connect(update_photo_sizes, sender=LuxImage.sizes.through)
                if new_size.height:
                    img = Image.open(instance.image.path)
                    try:
                        if new_size.height <= img.height:
                            resize_image(img, None, new_size.height, new_size.quality, instance.get_image_path_by_size(size))
                        else:
                            pass
                    except ImageSizeError:
                        m2m_changed.disconnect(update_photo_sizes, sender=LuxImage.sizes.through)
                        instance.sizes.remove(new_size)
                        m2m_changed.connect(update_photo_sizes, sender=LuxImage.sizes.through)
            else:
                # file exists, might add something here to force it to do the above when I want
                print("file %s exists" % size)
                pass
    instance.save()