from rest_framework import serializers
from django.utils.text import slugify

from product import models
from location import models as location_models
from tag import models as tag_models
from establishment import models as establishment_models
from transfer import models as transfer_models
from utils.methods import get_point_from_coordinates
from transfer.mixins import TransferSerializerMixin
from django.conf import settings
from functools import reduce
from gallery.models import Image


class WineColorSerializer(TransferSerializerMixin):
    CATEGORY_LABEL = 'Wine color'
    CATEGORY_INDEX_NAME = slugify(CATEGORY_LABEL)

    id = serializers.IntegerField()
    name = serializers.CharField(allow_null=True)
    order_number = serializers.IntegerField(allow_null=True)

    class Meta:
        model = tag_models.Tag
        fields = (
            'id',
            'name',
            'order_number',
        )

    def validate(self, attrs):
        value = attrs.pop('name')
        attrs['old_id'] = attrs.pop('id', None)
        attrs['label'] = {settings.FALLBACK_LOCALE: value}
        attrs['value'] = slugify(value)
        attrs['priority'] = attrs.pop('order_number')
        attrs['category'] = self.tag_category
        return attrs


class WineTypeSerializer(TransferSerializerMixin):
    CATEGORY_LABEL = 'Sugar content'
    CATEGORY_INDEX_NAME = slugify(CATEGORY_LABEL)

    id = serializers.IntegerField()
    name = serializers.CharField()

    class Meta:
        model = tag_models.Tag
        fields = (
            'id',
            'name',
        )

    def validate(self, attrs):
        value = attrs.pop('name')
        attrs['old_id'] = attrs.pop('id', None)
        attrs['label'] = {settings.FALLBACK_LOCALE: value}
        attrs['value'] = slugify(value)
        attrs['category'] = self.tag_category
        return attrs


class WineBottlesProducedSerializer(TransferSerializerMixin):
    CATEGORY_LABEL = 'Bottles produced'
    CATEGORY_INDEX_NAME = slugify(CATEGORY_LABEL)

    bottles_produced = serializers.CharField(allow_null=True)

    class Meta:
        model = tag_models.Tag
        fields = (
            'bottles_produced',
        )

    def validate(self, attrs):
        value = attrs.pop('bottles_produced')
        parsed_value = self.parsed_value(value)
        attrs['label'] = {settings.FALLBACK_LOCALE: parsed_value}
        attrs['value'] = slugify(parsed_value)
        attrs['category'] = self.tag_category
        return attrs

    def parsed_value(self, value):
        if value.isdigit():
            return int(value)

        parted = value.split(' ')
        if len(parted) > 1:
            values = [int(i) for i in parted if i.isdigit()]
            return reduce(lambda a, b: a + b, values)

        lowered = value.lower()
        if 'magnum' in lowered:
            return 1

        return 0


class ProductStandardSerializer(TransferSerializerMixin):
    id = serializers.IntegerField()
    name = serializers.CharField()
    type = serializers.CharField(allow_null=True)
    longitude = serializers.FloatField(allow_null=True)
    latitude = serializers.FloatField(allow_null=True)

    class Meta:
        model = models.ProductStandard
        fields = (
            'id',
            'name',
            'type',
            'longitude',
            'latitude',
        )

    def validate(self, attrs):
        latitude = attrs.pop('latitude', None)
        longitude = attrs.pop('longitude', None)
        standard_type = attrs.pop('type', None)

        attrs['coordinates'] = get_point_from_coordinates(latitude, longitude)
        attrs['old_id'] = attrs.get('id')
        attrs['standard_type'] = self.get_standard_type(standard_type)
        return attrs

    def get_standard_type(self, type: str):
        if type == 'Appellation':
            return models.ProductStandard.APPELLATION
        elif type == 'YardClassification':
            return models.ProductStandard.WINEQUALITY
        elif type == 'WineQuality':
            return models.ProductStandard.YARDCLASSIFICATION


class WineClassificationTypeSerializer(TransferSerializerMixin):

    name = serializers.CharField()

    class Meta:
        model = models.ProductClassificationType
        fields = (
            'name',
        )

    def validate(self, attrs):
        attrs['product_type'] = self.wine_type
        return attrs

    @property
    def wine_type(self):
        qs = models.ProductType.objects.filter(index_name=models.ProductType.WINE)
        if qs.exists():
            return qs.first()


class ProductClassificationSerializer(TransferSerializerMixin):

    id = serializers.IntegerField()
    name = serializers.CharField()
    possible_type_id = serializers.IntegerField(allow_null=True)
    possible_color_id = serializers.IntegerField(allow_null=True)
    parent_id = serializers.IntegerField(allow_null=True)

    class Meta:
        model = models.ProductClassification
        fields = (
            'id',
            'name',
            'possible_type_id',
            'possible_color_id',
            'parent_id',
        )

    def validate(self, attrs):
        classification_name = attrs.pop('name')
        possible_type_id = attrs.pop('possible_type_id', None)
        possible_color_id = attrs.pop('possible_color_id', None)
        parent_id = attrs.pop('parent_id', None)

        attrs['old_id'] = attrs.pop('id')
        attrs['classification_type'] = self.get_classification_type(classification_name)
        attrs['possible_subtype_tag'] = self.get_possible_type_tag(possible_type_id)
        attrs['possible_color_tag'] = self.get_possible_color_tag(possible_color_id)
        attrs['standard'] = self.get_wine_standard(parent_id)
        return attrs

    def create(self, validated_data):
        possible_color_tag = validated_data.pop('possible_color_tag', None)
        possible_subtype_tag = validated_data.pop('possible_subtype_tag', None)
        obj, _ = self.Meta.model.objects.get_or_create(**validated_data)
        if possible_color_tag:
            obj.tags.add(possible_color_tag)
        if possible_subtype_tag:
            obj.tags.add(possible_subtype_tag)
        return obj

    def get_classification_type(self, classification_name):
        qs = models.ProductClassificationType.objects.filter(name__icontains=classification_name)
        if qs.exists():
            return qs.first()

    def get_possible_type_tag(self, possible_type_id):
        if possible_type_id:
            qs = tag_models.Tag.objects.filter(
                old_id=possible_type_id,
                category__index_name=WineTypeSerializer.CATEGORY_INDEX_NAME)
            if qs.exists():
                return qs.first()

    def get_possible_color_tag(self, possible_color_id):
        if possible_color_id:
            qs = tag_models.Tag.objects.filter(
                old_id=possible_color_id,
                category__index_name=WineColorSerializer.CATEGORY_INDEX_NAME)
            if qs.exists():
                return qs.first()

    def get_wine_standard(self, parent_id):
        if parent_id:
            qs = models.ProductStandard.objects.filter(old_id=parent_id)
            if qs.exists():
                return qs.first()


class ProductSerializer(TransferSerializerMixin):

    establishment_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.Establishments.objects.all(),
        allow_null=True)
    classification_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.ProductClassification.objects.all(),
        allow_null=True)
    wine_region_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.WineLocations.objects.all(),
        allow_null=True)
    wine_type_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.WineType.objects.all(),
        allow_null=True)
    wine_color_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.WineColor.objects.all(),
        allow_null=True)
    appellation_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.ProductClassification.objects.all(),
        allow_null=True)
    village_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.WineLocations.objects.all(),
        allow_null=True)
    # same as wine_village
    # vineyard_id = serializers.PrimaryKeyRelatedField(
    #     queryset=transfer_models.WineLocations.objects.all(),
    #     allow_null=True)
    wine_quality_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.ProductClassification.objects.all(),
        allow_null=True)

    # same as establishment name
    id = serializers.IntegerField()
    brand = serializers.CharField(allow_null=True, allow_blank=True)
    name = serializers.CharField(allow_null=True, allow_blank=True)
    vintage = serializers.CharField(allow_null=True)
    type = serializers.CharField(allow_null=True)
    wine_sub_region_id = serializers.CharField(allow_null=True)
    state = serializers.CharField()
    bottles_produced = serializers.CharField(allow_null=True, allow_blank=True)
    unique_key = serializers.CharField(allow_null=True)
    price = serializers.DecimalField(max_digits=14, decimal_places=2, allow_null=True)

    class Meta:
        model = models.Product
        fields = (
            'id',
            'establishment_id',  # done
            'classification_id',  # done
            'wine_region_id',  # done
            'wine_type_id',  # done
            'wine_color_id',  # done
            'appellation_id',  # done
            'village_id',  # done
            # 'vineyard_id',
            'wine_quality_id',  # done
            'brand',
            'name',  # done
            'vintage',  # done
            'type',  # done
            'wine_sub_region_id',  # done
            'state',  # done
            'bottles_produced',  # done
            'unique_key',  # done
            'price',
        )

    def validate(self, attrs):
        name = attrs.pop('name', None)
        establishment = attrs.pop('establishment_id', None)
        brand = attrs.pop('brand', None)
        classification = attrs.pop('classification_id', None)
        vintage = attrs.pop('vintage', None)
        product_type = attrs.pop('type')
        wine_region = attrs.pop('wine_region_id', None)
        wine_sub_region_id = attrs.pop('wine_sub_region_id', None)
        bottles_produced = attrs.pop('bottles_produced', None)
        wine_type = attrs.pop('wine_type_id', None)
        wine_color = attrs.pop('wine_color_id', None)
        appellation = attrs.pop('appellation_id', None)
        village = attrs.pop('village_id', None)
        wine_quality = attrs.pop('wine_quality_id', None)
        name = self.get_name(name, brand)
        old_id = attrs.pop('id')
        state = self.get_state(attrs.pop('state', None))


        attrs['old_id'] = old_id
        attrs['name'] = name
        attrs['old_unique_key'] = attrs.pop('unique_key')
        attrs['establishment'] = self.get_establishment(establishment)
        attrs['state'] = state
        # attrs['brand'] = self.get_brand(brand)
        attrs['wine_classification'] = self.get_wine_classification(classification)
        attrs['wine_quality'] = self.get_wine_standard(wine_quality,
                                                       models.ProductStandard.WINEQUALITY)
        attrs['wine_appellation'] = self.get_wine_standard(appellation,
                                                           models.ProductStandard.APPELLATION)
        attrs['product_type'] = self.get_product_type(product_type)
        attrs['vintage'] = self.get_vintage_year(vintage)
        attrs['wine_region'] = self.get_wine_region(wine_region)
        attrs['wine_sub_region'] = self.get_wine_sub_region(wine_sub_region_id)
        attrs['bottles_produced_tag'] = self.get_tag(bottles_produced,
                                                     WineBottlesProducedSerializer.CATEGORY_INDEX_NAME)
        attrs['wine_type_tag'] = self.get_tag(wine_type,
                                              WineTypeSerializer.CATEGORY_INDEX_NAME)
        attrs['wine_color_tag'] = self.get_tag(wine_color,
                                               WineColorSerializer.CATEGORY_INDEX_NAME)
        attrs['wine_village'] = self.get_wine_village(village)
        attrs['available'] = self.get_availability(state)
        attrs['slug'] = self.get_slug(name, old_id)
        attrs['average_price'] = attrs.pop('price')
        return attrs

    def create(self, validated_data):
        qs = self.Meta.model.objects.filter(old_id=validated_data.get('old_id'))
        wine_region = validated_data.pop('wine_region')
        wine_sub_region = validated_data.pop('wine_sub_region')

        # classifications
        classifications = [validated_data.pop('wine_classification', None)]
        # standards
        standards = [validated_data.pop('wine_quality', None),
                     validated_data.pop('wine_appellation', None)]
        # tags
        tags = [validated_data.pop('bottles_produced_tag', None),
                validated_data.pop('wine_type_tag', None),
                validated_data.pop('wine_color_tag', None)]

        if not qs.exists():
            obj = super().create(validated_data)
        else:
            obj = qs.first()

        # adding classification
        obj.classifications.add(*[i for i in classifications if i and i not in obj.classifications.all()])
        # adding standard
        obj.standards.add(*[i for i in standards if i and i not in obj.standards.all()])
        # adding tags
        obj.tags.add(*[i for i in tags if i and i not in obj.tags.all()])
        # checking wine origin address
        wine_origin_address, _ = location_models.WineOriginAddress.objects.get_or_create(
            product=obj,
            wine_region=wine_region,
            wine_sub_region=wine_sub_region)
        return obj

    def get_name(self, name, brand):
        if name:
            return name
        if brand and not name:
            return brand

    def get_establishment(self, establishment):
        if establishment:
            qs = establishment_models.Establishment.objects.filter(
                old_id=establishment.id)
            if qs.exists():
                return qs.first()

    def get_state(self, state):
        if state == 'published':
            return models.Product.PUBLISHED
        elif state == 'out_of_production':
            return models.Product.OUT_OF_PRODUCTION
        return models.Product.WAITING

    def get_wine_classification(self, classification):
        if classification:
            classification_qs = models.ProductClassification.objects.filter(
                old_id=classification.id)
            if classification_qs.exists():
                return classification_qs.first()

    @staticmethod
    def get_wine_region(wine_region):
        if wine_region:
            old_id = wine_region if not isinstance(wine_region, transfer_models.WineLocations) \
                                 else wine_region.id
            wine_region_qs = location_models.WineRegion.objects.filter(
                old_id=old_id)
            if wine_region_qs.exists():
                return wine_region_qs.first()

    @staticmethod
    def get_wine_sub_region(wine_sub_region):
        if wine_sub_region:
            old_id = wine_sub_region if not isinstance(wine_sub_region, transfer_models.WineLocations) \
                                     else wine_sub_region.id
            sub_region_qs = location_models.WineSubRegion.objects.filter(
                old_id=old_id)
            if sub_region_qs.exists():
                return sub_region_qs.first()

    def get_wine_village(self, village):
        if village:
            village_qs = location_models.WineVillage.objects.filter(
                old_id=village.id)
            if village_qs.exists():
                return village_qs.first()

    def get_wine_standard(self, standard, standard_type):
        if standard:
            if isinstance(standard, transfer_models.ProductClassification):
                standard = standard.id
            standard_qs = models.ProductStandard.objects.filter(
                standard_type=standard_type,
                old_id=standard)
            if standard_qs.exists():
                return standard_qs.first()

    def get_availability(self, state: int):
        if state == models.Product.PUBLISHED:
            return True
        return False


class WineColorTagSerializer(TransferSerializerMixin):
    """Serializer for fixing existing products with missing tags."""

    id = serializers.IntegerField()
    wine_color_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.WineColor.objects.all(),
        allow_null=True)

    class Meta:
        model = models.Product
        fields = (
            'id',
            'wine_color_id',
        )

    def validate(self, attrs):
        attrs['old_id'] = attrs.pop('id')
        attrs['wine_color_tag'] = self.get_tag(attrs.pop('wine_color_id', None),
                                               WineColorSerializer.CATEGORY_INDEX_NAME)
        return attrs

    def create(self, validated_data):
        qs = self.Meta.model.objects.filter(old_id=validated_data.get('old_id'))

        # tags
        wine_color_tag = validated_data.pop('wine_color_tag', None)

        if qs.exists():
            obj = qs.first()

            # adding missing tag
            if wine_color_tag in obj.tags.all():
                obj.tags.add(wine_color_tag)
            return obj


class PlateSerializer(TransferSerializerMixin):

    PRODUCT_TYPE_INDEX_NAME = 'souvenir'
    PRODUCT_SUB_TYPE_INDEX_NAME = 'plate'

    id = serializers.IntegerField()
    name = serializers.CharField()
    vintage = serializers.CharField()

    class Meta(ProductSerializer.Meta):
        fields = (
            'id',
            'name',
            'vintage',
        )

    def validate(self, attrs):
        old_id = attrs.pop('id')
        name = attrs.get('name')
        product_type = self.get_product_type(self.PRODUCT_TYPE_INDEX_NAME)

        attrs['old_id'] = old_id
        attrs['vintage'] = self.get_vintage_year(attrs.pop('vintage'))
        attrs['product_type'] = product_type
        attrs['state'] = self.Meta.model.PUBLISHED
        attrs['subtype'] = self.get_product_sub_type(product_type,
                                                     self.PRODUCT_SUB_TYPE_INDEX_NAME)
        attrs['slug'] = self.get_slug(name, old_id)
        return attrs

    def create(self, validated_data):
        qs = self.Meta.model.objects.filter(old_id=validated_data.get('old_id'))
        # subtypes
        subtypes = [validated_data.pop('subtype', None)]

        if not qs.exists():
            obj = super().create(validated_data)
        else:
            obj = qs.first()

        # adding classification
        obj.subtypes.add(*[i for i in subtypes if i])
        return obj


class PlateImageSerializer(TransferSerializerMixin):

    id = serializers.IntegerField()
    name = serializers.CharField()
    attachment_suffix_url = serializers.CharField()

    class Meta:
        model = models.ProductGallery
        fields = (
            'id',
            'name',
            'attachment_suffix_url',
        )

    def create(self, validated_data):
        image = self.get_image(validated_data.pop('attachment_suffix_url', None),
                               validated_data.pop('name', None))
        product = self.get_product(validated_data.pop('id'))

        if product and image:
            obj, created = models.ProductGallery.objects.get_or_create(
                product=product,
                image=image,
                is_main=True)
            return obj

    def get_image(self, image_url, name):
        if image_url:
            obj, created = Image.objects.get_or_create(
                title=name,
                image=image_url)
            return obj if created else None


class ProductNoteSerializer(TransferSerializerMixin):

    id = serializers.IntegerField()
    product_id = serializers.IntegerField()
    text = serializers.CharField(allow_blank=True)

    class Meta:
        model = models.ProductNote
        fields = (
            'id',
            'product_id',
            'text',
        )

    def validate(self, attrs):
        attrs['old_id'] = attrs['id']
        attrs['product'] = self.get_product(attrs.pop('product_id'))
        return attrs

    def create(self, validated_data):
        qs = self.Meta.model.objects.filter(**validated_data)
        product = validated_data.get('product')

        if not qs.exists() and product:
            return super().create(validated_data)

    def get_product(self, old_id):
        if old_id:
            qs = models.Product.objects.filter(old_id=old_id)
            if qs.exists():
                return qs.first()


class AssemblageProductTagSerializer(TransferSerializerMixin):

    CATEGORY_LABEL = 'Grape variety'
    CATEGORY_INDEX_NAME = slugify(CATEGORY_LABEL)

    percent = serializers.FloatField()
    cepage_id = serializers.PrimaryKeyRelatedField(
        queryset=transfer_models.Cepages.objects.all())
    product_id = serializers.IntegerField()

    class Meta(ProductSerializer.Meta):
        fields = (
            'percent',
            'cepage_id',  # tag category
            'product_id',
        )

    def validate(self, attrs):
        cepage = attrs.pop('cepage_id')
        legacy_tag = str(attrs.pop('percent'))

        attrs['product'] = self.get_product(attrs.pop('product_id'))
        attrs['tag'] = self.get_tag_object(legacy_tag, self.tag_category, cepage)
        return attrs

    def create(self, validated_data):
        obj = validated_data.pop('product', None)
        tag = validated_data.pop('tag', None)

        if obj and tag:
            if tag not in obj.tags.all():
                obj.tags.add(tag)
            return obj

    def get_tag_object(self, tag, tag_category, cepage):
        cepage = cepage.name if isinstance(cepage, transfer_models.Cepages) else cepage
        if tag and tag_category and cepage:
            qs = tag_models.Tag.objects.filter(category=tag_category,
                                               value=slugify(f'{cepage} - {tag}'))
            if qs.exists():
                return qs.first()
