"""News app models."""
import uuid

from django.conf import settings
from django.contrib.contenttypes import fields as generic
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import HStoreField
from django.db import models
from django.db.models import Case, When
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from rest_framework.reverse import reverse

from main.models import Carousel
from rating.models import Rating, ViewCount
from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, HasTagsMixin,
                          ProjectBaseMixin, GalleryMixin, IntermediateGalleryModelMixin,
                          FavoritesMixin)
from utils.querysets import TranslationQuerysetMixin
from datetime import datetime


class Agenda(ProjectBaseMixin, TranslatedFieldsMixin):
    """News agenda model"""
    start_datetime = models.DateTimeField(default=timezone.now, editable=True,
                                          verbose_name=_('Start datetime'))
    end_datetime = models.DateTimeField(default=timezone.now, editable=True,
                                          verbose_name=_('End datetime'))
    address = models.ForeignKey('location.Address', blank=True, null=True,
                                default=None, verbose_name=_('address'),
                                on_delete=models.SET_NULL)
    event_name = TJSONField(blank=True, null=True, default=None,
                         verbose_name=_('event name'),
                         help_text='{"en-GB":"some text"}')
    content = TJSONField(blank=True, null=True, default=None,
                         verbose_name=_('content'),
                         help_text='{"en-GB":"some text"}')


class NewsBanner(ProjectBaseMixin, TranslatedFieldsMixin):
    """News banner model"""
    title = TJSONField(blank=True, null=True, default=None,
                       verbose_name=_('title'),
                       help_text='{"en-GB":"some text"}')
    image_url = models.URLField(verbose_name=_('Image URL path'),
                                blank=True, null=True, default=None)
    content_url = models.URLField(verbose_name=_('Content URL path'),
                                  blank=True, null=True, default=None)


class NewsType(models.Model):
    """NewsType model."""

    name = models.CharField(_('name'), max_length=250)
    tag_categories = models.ManyToManyField('tag.TagCategory',
                                            related_name='news_types')

    class Meta:
        """Meta class."""

        verbose_name_plural = _('news types')
        verbose_name = _('news type')

    def __str__(self):
        """Overrided __str__ method."""
        return self.name


class NewsQuerySet(TranslationQuerysetMixin):
    """QuerySet for model News"""

    def sort_by_start(self):
        """Return qs sorted by start DESC"""
        return self.order_by('-publication_date', '-publication_time')

    def rating_value(self):
        return self.annotate(rating=models.Count('ratings__ip', distinct=True))

    def with_base_related(self):
        """Return qs with related objects."""
        return self.select_related('news_type', 'country').prefetch_related('tags', 'tags__translation')

    def with_extended_related(self):
        """Return qs with related objects."""
        return self.select_related('created_by', 'agenda', 'banner')

    def by_type(self, news_type):
        """Filter News by type"""
        return self.filter(news_type__name=news_type)

    def by_tags(self, tags):
        return self.filter(tags__in=tags)

    def by_country_code(self, code):
        """Filter collection by country code."""
        return self.filter(country__code=code)

    def recipe_news(self):
        """Returns news with tag 'cook' qs."""
        return self.filter(tags__value=News.RECIPES_TAG_VALUE)

    def international_news(self):
        """Returns only international news qs."""
        return self.filter(tags__value=News.INTERNATIONAL_TAG_VALUE)

    def published(self):
        """Return only published news"""
        now = timezone.now()
        date_now = now.date()
        time_now = now.time()
        return self.exclude(models.Q(publication_date__isnull=True) | models.Q(publication_time__isnull=True)). \
            filter(models.Q(models.Q(end__gte=now) |
                            models.Q(end__isnull=True)),
                   state__in=self.model.PUBLISHED_STATES, publication_date__lte=date_now,
                   publication_time__lte=time_now)

    # todo: filter by best score
    # todo: filter by country?
    def should_read(self, news, user):
        return self.model.objects.exclude(pk=news.pk).published(). \
            annotate_in_favorites(user). \
            with_base_related().by_type(news.news_type).distinct().order_by('?')

    def same_theme(self, news, user):
        return self.model.objects.exclude(pk=news.pk).published(). \
            annotate_in_favorites(user). \
            with_base_related().by_type(news.news_type). \
            by_tags(news.tags.all()).distinct().sort_by_start()

    def annotate_in_favorites(self, user):
        """Annotate flag in_favorites"""
        favorite_news_ids = []
        if user.is_authenticated:
            favorite_news_ids = user.favorite_news_ids
        return self.annotate(
            in_favorites=Case(
                When(id__in=favorite_news_ids, then=True),
                default=False,
                output_field=models.BooleanField(default=False)
            )
        )

    def by_locale(self, locale):
        return self.filter(title__icontains=locale)


class News(GalleryMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixin,
           FavoritesMixin):
    """News model."""

    STR_FIELD_NAME = 'title'

    # TEMPLATE CHOICES
    NEWSPAPER = 0
    MAIN_PDF_ERB = 1
    MAIN = 2

    TEMPLATE_CHOICES = (
        (NEWSPAPER, 'newspaper'),
        (MAIN_PDF_ERB, 'main.pdf.erb'),
        (MAIN, 'main'),
    )

    # STATE CHOICES
    WAITING = 0
    HIDDEN = 1
    PUBLISHED = 2
    PUBLISHED_EXCLUSIVE = 3

    PUBLISHED_STATES = [PUBLISHED, PUBLISHED_EXCLUSIVE]

    STATE_CHOICES = (
        (WAITING, _('Waiting')),
        (HIDDEN, _('Hidden')),
        (PUBLISHED, _('Published')),
        (PUBLISHED_EXCLUSIVE, _('Published exclusive')),
    )

    INTERNATIONAL_TAG_VALUE = 'international'
    RECIPES_TAG_VALUE = 'cook'

    old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
    news_type = models.ForeignKey(NewsType, on_delete=models.PROTECT,
                                  verbose_name=_('news type'), related_name='news')
    title = TJSONField(blank=True, null=True, default=None,
                       verbose_name=_('title'),
                       help_text='{"en-GB":"some text"}')
    backoffice_title = models.TextField(null=True, default=None,
                                        verbose_name=_('Title for searching via BO'))
    subtitle = TJSONField(blank=True, null=True, default=None,
                          verbose_name=_('subtitle'),
                          help_text='{"en-GB":"some text"}')
    description = TJSONField(blank=True, null=True, default=None,
                             verbose_name=_('description'),
                             help_text='{"en-GB":"some text"}')
    locale_to_description_is_active = HStoreField(null=True, default=dict, blank=True,
                                                  verbose_name=_('Is description for certain locale active'),
                                                  help_text='{"en-GB": true, "fr-FR": false}')
    publication_date = models.DateField(blank=True, null=True, verbose_name=_('News publication date'),
                                        help_text=_('date since when news item is published'))
    publication_time = models.TimeField(blank=True, null=True, verbose_name=_('News publication time'),
                                        help_text=_('time since when news item is published'))
    end = models.DateTimeField(blank=True, null=True, default=None,
                               verbose_name=_('End'))
    slugs = HStoreField(null=True, blank=True, default=dict,
                        verbose_name=_('Slugs for current news obj'),
                        help_text='{"en-GB":"some slug"}')
    state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
                                             verbose_name=_('State'))
    is_highlighted = models.BooleanField(default=False,
                                         verbose_name=_('Is highlighted'))
    template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER)
    address = models.ForeignKey('location.Address', blank=True, null=True,
                                default=None, verbose_name=_('address'),
                                on_delete=models.SET_NULL)
    country = models.ForeignKey('location.Country', blank=True, null=True,
                                on_delete=models.SET_NULL,
                                verbose_name=_('country'))
    tags = models.ManyToManyField('tag.Tag', related_name='news',
                                  verbose_name=_('Tags'))
    gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery')
    views_count = models.OneToOneField('rating.ViewCount', blank=True, null=True, on_delete=models.SET_NULL,
                                       related_name='news')
    ratings = generic.GenericRelation(Rating)
    favorites = generic.GenericRelation(to='favorites.Favorites')
    carousels = generic.GenericRelation(to='main.Carousel')
    agenda = models.ForeignKey('news.Agenda', blank=True, null=True,
                               on_delete=models.SET_NULL,
                               verbose_name=_('agenda'))

    banner = models.ForeignKey('news.NewsBanner', blank=True, null=True,
                               on_delete=models.SET_NULL,
                               verbose_name=_('banner'))
    site = models.ForeignKey('main.SiteSettings', blank=True, null=True,
                             on_delete=models.SET_NULL, verbose_name=_('site settings'))
    duplication_date = models.DateTimeField(blank=True, null=True, default=None,
                                            verbose_name=_('Duplication datetime'))
    duplication_uuid = models.UUIDField(default=uuid.uuid4, editable=True, unique=False,
                                        verbose_name=_('Field to detect doubles'))
    objects = NewsQuerySet.as_manager()

    class Meta:
        """Meta class."""

        verbose_name = _('news')
        verbose_name_plural = _('news')

    def __str__(self):
        return f'news: {next(iter(self.slugs.values()))}'

    def create_duplicate(self, new_country, view_count_model):
        self.pk = None
        self.state = self.WAITING
        self.slugs = {locale: f'{slug}-{new_country.code}' for locale, slug in self.slugs.items()}
        self.country = new_country
        self.views_count = view_count_model
        self.duplication_date = timezone.now()
        self.save()

    @property
    def must_of_the_week(self) -> bool:
        """Detects whether current item in carousel"""
        kwargs = {
            'content_type': ContentType.objects.get_for_model(self),
            'object_id': self.pk,
            'country': self.country,
        }
        return Carousel.objects.filter(**kwargs).exists()

    @property
    def publication_datetime(self):
        """Represents datetime object combined from `publication_date` & `publication_time` fields"""
        try:
            return datetime.combine(date=self.publication_date, time=self.publication_time)
        except TypeError:
            return None

    @property
    def duplicates(self):
        """Duplicates for this news item excluding same country code labeled"""
        return News.objects.filter(duplication_uuid=self.duplication_uuid).exclude(country=self.country)

    @property
    def has_any_desc_active(self):
        """Detects whether news item has any active description"""
        return any(list(map(lambda v: v.lower() == 'true' if isinstance(v, str) else v, self.locale_to_description_is_active.values())))

    @property
    def is_publish(self):
        return self.state in self.PUBLISHED_STATES

    @property
    def is_international(self):
        return self.INTERNATIONAL_TAG_VALUE in map(lambda tag: tag.value, self.tags.all())

    @property
    def web_url(self):
        return reverse('web:news:rud', kwargs={'slug': next(iter(self.slugs.values()))})

    def should_read(self, user):
        return self.__class__.objects.should_read(self, user)[:3]

    def same_theme(self, user):
        return self.__class__.objects.same_theme(self, user)[:3]

    @property
    def main_image(self):
        qs = self.news_gallery.main_image()
        if qs.exists():
            return qs.order_by('-id').first().image

    @property
    def image_url(self):
        return self.main_image.image.url if self.main_image else None

    @property
    def preview_image_url(self):
        if self.main_image:
            return self.main_image.get_image_url(thumbnail_key='news_preview')

    @property
    def view_counter(self):
        count_value = 0
        if self.views_count:
            count_value = self.views_count.count
        return count_value

    # todo: remove in future
    @property
    def crop_gallery(self):
        if hasattr(self, 'gallery'):
            gallery = []
            images = self.gallery.all()
            model_name = self._meta.model_name.lower()
            crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
                               if p.startswith(model_name)]
            for image in images:
                d = {
                    'id': image.id,
                    'title': image.title,
                    'original_url': image.image.url,
                    'orientation_display': image.get_orientation_display(),
                    'auto_crop_images': {},
                }
                for crop in crop_parameters:
                    d['auto_crop_images'].update(
                        {f'{crop[len(f"{model_name}_"):]}_url': image.get_image_url(crop)})
                gallery.append(d)
            return gallery

    @property
    def crop_main_image(self):
        if hasattr(self, 'main_image') and self.main_image:
            image = self.main_image
            model_name = self._meta.model_name.lower()
            image_property = {
                'id': image.id,
                'title': image.title,
                'original_url': image.image.url,
                'orientation_display': image.get_orientation_display(),
                'auto_crop_images': {},
            }
            crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
                               if p.startswith(self._meta.model_name.lower())]
            for crop in crop_parameters:
                image_property['auto_crop_images'].update(
                    {f'{crop[len(f"{model_name}_"):]}_url': image.get_image_url(crop)})
            return image_property

    @property
    def descriptions(self):
        """Read-only list field for backoffice news representation"""
        return [{
            'locale': locale,
            'slug': (self.slugs or {}).get(locale),
            'status': 'active' if (self.locale_to_description_is_active or {}).get(locale) else 'inactive',
            'title': (self.title or {}).get(locale),
            'text': desc
        } for locale, desc in self.description.items()] if self.description else []


class NewsGallery(IntermediateGalleryModelMixin):
    news = models.ForeignKey(News, null=True,
                             related_name='news_gallery',
                             on_delete=models.CASCADE,
                             verbose_name=_('news'))
    image = models.ForeignKey('gallery.Image', null=True,
                              related_name='news_gallery',
                              on_delete=models.CASCADE,
                              verbose_name=_('gallery'))

    class Meta:
        """NewsGallery meta class."""
        verbose_name = _('news gallery')
        verbose_name_plural = _('news galleries')
        unique_together = [['news', 'image'], ]
