"""Location app models."""
from django.conf import settings
from django.contrib.gis.db import models
from django.db.models.signals import post_save
from django.db.transaction import on_commit
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from functools import reduce
from typing import List


from django.contrib.postgres.fields import ArrayField

from translation.models import Language
from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
                          TranslatedFieldsMixin, get_current_locale,
                          IntermediateGalleryModelMixin, GalleryMixin)


class CountryQuerySet(models.QuerySet):
    """Country queryset."""
    def active(self, switcher=True):
        """Filter only active users."""
        return self.filter(is_active=switcher)


class Country(TranslatedFieldsMixin,
              SVGImageMixin, ProjectBaseMixin):
    """Country model."""

    STR_FIELD_NAME = 'name'

    TWELVE_HOURS_FORMAT_COUNTRIES = [
        'ca',  # Canada
        'au',  # Australia
        'us',  # USA
        'nz',  # New Zealand
    ]

    name = TJSONField(null=True, blank=True, default=None,
                      verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
    code = models.CharField(max_length=255, verbose_name=_('Code'))
    low_price = models.IntegerField(default=25, verbose_name=_('Low price'))
    high_price = models.IntegerField(default=50, verbose_name=_('High price'))
    languages = models.ManyToManyField(Language, verbose_name=_('Languages'))
    is_active = models.BooleanField(_('is active'), default=True)
    old_id = models.IntegerField(null=True, blank=True, default=None)

    mysql_ids = ArrayField(models.IntegerField(), blank=True, null=True)

    objects = CountryQuerySet.as_manager()

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

        verbose_name_plural = _('Countries')
        verbose_name = _('Country')

    def __str__(self):
        str_name = self.code
        if isinstance(self.name, dict):
            translated_name = self.name.get(get_current_locale())
            if translated_name:
                str_name = translated_name
        return str_name

    @property
    def time_format(self):
        if self.code.lower() not in self.TWELVE_HOURS_FORMAT_COUNTRIES:
            return 'HH:mm'
        return 'hh:mmA'

    @property
    def country_id(self):
        return self.id



class RegionQuerySet(models.QuerySet):
    """QuerySet for model Region."""

    def without_parent_region(self, switcher: bool = True):
        """Filter regions by parent region."""
        return self.filter(parent_region__isnull=switcher)

    def by_region_id(self, region_id):
        """Filter regions by region id."""
        return self.filter(id=region_id)

    def by_sub_region_id(self, sub_region_id):
        """Filter sub regions by sub region id."""
        return self.filter(parent_region_id=sub_region_id)

    def sub_regions_by_region_id(self, region_id):
        """Filter regions by sub region id."""
        return self.filter(parent_region_id=region_id)


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

    name = models.CharField(_('name'), max_length=250)
    code = models.CharField(_('code'), max_length=250)
    parent_region = models.ForeignKey(
        'self', verbose_name=_('parent region'), null=True,
        blank=True, default=None, on_delete=models.CASCADE)
    country = models.ForeignKey(
        Country, verbose_name=_('country'), on_delete=models.CASCADE)
    old_id = models.IntegerField(null=True, blank=True, default=None)
    mysql_ids = ArrayField(models.IntegerField(), blank=True, null=True)

    objects = RegionQuerySet.as_manager()

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

        verbose_name_plural = _('regions')
        verbose_name = _('region')

    def __str__(self):
        return self.name


class CityQuerySet(models.QuerySet):
    """Extended queryset for City model."""

    def _generic_search(self, value, filter_fields_names: List[str]):
        """Generic method for searching value in specified fields"""
        filters = [
            {f'{field}__icontains': value}
            for field in filter_fields_names
        ]
        return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters]))

    def search_by_name(self, value):
        """Search by name or last_name."""
        return self._generic_search(value, ['name', 'code', 'postal_code'])

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


class City(GalleryMixin, models.Model):
    """Region model."""
    name = models.CharField(_('name'), max_length=250)
    name_translated = TJSONField(blank=True, null=True, default=None,
                                 verbose_name=_('Translated name'),
                                 help_text='{"en-GB":"some text"}')
    code = models.CharField(_('code'), max_length=250)
    region = models.ForeignKey(Region, on_delete=models.CASCADE,
                               blank=True, null=True,
                               verbose_name=_('parent region'))
    country = models.ForeignKey(
        Country, verbose_name=_('country'), on_delete=models.CASCADE)

    postal_code = models.CharField(
        _('postal code'), max_length=10, default='', help_text=_('Ex.: 350018'))

    is_island = models.BooleanField(_('is island'), default=False)
    old_id = models.IntegerField(null=True, blank=True, default=None)

    map1 = models.CharField(max_length=255, blank=True, null=True)
    map2 = models.CharField(max_length=255, blank=True, null=True)
    map_ref = models.CharField(max_length=255, blank=True, null=True)
    situation = models.CharField(max_length=255, blank=True, null=True)

    gallery = models.ManyToManyField('gallery.Image', through='location.CityGallery', blank=True)

    mysql_id = models.IntegerField(blank=True, null=True, default=None)

    objects = CityQuerySet.as_manager()

    class Meta:
        verbose_name_plural = _('cities')
        verbose_name = _('city')

    def __str__(self):
        return self.name


class CityGallery(IntermediateGalleryModelMixin):
    """Gallery for model City."""
    city = models.ForeignKey(City, null=True,
                             related_name='city_gallery',
                             on_delete=models.CASCADE,
                             verbose_name=_('city'))
    image = models.ForeignKey('gallery.Image', null=True,
                              related_name='city_gallery',
                              on_delete=models.CASCADE,
                              verbose_name=_('image'))

    class Meta:
        """CityGallery meta class."""
        verbose_name = _('city gallery')
        verbose_name_plural = _('city galleries')
        unique_together = (('city', 'is_main'), ('city', 'image'))


class Address(models.Model):
    """Address model."""
    city = models.ForeignKey(City, verbose_name=_('city'), on_delete=models.CASCADE)
    street_name_1 = models.CharField(
        _('street name 1'), max_length=500, blank=True, default='')
    street_name_2 = models.CharField(
        _('street name 2'), max_length=500, blank=True, default='')
    number = models.IntegerField(_('number'), blank=True, default=0)
    postal_code = models.CharField(
        _('postal code'), max_length=10, blank=True,
        default='', help_text=_('Ex.: 350018'))
    coordinates = models.PointField(
        _('Coordinates'), blank=True, null=True, default=None)
    old_id = models.IntegerField(null=True, blank=True, default=None)

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

        verbose_name_plural = _('Address')
        verbose_name = _('Address')

    def __str__(self):
        return f'{self.id}: {self.get_street_name()[:50]}'

    def get_street_name(self):
        return self.street_name_1 or self.street_name_2

    @property
    def latitude(self):
        return self.coordinates.y if self.coordinates else float(0)

    @property
    def longitude(self):
        return self.coordinates.x if self.coordinates else float(0)

    @property
    def location_field_indexing(self):
        return {'lat': self.latitude,
                'lon': self.longitude}

    @property
    def country_id(self):
        return self.city.country_id

    @property
    def full_address(self):
        full_address = self.get_street_name()
        if self.number and int(self.number):
            full_address = f'{self.number} {self.get_street_name()}'
        return full_address


class WineRegionQuerySet(models.QuerySet):
    """Wine region queryset."""

    def with_sub_region_related(self):
        return self.prefetch_related('wine_sub_region')

    def having_wines(self, value=True):
        """Return qs with regions, which have any wine related to them"""
        return self.exclude(wineoriginaddress__product__isnull=value)


class WineRegion(TranslatedFieldsMixin, models.Model):
    """Wine region model."""
    name = models.CharField(_('name'), max_length=255)
    country = models.ForeignKey(Country, on_delete=models.PROTECT,
                                blank=True, null=True, default=None,
                                verbose_name=_('country'))
    coordinates = models.PointField(
        _('Coordinates'), blank=True, null=True, default=None)
    old_id = models.PositiveIntegerField(_('old id'), default=None,
                                         blank=True, null=True)
    description = TJSONField(blank=True, null=True, default=None,
                             verbose_name=_('description'),
                             help_text='{"en-GB":"some text"}')
    tags = models.ManyToManyField('tag.Tag', blank=True,
                                  related_name='wine_regions',
                                  help_text='attribute from legacy db')

    objects = WineRegionQuerySet.as_manager()

    class Meta:
        """Meta class."""
        verbose_name_plural = _('wine regions')
        verbose_name = _('wine region')

    def __str__(self):
        """Override dunder method."""
        return self.name


class WineSubRegionQuerySet(models.QuerySet):
    """Wine sub region QuerySet."""


class WineSubRegion(models.Model):
    """Wine sub region model."""
    name = models.CharField(_('name'), max_length=255)
    wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT,
                                    related_name='wine_sub_region',
                                    verbose_name=_('wine sub region'))
    old_id = models.PositiveIntegerField(_('old id'), default=None,
                                         blank=True, null=True)

    objects = WineSubRegionQuerySet.as_manager()

    class Meta:
        """Meta class."""
        verbose_name_plural = _('wine sub regions')
        verbose_name = _('wine sub region')

    def __str__(self):
        """Override dunder method."""
        return self.name


class WineVillageQuerySet(models.QuerySet):
    """Wine village QuerySet."""


class WineVillage(models.Model):
    """
    Wine village.
    Description: Imported from legacy DB.
    """
    name = models.CharField(_('name'), max_length=255)
    wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT,
                                    verbose_name=_('wine region'))
    old_id = models.PositiveIntegerField(_('old id'), default=None,
                                         blank=True, null=True)

    objects = WineVillageQuerySet.as_manager()

    class Meta:
        """Meta class."""
        verbose_name = _('wine village')
        verbose_name_plural = _('wine villages')

    def __str__(self):
        """Override str dunder."""
        return self.name


class WineOriginAddressMixin(models.Model):
    """Model for wine origin address."""
    wine_region = models.ForeignKey('location.WineRegion', on_delete=models.CASCADE,
                                    verbose_name=_('wine region'))
    wine_sub_region = models.ForeignKey('location.WineSubRegion', on_delete=models.CASCADE,
                                        blank=True, null=True, default=None,
                                        verbose_name=_('wine sub region'))

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


class EstablishmentWineOriginAddressQuerySet(models.QuerySet):
    """QuerySet for EstablishmentWineOriginAddress model."""


class EstablishmentWineOriginAddress(WineOriginAddressMixin):
    """Establishment wine origin address model."""
    establishment = models.ForeignKey('establishment.Establishment', on_delete=models.CASCADE,
                                      related_name='wine_origins',
                                      verbose_name=_('product'))

    objects = EstablishmentWineOriginAddressQuerySet.as_manager()

    class Meta:
        """Meta class."""
        verbose_name = _('establishment wine origin address')
        verbose_name_plural = _('establishment wine origin addresses')


class WineOriginAddressQuerySet(models.QuerySet):
    """QuerySet for WineOriginAddress model."""


class WineOriginAddress(WineOriginAddressMixin):
    """Wine origin address model."""
    product = models.ForeignKey('product.Product', on_delete=models.CASCADE,
                                related_name='wine_origins',
                                verbose_name=_('product'))

    objects = WineOriginAddressQuerySet.as_manager()

    class Meta:
        """Meta class."""
        verbose_name = _('wine origin address')
        verbose_name_plural = _('wine origin addresses')


# todo: Make recalculate price levels
@receiver(post_save, sender=Country)
def run_recalculate_price_levels(sender, instance, **kwargs):
    from establishment.tasks import recalculate_price_levels_by_country
    if settings.USE_CELERY:
        on_commit(lambda: recalculate_price_levels_by_country.delay(
            country_id=instance.id))
    else:
        on_commit(lambda: recalculate_price_levels_by_country(
            country_id=instance.id))
