import hashlib
import json
from django.apps import apps
from django.conf import settings
from django.db import models
from django.template import Context, Template
from django.utils.timezone import now
from lxml import etree, html
from .places import PLACES, PLACE_CHOICES, STYLES


class Campaign(models.Model):
    name = models.CharField(max_length=255, help_text='Wewnętrzna nazwa kampanii')
    image = models.FileField('obraz', upload_to='annoy/banners/', blank=True)
    start = models.DateTimeField(null=True, blank=True)
    end = models.DateTimeField(null=True, blank=True)
    landing = models.CharField(blank=True, max_length=1024)
    target = models.IntegerField('cel', null=True, blank=True)
    progress = models.IntegerField('postęp', null=True, blank=True)
    priority = models.PositiveSmallIntegerField(
        'priorytet', default=0,
        help_text='Kampanie z wyższym priorytetem mają pierwszeństwo.')

    def __str__(self):
        return self.name

    @classmethod
    def get_active(cls):
        n = now()
        return cls.objects.exclude(start__gt=n).exclude(end__lt=n)

    def has_progress(self):
        return self.target is not None and self.start is not None and self.end is not None

    @property
    def progress_percent(self):
        if not self.target:
            return 0
        return (self.progress or 0) / self.target * 100

    @property
    def progress_percent_pretty(self):
        return int(self.progress_percent)

    def update_progress(self):
        if not self.start or not self.end or not self.target:
            return
        Schedule = apps.get_model('club', 'Schedule')
        PayUOrder = apps.get_model('club', 'PayUOrder')
        progress = PayUOrder.objects.filter(
            completed_at__gte=self.start,
            completed_at__lte=self.end,
        ).aggregate(c=models.Sum('schedule__amount'))['c'] or 0

        for schedule in Schedule.objects.filter(
                method='paypal',
                expires_at__gt=self.start
        ):
            progress += schedule.n_paypal_payments(self.start, self.end) * schedule.amount
        self.progress = progress
        self.save(update_fields=['progress'])

    @classmethod
    def update_all_progress(cls):
        n = now()
        for obj in cls.get_active().exclude(target=None):
            obj.update_progress()


class Banner(models.Model):
    place = models.SlugField('miejsce', choices=PLACE_CHOICES)
    campaign = models.ForeignKey(Campaign, models.PROTECT, null=True, blank=True)

    style = models.CharField(
        'styl', max_length=255, blank=True,
        choices=STYLES,
    )
    smallfont = models.BooleanField('mały font', default=False)
    text_color = models.CharField(max_length=10, blank=True)
    background_color = models.CharField(max_length=10, blank=True)
    action_label = models.CharField(
        'etykieta akcji',
        max_length=255, blank=True,
        help_text='Jeśli pusta, cały banner będzie służył jako link.'
    )
    open_label = models.CharField('etykieta otwierania', max_length=255, blank=True)
    close_label = models.CharField('etykieta zamykania', max_length=255, blank=True)
    text = models.TextField('tekst')
    image = models.FileField('obraz', upload_to='annoy/banners/', blank=True)
    video = models.FileField('video', upload_to='annoy/banners/', blank=True)
    url = models.CharField('URL', max_length=1024)
    priority = models.PositiveSmallIntegerField(
        'priorytet', default=0,
        help_text='Bannery z wyższym priorytetem mają pierwszeństwo.')
    since = models.DateTimeField('od', null=True, blank=True)
    until = models.DateTimeField('do', null=True, blank=True)
    books = models.ManyToManyField('catalogue.Book', blank=True)

    show_members = models.BooleanField('widoczny dla członków klubu', default=False)
    staff_preview = models.BooleanField('podgląd tylko dla zespołu', default=False)
    only_authenticated = models.BooleanField('tylko dla zalogowanych', default=False)

    class Meta:
        verbose_name = 'banner'
        verbose_name_plural = 'bannery'
        ordering = ('place', '-priority',)

    def __str__(self):
        return self.text

    def get_text(self):
        return Template(self.text).render(Context())

    def get_image(self):
        if self.campaign and self.campaign.image:
            return self.campaign.image
        else:
            return self.image

    def get_url(self):
        if self.url:
            return self.url
        if self.campaign:
            return self.campaign.landing

    def is_external(self):
        return (self.url and
                not self.url.startswith('/') and
                not self.url.startswith('https://wolnelektury.pl/')
                )

    @classmethod
    def choice(cls, place, request, exemptions=True, book=None):
        Membership = apps.get_model('club', 'Membership')

        if exemptions and hasattr(request, 'annoy_banner_exempt'):
            return cls.objects.none()

        if settings.DEBUG:
            assert place in PLACES, f"Banner place `{place}` must be defined in annoy.places."

        n = now()
        banners = cls.objects.filter(
            place=place
        ).exclude(
            campaign__start__gt=n
        ).exclude(
            campaign__end__lt=n
        ).exclude(
            since__gt=n
        ).exclude(
            until__lt=n
        ).order_by('-campaign__priority', '-priority', '?')

        if book is None:
            banners = banners.filter(books=None)
        else:
            banners = banners.filter(models.Q(books=None) | models.Q(books=book))
            
        
        if not request.user.is_authenticated:
            banners = banners.filter(only_authenticated=False)

        if not request.user.is_staff:
            banners = banners.filter(staff_preview=False)

        if Membership.is_active_for(request.user):
            banners = banners.filter(show_members=True)

        return banners



class DynamicTextInsert(models.Model):
    paragraphs = models.IntegerField('akapity')
    url = models.CharField(max_length=1024)

    class Meta:
        verbose_name = 'dynamiczna wstawka'
        verbose_name_plural = 'dynamiczne wstawki'
        ordering = ('paragraphs', )

    def __str__(self):
        return str(self.paragraphs)

    @classmethod
    def get_all(cls, request):
        Membership = apps.get_model('club', 'Membership')
        if Membership.is_active_for(request.user) and not request.user.is_staff:
            return cls.objects.none()
        return cls.objects.all()


    def choose(self):
        return self.dynamictextinserttext_set.order_by('?').first()


class DynamicTextInsertText(models.Model):
    insert = models.ForeignKey(DynamicTextInsert, models.CASCADE)
    own_colors = models.BooleanField(default=False)
    background_color = models.CharField(max_length=10, blank=True)
    text_color = models.CharField(max_length=10, blank=True)
    text = models.TextField('tekst')
    image = models.FileField(blank=True, upload_to='annoy/inserts/')


class MediaInsertSet(models.Model):
    file_format = models.CharField(max_length=8, choices=[
        ('epub', 'epub'),
        ('mobi', 'mobi'),
        ('pdf', 'pdf'),
        ])
    etag = models.CharField(max_length=64, blank=True)

    def update_etag(self):
        self.etag = hashlib.sha1(json.dumps(self.get_texts()).encode('utf-8')).hexdigest()
        self.save(update_fields=['etag'])

    def get_texts(self):
        return [
            etree.tostring(html.fromstring(t.text), encoding='unicode')
            for t in self.mediainserttext_set.all()
        ]

    @classmethod
    def get_for_format(cls, file_format):
        return cls.objects.filter(file_format=file_format).first()

    @classmethod
    def get_texts_for(cls, file_format):
        self = cls.get_for_format(file_format)
        if self is None:
            return []
        return self.get_texts()


class MediaInsertText(models.Model):
    media_insert_set = models.ForeignKey(MediaInsertSet, models.CASCADE)
    ordering = models.IntegerField()
    text = models.TextField()

    class Meta:
        ordering = ('ordering',)
