from django.core.exceptions import ValidationError
from rest_framework import serializers

from orchestra.api.serializers import HyperlinkedModelSerializer, RelatedHyperlinkedModelSerializer
from orchestra.contrib.accounts.serializers import AccountSerializerMixin

from .directives import SiteDirective
from .models import Website, Content, WebsiteDirective
from .utils import normurlpath
from .validators import validate_domain_protocol



class RelatedDomainSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
    class Meta:
        model = Website.domains.field.related_model
        fields = ('url', 'id', 'name')


class RelatedWebAppSerializer(AccountSerializerMixin, RelatedHyperlinkedModelSerializer):
    class Meta:
        model = Content.webapp.field.related_model
        fields = ('url', 'id', 'name', 'type')


class ContentSerializer(serializers.ModelSerializer):
    webapp = RelatedWebAppSerializer()

    class Meta:
        model = Content
        fields = ('webapp', 'path')

    def get_identity(self, data):
        return '%s-%s' % (data.get('website'), data.get('path'))


class DirectiveSerializer(serializers.ModelSerializer):
    class Meta:
        model = WebsiteDirective
        fields = ('name', 'value')

    def to_representation(self, instance):
        return {prop.name: prop.value for prop in instance.all()}

    def to_internal_value(self, data):
        return data


class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
    domains = RelatedDomainSerializer(many=True, required=False)
    contents = ContentSerializer(required=False, many=True, source='content_set')
    directives = DirectiveSerializer(required=False)

    class Meta:
        model = Website
        fields = ('url', 'id', 'name', 'protocol', 'domains', 'is_active', 'contents', 'directives')
        postonly_fields = ('name',)

    def validate(self, data):
        """ Prevent multiples domains on the same protocol """
        # Validate location and directive uniqueness
        errors = []
        directives = data.get('directives', [])
        if directives:
            locations = set()
            for content in data.get('content_set', []):
                location = content.get('path')
                if location is not None:
                    locations.add(normurlpath(location))
            values = defaultdict(list)
            for name, value in directives.items():
                directive = {
                    'name': name,
                    'value': value,
                }
                try:
                    SiteDirective.get(name).validate_uniqueness(directive, values, locations)
                except ValidationError as err:
                    errors.append(err)
        # Validate domain protocol uniqueness
        instance = self.instance
        for domain in data['domains']:
            try:
                validate_domain_protocol(instance, domain, data['protocol'])
            except ValidationError as err:
                errors.append(err)
        if errors:
            raise ValidationError(errors)
        return data

    def create(self, validated_data):
        directives_data = validated_data.pop('directives')
        webapp = super(WebsiteSerializer, self).create(validated_data)
        for key, value in directives_data.items():
            WebsiteDirective.objects.create(webapp=webapp, name=key, value=value)
        return webap

    def update_directives(self, instance, directives_data):
        existing = {}
        for obj in instance.directives.all():
            existing[obj.name] = obj
        posted = set()
        for key, value in directives_data.items():
            posted.add(key)
            try:
                directive = existing[key]
            except KeyError:
                directive = instance.directives.create(name=key, value=value)
            else:
                if directive.value != value:
                    directive.value = value
                    directive.save(update_fields=('value',))
        for to_delete in set(existing.keys())-posted:
            existing[to_delete].delete()

    def update_contents(self, instance, contents_data):
        raise NotImplementedError

    def update_domains(self, instance, domains_data):
        raise NotImplementedError

    def update(self, instance, validated_data):
        directives_data = validated_data.pop('directives')
        domains_data = validated_data.pop('domains')
        contents_data = validated_data.pop('content_set')
        instance = super(WebsiteSerializer, self).update(instance, validated_data)
        self.update_directives(instance, directives_data)
        self.update_contents(instance, contents_data)
        self.update_domains(instance, domains_data)
        return instance