from itertools import chain

import yaml
from django.utils.text import slugify
from pycountry import countries, subdivisions
from rest_framework import serializers

from collection import models
from establishment.models import EstablishmentType
from location.models import Country, Region, WineRegion
from main.models import SiteSettings
from review.models import Review
from transfer.mixins import TransferSerializerMixin
from translation.models import Language


class GuideSerializer(TransferSerializerMixin):
    id = serializers.IntegerField()
    title = serializers.CharField()
    vintage = serializers.IntegerField()
    slug = serializers.CharField()
    state = serializers.CharField()
    site_id = serializers.IntegerField()
    inserter_field = serializers.CharField()

    class Meta:
        model = models.Guide
        fields = (
            'id',
            'title',
            'vintage',
            'slug',
            'state',
            'site_id',
            'inserter_field',
        )

    def validate(self, attrs):
        """Overridden validate method."""
        attrs['old_id'] = attrs.pop('id')
        attrs['name'] = attrs.pop('title')
        attrs['vintage'] = int(attrs.pop('vintage'))
        attrs['state'] = self.get_state(attrs.pop('state'))
        attrs['site'] = self.get_site(attrs.pop('site_id'))
        attrs['guide_type'] = self.get_guide_type(attrs.pop('inserter_field'))
        return attrs

    def get_state(self, state: str):
        if state == 'built':
            return models.Guide.BUILT
        elif state == 'removing':
            return models.Guide.REMOVING
        elif state == 'building':
            return models.Guide.BUILDING
        else:
            return models.Guide.WAITING

    def get_site(self, site_id):
        qs = SiteSettings.objects.filter(old_id=site_id)
        if qs.exists():
            return qs.first()

    def get_guide_type(self, inserter_field):
        if inserter_field:
            if inserter_field.startswith('restaurant'):
                return models.Guide.RESTAURANT
            elif inserter_field.startswith('wine'):
                return models.Guide.WINE


class GuideFilterSerializer(TransferSerializerMixin):
    id = serializers.IntegerField()
    year = serializers.CharField(allow_null=True)
    establishment_type = serializers.CharField(allow_null=True)
    countries = serializers.CharField(allow_null=True)
    regions = serializers.CharField(allow_null=True)
    subregions = serializers.CharField(allow_null=True)
    wine_regions = serializers.CharField(allow_null=True)
    locales = serializers.CharField(allow_null=True)
    states = serializers.CharField(allow_null=True)

    max_mark = serializers.FloatField(allow_null=True)
    min_mark = serializers.FloatField(allow_null=True)
    marks_only = serializers.NullBooleanField()
    guide_id = serializers.IntegerField()

    class Meta:
        model = models.GuideFilter
        fields = (
            'id',
            'year',
            'establishment_type',
            'countries',
            'regions',
            'subregions',
            'wine_regions',
            'max_mark',
            'min_mark',
            'marks_only',
            'locales',
            'states',
            'guide_id',
        )

    def create(self, validated_data):
        qs = self.Meta.model.objects.filter(guide=validated_data.get('guide'))
        if not qs.exists():
            return super().create(validated_data)

    @staticmethod
    def parse_ruby_helper(raw_value: str):
        """Parse RubyActiveSupport records"""
        def convert_entry(loader, node):
            return {e[0]: e[1] for e in loader.construct_pairs(node)}

        loader = yaml.Loader
        loader.add_constructor('!ruby/hash:ActiveSupport::HashWithIndifferentAccess', convert_entry)
        return yaml.load(raw_value, Loader=loader) if raw_value else None

    @staticmethod
    def parse_dictionary(dictionary: dict):
        """
        Exclude root values from dictionary.
        Convert {key_2: [value_1, value_2]} into
                [value_1, value_2]
        """
        return list(chain.from_iterable(dictionary.values()))

    @staticmethod
    def parse_nested_dictionary(dictionary: dict):
        """
        Exclude root values from dictionary.
        Convert {key_1: {key_2: [value_1, value_2]}} into
                [value_1, value_2]
        """
        l = []
        for i in dictionary:
            l.append(list(chain.from_iterable(list(dictionary[i].values()))))
        return list(chain.from_iterable(l))

    def get_country(self, code_alpha_3: str) -> Country:
        country = countries.get(alpha_3=code_alpha_3.upper())
        if country:
            country_name = country.name
            country_code = country.alpha_2
            if country_name and country_code:
                country, _ = Country.objects.get_or_create(
                    code__icontains=country_code,
                    name__contains={'en-GB': country_name},
                    defaults={
                        'code': country_code,
                        'name': {'en-GB': country_name}
                    }
                )
                return country

    def get_region(self, region_code_alpha_3: str,
                         country_code_alpha_3: str,
                         sub_region_code_alpha_3: str = None):
        country = self.get_country(country_code_alpha_3)
        if country:
            country_code_alpha_2 = country.code.upper()
            region_qs = Region.objects.filter(code__iexact=region_code_alpha_3,
                                              country__code__iexact=country_code_alpha_2)

            if region_qs.exists():
                return region_qs.first()

            # If region isn't existed, check sub region for parent_code (region code)
            if sub_region_code_alpha_3:
                #  sub region
                subdivision = subdivisions.get(
                    code=f"{country_code_alpha_2}-{sub_region_code_alpha_3}")
                if subdivision:
                    # try with parent code
                    subdivision_region = subdivisions.get(code=subdivision.__dict__.get('_fields')
                                                                                   .get('parent_code'))
                    if not subdivision_region:
                        # try with parent
                        subdivision_region = subdivisions.get(code=subdivision.__dict__.get('_fields')
                                                                                       .get('parent'))
                    if subdivision_region:
                        obj = Region.objects.create(
                            name=subdivision_region.name,
                            code=subdivision_region.code,
                            country=country)
                        return obj

    def validate_year(self, value):
        return self.parse_ruby_helper(value)

    def validate_establishment_type(self, value):
        return self.parse_ruby_helper(value)

    def validate_countries(self, value):
        return self.parse_ruby_helper(value)

    def validate_regions(self, value):
        return self.parse_ruby_helper(value)

    def validate_subregions(self, value):
        return self.parse_ruby_helper(value)

    def validate_wine_regions(self, value):
        return self.parse_ruby_helper(value)

    def validate_wine_classifications(self, value):
        return self.parse_ruby_helper(value)

    def validate_wine_colors(self, value):
        return self.parse_ruby_helper(value)

    def validate_wine_types(self, value):
        return self.parse_ruby_helper(value)

    def validate_locales(self, value):
        return self.parse_ruby_helper(value)

    def validate_states(self, value):
        return self.parse_ruby_helper(value)

    def validate(self, attrs):
        sub_regions = attrs.pop('subregions')
        regions = attrs.pop('regions')

        attrs['old_id'] = attrs.pop('id')
        attrs['review_vintage_json'] = self.get_review_vintage(attrs.pop('year'))
        attrs['establishment_type_json'] = self.get_establishment_type_ids(
            attrs.pop('establishment_type'))
        attrs['country_json'] = self.get_country_ids(attrs.pop('countries'))
        attrs['region_json'] = self.get_region_ids(regions=regions,
                                                   sub_regions=sub_regions)
        attrs['sub_region_json'] = self.get_sub_region_ids(sub_regions)
        attrs['wine_region_json'] = self.get_wine_region_ids(attrs.pop('wine_regions'))
        attrs['with_mark'] = attrs.pop('marks_only') or True
        attrs['locale_json'] = self.get_locale_ids(attrs.pop('locales'))
        attrs['review_state_json'] = self.get_review_state(attrs.pop('states'))
        attrs['guide'] = self.get_guide(attrs.pop('guide_id'))
        return attrs

    def get_review_vintage(self, year):
        if hasattr(year, '__iter__'):
            return {'vintage': list(set(int(i) for i in set(year) if i.isdigit()))}
        return {'vintage': [year, ]}

    def get_establishment_type_ids(self, establishment_types):
        establishment_type_ids = []
        if establishment_types:
            for establishment_type in establishment_types:
                establishment_type_qs = EstablishmentType.objects.filter(index_name__iexact=establishment_type)
                if not establishment_type_qs.exists():
                    obj = EstablishmentType.objects.create(
                        name={'en-GB': establishment_type.capitalize()},
                        index_name=slugify(establishment_type))
                else:
                    obj = establishment_type_qs.first()
                establishment_type_ids.append(obj.id)
        return {'id': list(set(establishment_type_ids))}

    def get_country_ids(self, country_codes_alpha_3):
        country_ids = []
        if country_codes_alpha_3:
            for code_alpha_3 in country_codes_alpha_3:
                # Code can be an empty string.
                if code_alpha_3 and not code_alpha_3 == 'AAA':
                    country = self.get_country(code_alpha_3)
                    if not country:
                        raise serializers.ValidationError({'detail': f'Country with alpha code 3 -'
                                                                     f'{code_alpha_3}, is not found.'})
                    country_ids.append(country.id)
        return {'id': list(set(country_ids))}

    def get_region_ids(self, regions, sub_regions):
        region_ids = []
        if regions:
            for country_code_alpha_3 in regions:
                for region_code_alpha_3 in regions[country_code_alpha_3]:
                    # Get region from sub region code.
                    if sub_regions and country_code_alpha_3 in sub_regions:
                        if region_code_alpha_3 in sub_regions[country_code_alpha_3]:
                            for sub_region_code_alpha_3 in sub_regions[country_code_alpha_3][region_code_alpha_3]:
                                region = self.get_region(
                                    region_code_alpha_3=region_code_alpha_3,
                                    country_code_alpha_3=country_code_alpha_3,
                                    sub_region_code_alpha_3=sub_region_code_alpha_3)
                                if region:
                                    region_ids.append(region.id)
        return {'id': list(set(region_ids))}

    def get_sub_region_ids(self, sub_regions):
        sub_region_ids = []
        if sub_regions:
            for country_code_alpha_3 in sub_regions:
                # FRA etc.
                if country_code_alpha_3 in sub_regions:
                    for region_code_alpha_3 in sub_regions[country_code_alpha_3]:
                        # B, C, A etc.
                        if region_code_alpha_3 in sub_regions[country_code_alpha_3]:
                            for sub_region_code_alpha_3 in sub_regions[country_code_alpha_3][region_code_alpha_3]:
                                # 24, 32 etc.
                                # Get parent region
                                region = self.get_region(
                                    region_code_alpha_3=region_code_alpha_3,
                                    country_code_alpha_3=country_code_alpha_3,
                                    sub_region_code_alpha_3=sub_region_code_alpha_3)
                                if region:
                                    sub_region_qs = Region.objects.filter(parent_region__code=region.code)
                                    if sub_region_qs.exists():
                                        sub_region_ids.append(sub_region_qs.first().id)
                                    else:
                                        subdivision = subdivisions.get(code=region.code.upper())
                                        if subdivision:
                                            sub_region, _ = Region.objects.get_or_create(
                                                name=subdivision.name,
                                                code=subdivision.code,
                                                parent_region=region,
                                                country=region.country)
                                            sub_region_ids.append(sub_region.id)
        return {'id': list(set(sub_region_ids))}

    def get_wine_region_ids(self, wine_regions):
        wine_region_ids = []
        if wine_regions:
            for wine_region in wine_regions:
                qs = WineRegion.objects.filter(name__iexact=wine_region)
                if not qs.exists():
                    raise serializers.ValidationError({
                        'detail': f'Wine region - {wine_region}, is not found.'})
                wine_region_ids.append(qs.first().id)
        return {'id': list(set(wine_region_ids))}

    def get_locale_ids(self, locales):
        locale_ids = []
        if locales:
            for locale in [locale for locale in locales if locale]:
                if len(locale) == 2:
                    qs = Language.objects.filter(locale__startswith=locale)
                else:
                    qs = Language.objects.filter(locale=locale)
                if not qs.exists():
                    raise serializers.ValidationError({
                        'detail': f'Language with locale - {locale}, is not found.'})
                locale_ids.extend(qs.values_list('id', flat=True))
        return {'id': list(set(locale_ids))}

    def get_review_state(self, states):
        review_states = []
        if states:
            for state in [state for state in states if state]:
                if state == 'published':
                    review_states.append(Review.READY)
                else:
                    review_states.append(Review.TO_INVESTIGATE)
        return {'state': list(set(review_states))}

    def get_guide(self, old_guide_id: int):
        qs = models.Guide.objects.filter(old_id=old_guide_id)
        if qs.exists():
            return qs.first()
