"""Utils app models."""
import logging
from os.path import exists

from django.conf import settings
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.contrib.gis.db import models
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.fields.jsonb import KeyTextTransform
from django.utils import timezone
from django.utils.html import mark_safe
from django.utils.translation import ugettext_lazy as _, get_language
from easy_thumbnails.fields import ThumbnailerImageField
from sorl.thumbnail import get_thumbnail
from sorl.thumbnail.fields import ImageField as SORLImageField

from configuration.models import TranslationSettings
from utils.methods import image_path, svg_image_path
from utils.validators import svg_image_validator

logger = logging.getLogger(__name__)


class ProjectBaseMixin(models.Model):
    """Base mixin model."""

    created = models.DateTimeField(default=timezone.now, editable=False,
                                   verbose_name=_('Date created'))
    modified = models.DateTimeField(auto_now=True,
                                    verbose_name=_('Date updated'))

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

        abstract = True


class TJSONField(JSONField):
    """Overrided JsonField."""


def to_locale(language):
    """Turn a language name (en-us) into a locale name (en_US)."""
    if language:
        language, _, country = language.lower().partition('-')
        if not country:
            return language
        # A language with > 2 characters after the dash only has its first
        # character after the dash capitalized; e.g. sr-latn becomes sr-Latn.
        # A language with 2 characters after the dash has both characters
        # capitalized; e.g. en-us becomes en-US.
        country, _, tail = country.partition('-')
        country = country.title() if len(country) > 2 else country.upper()
        if tail:
            country += '-' + tail
        return language + '-' + country


def get_current_locale():
    """Get current language."""
    return to_locale(get_language())


def get_default_locale():
    return TranslationSettings.get_solo().default_language or \
           settings.FALLBACK_LOCALE


def translate_field(self, field_name, toggle_field_name=None):
    def translate(self):
        field = getattr(self, field_name)
        toggler = getattr(self, toggle_field_name, None)
        if isinstance(field, dict):
            if toggler:
                field = {locale: v for locale, v in field.items() if toggler.get(locale) in [True, 'True', 'true']}
            value = field.get(to_locale(get_language()))
            # fallback
            if value is None:
                value = field.get(get_default_locale())
                if value is None:
                    try:
                        value = next(iter(field.values()))
                    except StopIteration:
                        # field values are absent
                        return None
            return value
        return None
    return translate


# todo: refactor this
class IndexJSON:

    def __getattr__(self, item):
        return None


def index_field(self, field_name):
    def index(self):
        field = getattr(self, field_name)
        obj = IndexJSON()
        if isinstance(field, dict):
            for key, value in field.items():
                setattr(obj, key, value)
        return obj

    return index


class TranslatedFieldsMixin:
    """Translated Fields mixin"""

    STR_FIELD_NAME = ''

    def __init__(self, *args, **kwargs):
        """Overrided __init__ method."""
        super(TranslatedFieldsMixin, self).__init__(*args, **kwargs)
        cls = self.__class__
        for field in cls._meta.fields:
            field_name = field.name
            if isinstance(field, TJSONField):
                setattr(cls, f'{field.name}_translated',
                        property(translate_field(self, field_name, f'locale_to_{field_name}_is_active')))
                setattr(cls, f'{field_name}_indexing',
                        property(index_field(self, field_name)))

    def __str__(self):
        """Overrided __str__ method."""
        value = None
        if self.STR_FIELD_NAME:
            field = getattr(self, getattr(self, 'STR_FIELD_NAME'))
            if isinstance(field, dict):
                value = field.get(get_current_locale())
        return value if value else super(TranslatedFieldsMixin, self).__str__()


class OAuthProjectMixin:
    """OAuth2 mixin for project GM"""

    def get_source(self):
        """Method to get of platform"""
        return NotImplementedError


class BaseAttributes(ProjectBaseMixin):
    created_by = models.ForeignKey(
        'account.User', on_delete=models.SET_NULL, verbose_name=_('created by'),
        null=True, related_name='%(class)s_records_created'
    )
    modified_by = models.ForeignKey(
        'account.User', on_delete=models.SET_NULL, verbose_name=_('modified by'),
        null=True, related_name='%(class)s_records_modified'
    )

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

        abstract = True


class ImageMixin(models.Model):
    """Avatar model."""

    THUMBNAIL_KEY = 'default'

    image = ThumbnailerImageField(upload_to=image_path,
                                  blank=True, null=True, default=None,
                                  verbose_name=_('Image'))

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

        abstract = True

    def get_image(self, key=None):
        """Get thumbnailed image file."""
        return self.image[key or self.THUMBNAIL_KEY] if self.image else None

    def get_image_url(self, key=None):
        """Get image thumbnail url."""
        return self.get_image(key).url if self.image else None

    def image_tag(self):
        """Admin preview tag."""
        if self.image:
            return mark_safe('<img src="%s" />' % self.get_image_url())
        else:
            return None

    def get_full_image_url(self, request, thumbnail_key=None):
        """Get full image url"""
        if self.image and exists(self.image.path):
            return request.build_absolute_uri(self.get_image(thumbnail_key).url)
        else:
            return None

    image_tag.short_description = _('Image')
    image_tag.allow_tags = True


class SORLImageMixin(models.Model):
    """Abstract model for SORL ImageField"""

    image = SORLImageField(upload_to=image_path,
                           blank=True, null=True, default=None,
                           verbose_name=_('Image'))

    class Meta:
        """Meta class."""
        abstract = True

    def get_image(self, thumbnail_key: str):
        """Get thumbnail image file."""
        if thumbnail_key in settings.SORL_THUMBNAIL_ALIASES:
            return get_thumbnail(
                file_=self.image,
                **settings.SORL_THUMBNAIL_ALIASES[thumbnail_key])

    def get_image_url(self, thumbnail_key: str):
        """Get image thumbnail url."""
        crop_image = self.get_image(thumbnail_key)
        if hasattr(crop_image, 'url'):
            return self.get_image(thumbnail_key).url

    def image_tag(self):
        """Admin preview tag."""
        if self.image:
            return mark_safe(f'<img src="{self.image.url}" style="max-height: 25%; max-width: 25%" />')
        else:
            return None

    def get_cropped_image(self, geometry: str, quality: int, crop: str) -> dict:
        cropped_image = get_thumbnail(self.image,
                                      geometry_string=geometry,
                                      crop=crop,
                                      quality=quality)
        return {
            'geometry_string': geometry,
            'crop_url': cropped_image.url,
            'quality': quality,
            'crop': crop
        }

    image_tag.short_description = _('Image')
    image_tag.allow_tags = True


class SVGImageMixin(models.Model):
    """SVG image model."""

    svg_image = models.FileField(upload_to=svg_image_path,
                                 blank=True, null=True, default=None,
                                 validators=[svg_image_validator, ],
                                 verbose_name=_('SVG image'))

    @property
    def svg_image_indexing(self):
        return self.svg_image.url if self.svg_image else None

    class Meta:
        abstract = True


class URLImageMixin(models.Model):
    """URl for image and special methods."""
    image_url = models.URLField(verbose_name=_('Image URL path'),
                                blank=True, null=True, default=None)

    class Meta:
        abstract = True

    def image_tag(self):
        if self.image_url:
            return mark_safe(
                f'<a href="{self.image_url}"><img src="{self.image_url}" height="50"/>')
        else:
            return None

    image_tag.short_description = _('Image')
    image_tag.allow_tags = True


class PlatformMixin(models.Model):
    """Platforms"""

    MOBILE = 0
    WEB = 1
    ALL = 2

    SOURCES = (
        (MOBILE, _('Mobile')),
        (WEB, _('Web')),
        (ALL, _('All'))
    )
    source = models.PositiveSmallIntegerField(choices=SOURCES, default=MOBILE,
                                              verbose_name=_('Source'))

    class Meta:
        """Meta class"""
        abstract = True


class LocaleManagerMixin(models.Manager):
    """Manager for locale"""

    def annotate_localized_fields(self, locale):
        """Return queryset by locale"""
        prefix = 'trans'

        # Prepare fields to localization (only JSONField can be localized)
        fields = [field.name for field in self.model._meta.fields if isinstance(field, JSONField)]

        # Check filters to check if field has localization
        filters = {f'{field}__has_key': locale for field in fields}
        # Filter QuerySet by prepared filters
        queryset = self.filter(**filters)

        # Prepare field for annotator
        localized_fields = {f'{field}_{prefix}': KeyTextTransform(f'{locale}', field) for field in
                            fields}

        # Annotate them
        for _ in fields:
            queryset = queryset.annotate(**localized_fields)
        return queryset


class GMTokenGenerator(PasswordResetTokenGenerator):
    CHANGE_EMAIL = 0
    RESET_PASSWORD = 1
    CHANGE_PASSWORD = 2
    CONFIRM_EMAIL = 3

    TOKEN_CHOICES = (
        CHANGE_EMAIL,
        RESET_PASSWORD,
        CHANGE_PASSWORD,
        CONFIRM_EMAIL
    )

    def __init__(self, purpose: int):
        if purpose in self.TOKEN_CHOICES:
            self.purpose = purpose

    def get_fields(self, user, timestamp):
        """
        Get user fields for hash value.
        """
        fields = [str(timestamp), str(user.is_active), str(user.pk)]
        if self.purpose == self.CHANGE_EMAIL or \
                self.purpose == self.CONFIRM_EMAIL:
            fields.extend([str(user.email_confirmed), str(user.email)])
        elif self.purpose == self.RESET_PASSWORD or \
                self.purpose == self.CHANGE_PASSWORD:
            fields.append(str(user.password))
        return fields

    def _make_hash_value(self, user, timestamp):
        """
        Hash the user's primary key and some user state that's sure to change
        after a password reset to produce a token that invalidated when it's
        used.
        """
        return self.get_fields(user, timestamp)


class GalleryMixin:
    """Mixin for models that has gallery."""

    @property
    def crop_gallery(self):
        if hasattr(self, 'gallery') and hasattr(self, '_meta'):
            gallery = []
            images = self.gallery.all()
            crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
                               if p.startswith(self._meta.model_name.lower())]
            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({crop: image.get_image_url(crop)})
                gallery.append(d)
            return gallery

    @property
    def crop_main_image(self):
        if hasattr(self, 'main_image') and hasattr(self, '_meta'):
            if self.main_image:
                image = self.main_image
                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(
                        {crop: image.get_image_url(crop)}
                    )
                return image_property


class IntermediateGalleryModelQuerySet(models.QuerySet):
    """Extended QuerySet."""

    def main_image(self):
        """Return objects with flag is_main is True"""
        return self.filter(is_main=True)


class IntermediateGalleryModelMixin(models.Model):
    """Mixin for intermediate gallery model."""

    is_main = models.BooleanField(default=False,
                                  verbose_name=_('Is the main image'))

    objects = IntermediateGalleryModelQuerySet.as_manager()

    class Meta:
        """Meta class."""
        abstract = True

    def __str__(self):
        """Overridden str method."""
        if hasattr(self, 'image'):
            return self.image.title if self.image.title else self.id


class HasTagsMixin(models.Model):
    """Mixin for filtering tags"""

    @property
    def visible_tags(self):
        return self.tags.filter(category__public=True).prefetch_related(
            'category',
            'translation',
            'category__translation',
        ).exclude(category__value_type='bool')

    class Meta:
        """Meta class."""
        abstract = True


class FavoritesMixin:
    """Append favorites_for_user property."""

    @property
    def favorites_for_users(self):
        return self.favorites.aggregate(arr=ArrayAgg('user_id')).get('arr')


timezone.datetime.now().date().isoformat()


class TypeDefaultImageMixin:
    """Model mixin for default image."""

    @property
    def default_image_url(self):
        """Return image url."""
        if hasattr(self, 'default_image') and self.default_image:
            return self.default_image.image.url

    @property
    def preview_image_url(self):
        if hasattr(self, 'default_image') and self.default_image:
            return self.default_image.get_image_url(thumbnail_key='type_preview')
