#
# steering, Programme/schedule management for AURA
#
# Copyright (C) 2011-2017, 2020, Ernesto Rico Schmidt
# Copyright (C) 2017-2019, Ingo Leindecker
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from rest_framework import serializers

from profile.models import Profile
from profile.serializers import ProfileSerializer
from program.models import Show, Schedule, TimeSlot, Category, FundingCategory, Host, Topic, MusicFocus, Note, Type, Language, \
    RRule
from steering.settings import THUMBNAIL_SIZES


class UserSerializer(serializers.ModelSerializer):
    # Add profile fields to JSON
    profile = ProfileSerializer()

    class Meta:
        model = User
        fields = '__all__'

    def create(self, validated_data):
        """
        Create and return a new User instance, given the validated data.
        """

        profile_data = validated_data.pop('profile')

        user = super(UserSerializer, self).create(validated_data)
        user.date_joined = timezone.now()
        user.set_password(validated_data['password'])
        user.save()

        profile = Profile(user=user, cba_username=profile_data.get('cba_username').strip(),
                          cba_user_token=profile_data.get('cba_user_token').strip())
        profile.save()

        return user

    def update(self, instance, validated_data):
        """
        Update and return an existing User instance, given the validated data.
        """

        user = self.context['user']

        instance.first_name = validated_data.get('first_name', instance.first_name)
        instance.last_name = validated_data.get('last_name', instance.last_name)
        instance.email = validated_data.get('email', instance.email)

        if user.is_superuser:
            instance.groups = validated_data.get('groups', instance.groups)
            instance.user_permissions = validated_data.get('user_permissions', instance.user_permissions)
            instance.is_active = validated_data.get('is_active', instance.is_active)
            instance.is_staff = validated_data.get('is_staff', instance.is_staff)
            instance.is_superuser = validated_data.get('is_superuser', instance.is_superuser)

        # TODO: How to hook into this from ProfileSerializer without having to call it here?
        try:
            profile = Profile.objects.get(user=instance.id)
        except ObjectDoesNotExist:
            profile = Profile.objects.create(user=instance, **validated_data['profile'])

        profile.cba_username = validated_data['profile'].get('cba_username')
        profile.cba_user_token = validated_data['profile'].get('cba_user_token')
        profile.save()

        instance.save()
        return instance


class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = '__all__'

    def update(self, instance, validated_data):
        """
        Update and return an existing Category instance, given the validated data.
        """
        instance.category = validated_data.get('category', instance.category)
        instance.abbrev = validated_data.get('abbrev', instance.abbrev)
        instance.slug = validated_data.get('slug', instance.slug)
        instance.color = validated_data.get('color', instance.color)
        instance.description = validated_data.get('description', instance.description)
        instance.is_active = validated_data.get('is_active', instance.is_active)

        instance.save()
        return instance


class HostSerializer(serializers.ModelSerializer):
    thumbnails = serializers.SerializerMethodField()  # Read-only

    @staticmethod
    def get_thumbnails(host):
        """Returns thumbnails"""
        thumbnails = []

        if host.image.name and THUMBNAIL_SIZES:
            for size in THUMBNAIL_SIZES:
                thumbnails.append(host.image.crop[size].name)

        return thumbnails

    class Meta:
        model = Host
        fields = '__all__'

    def update(self, instance, validated_data):
        """
        Update and return an existing Host instance, given the validated data.
        """

        instance.name = validated_data.get('name', instance.name)
        instance.is_active = validated_data.get('is_active', instance.is_active)
        instance.email = validated_data.get('email', instance.email)
        instance.website = validated_data.get('website', instance.website)
        instance.biography = validated_data.get('biography', instance.biography)
        instance.googleplus_url = validated_data.get('googleplus_url', instance.googleplus_url)
        instance.facebook_url = validated_data.get('facebook_url', instance.facebook_url)
        instance.twitter_url = validated_data.get('twitter_url', instance.twitter_url)
        instance.linkedin_url = validated_data.get('linkedin_url', instance.linkedin_url)
        instance.youtube_url = validated_data.get('youtube_url', instance.youtube_url)
        instance.dorftv_url = validated_data.get('dorftv_url', instance.dorftv_url)
        instance.cba_url = validated_data.get('cba_url', instance.cba_url)
        instance.image = validated_data.get('image', instance.image)
        instance.ppoi = validated_data.get('ppoi', instance.ppoi)

        instance.save()
        return instance


class LanguageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Language
        fields = '__all__'

    def update(self, instance, validated_data):
        """
        Update and return an existing Language instance, given the validated data.
        """
        instance.name = validated_data.get('name', instance.name)
        instance.is_active = validated_data.get('is_active', instance.is_active)

        instance.save()
        return instance


class TopicSerializer(serializers.ModelSerializer):
    class Meta:
        model = Topic
        fields = '__all__'

    def update(self, instance, validated_data):
        """
        Update and return an existing Topic instance, given the validated data.
        """
        instance.topic = validated_data.get('topic', instance.topic)
        instance.abbrev = validated_data.get('abbrev', instance.abbrev)
        instance.slug = validated_data.get('slug', instance.slug)
        instance.is_active = validated_data.get('is_active', instance.is_active)

        instance.save()
        return instance


class MusicFocusSerializer(serializers.ModelSerializer):
    class Meta:
        model = MusicFocus
        fields = '__all__'

    def update(self, instance, validated_data):
        """
        Update and return an existing MusicFocus instance, given the validated data.
        """
        instance.focus = validated_data.get('focus', instance.focus)
        instance.abbrev = validated_data.get('abbrev', instance.abbrev)
        instance.slug = validated_data.get('slug', instance.slug)
        instance.is_active = validated_data.get('is_active', instance.is_active)

        instance.save()
        return instance


class TypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Type
        fields = '__all__'

    def update(self, instance, validated_data):
        """
        Update and return an existing Type instance, given the validated data.
        """
        instance.type = validated_data.get('type', instance.type)
        instance.slug = validated_data.get('slug', instance.slug)
        instance.color = validated_data.get('color', instance.color)
        instance.text_color = validated_data.get('text_color', instance.text_color)
        instance.is_active = validated_data.get('is_active', instance.is_active)

        instance.save()
        return instance


class FundingCategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = FundingCategory
        fields = '__all__'

    def update(self, instance, validated_data):
        """
        Update and return an existing FundingCategory instance, given the validated data.
        """
        instance.fundingcategory = validated_data.get('fundingcategory', instance.fundingcategory)
        instance.abbrev = validated_data.get('abbrev', instance.abbrev)
        instance.slug = validated_data.get('slug', instance.slug)
        instance.is_active = validated_data.get('is_active', instance.is_active)

        instance.save()
        return instance


class ShowSerializer(serializers.HyperlinkedModelSerializer):
    owners = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), many=True)
    category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all(), many=True)
    hosts = serializers.PrimaryKeyRelatedField(queryset=Host.objects.all(), many=True)
    language = serializers.PrimaryKeyRelatedField(queryset=Language.objects.all(), many=True)
    topic = serializers.PrimaryKeyRelatedField(queryset=Topic.objects.all(), many=True)
    musicfocus = serializers.PrimaryKeyRelatedField(queryset=MusicFocus.objects.all(), many=True)
    type = serializers.PrimaryKeyRelatedField(queryset=Type.objects.all())
    fundingcategory = serializers.PrimaryKeyRelatedField(queryset=FundingCategory.objects.all())
    predecessor = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all(), required=False, allow_null=True)
    thumbnails = serializers.SerializerMethodField()  # Read-only

    @staticmethod
    def get_thumbnails(show):
        """Returns thumbnails"""
        thumbnails = []

        if show.image.name and THUMBNAIL_SIZES:
            for size in THUMBNAIL_SIZES:
                thumbnails.append(show.image.crop[size].name)

        return thumbnails

    class Meta:
        model = Show
        fields = ('id', 'name', 'slug', 'image', 'ppoi', 'logo', 'short_description', 'description',
                  'email', 'website', 'created', 'last_updated', 'type', 'fundingcategory',
                  'predecessor', 'cba_series_id', 'default_id', 'category', 'hosts',
                  'owners', 'language', 'topic', 'musicfocus', 'thumbnails', 'is_active', 'is_public')

    def create(self, validated_data):
        """
        Create and return a new Show instance, given the validated data.
        """

        owners = validated_data.pop('owners')
        category = validated_data.pop('category')
        hosts = validated_data.pop('hosts')
        language = validated_data.pop('language')
        topic = validated_data.pop('topic')
        musicfocus = validated_data.pop('musicfocus')

        show = Show.objects.create(**validated_data)

        # Save many-to-many relationships
        show.owners.set(owners)
        show.category.set(category)
        show.hosts.set(hosts)
        show.language.set(language)
        show.topic.set(topic)
        show.musicfocus.set(musicfocus)

        show.save()
        return show

    def update(self, instance, validated_data):
        """
        Update and return an existing Show instance, given the validated data.
        """

        user = self.context['user']

        instance.name = validated_data.get('name', instance.name)
        instance.slug = validated_data.get('slug', instance.slug)
        instance.image = validated_data.get('image', instance.image)
        instance.ppoi = validated_data.get('ppoi', instance.ppoi)
        instance.logo = validated_data.get('logo', instance.logo)
        instance.short_description = validated_data.get('short_description', instance.short_description)
        instance.description = validated_data.get('description', instance.description)
        instance.email = validated_data.get('email', instance.email)
        instance.website = validated_data.get('website', instance.website)
        instance.cba_series_id = validated_data.get('cba_series_id', instance.cba_series_id)
        instance.default_id = validated_data.get('default_id', instance.default_id)

        # Only superusers may update the following fields
        if user.is_superuser:
            instance.owners.set(validated_data.get('owners', instance.owners))
            instance.category.set(validated_data.get('category', instance.category))
            instance.hosts.set(validated_data.get('hosts', instance.hosts))
            instance.language.set(validated_data.get('language', instance.language))
            instance.topic.set(validated_data.get('topic', instance.topic))
            instance.musicfocus.set(validated_data.get('musicfocus', instance.musicfocus))
            instance.type = validated_data.get('type', instance.type)
            instance.fundingcategory = validated_data.get('fundingcategory', instance.fundingcategory)
            instance.predecessor = validated_data.get('predecessor', instance.predecessor)
            instance.is_active = validated_data.get('is_active', instance.is_active)
            instance.is_public = validated_data.get('is_public', instance.is_public)

        instance.save()
        return instance


class ScheduleSerializer(serializers.ModelSerializer):
    rrule = serializers.PrimaryKeyRelatedField(queryset=RRule.objects.all())
    show = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all())

    class Meta:
        model = Schedule
        fields = '__all__'

    def create(self, validated_data):
        """Create and return a new Schedule instance, given the validated data."""

        rrule = validated_data.pop('rrule')
        show = validated_data.pop('show')

        schedule = Schedule.objects.create(**validated_data)
        schedule.rrule = rrule
        schedule.show = show

        schedule.save()
        return schedule

    def update(self, instance, validated_data):
        """Update and return an existing Schedule instance, given the validated data."""

        instance.byweekday = validated_data.get('byweekday', instance.byweekday)
        instance.dstart = validated_data.get('dstart', instance.dstart)
        instance.tstart = validated_data.get('tstart', instance.tstart)
        instance.tend = validated_data.get('tend', instance.tend)
        instance.until = validated_data.get('until', instance.until)
        instance.is_repetition = validated_data.get('is_repetition', instance.is_repetition)
        instance.default_id = validated_data.get('default_id', instance.default_id)
        instance.automation_id = validated_data.get('automation_id', instance.automation_id)
        instance.rrule = validated_data.get('rrule', instance.rrule)
        instance.show = validated_data.get('show', instance.show)
        instance.add_days_no = validated_data.get('add_days_no', instance.add_days_no)
        instance.add_business_days_only = validated_data.get('add_business_days_only', instance.add_business_days_only)

        instance.save()
        return instance


class TimeSlotSerializer(serializers.ModelSerializer):
    show = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all())
    schedule = serializers.PrimaryKeyRelatedField(queryset=Schedule.objects.all())

    class Meta:
        model = TimeSlot
        fields = '__all__'

    def create(self, validated_data):
        """Create and return a new TimeSlot instance, given the validated data."""
        return TimeSlot.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """Update and return an existing Show instance, given the validated data."""

        # Only save certain fields
        instance.memo = validated_data.get('memo', instance.memo)
        instance.is_repetition = validated_data.get('is_repetition', instance.is_repetition)
        instance.playlist_id = validated_data.get('playlist_id', instance.playlist_id)
        instance.save()
        return instance


class NoteSerializer(serializers.ModelSerializer):
    show = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all())
    timeslot = serializers.PrimaryKeyRelatedField(queryset=TimeSlot.objects.all())
    host = serializers.PrimaryKeyRelatedField(queryset=Host.objects.all())
    thumbnails = serializers.SerializerMethodField()  # Read-only

    @staticmethod
    def get_thumbnails(note):
        """Returns thumbnails"""
        thumbnails = []

        if note.image.name and THUMBNAIL_SIZES:
            for size in THUMBNAIL_SIZES:
                thumbnails.append(note.image.crop[size].name)

        return thumbnails

    class Meta:
        model = Note
        fields = '__all__'

    def create(self, validated_data):
        """Create and return a new Note instance, given the validated data."""

        # Save the creator
        validated_data['user_id'] = self.context['user_id']

        # Try to retrieve audio URL from CBA
        validated_data['audio_url'] = Note.get_audio_url(validated_data['cba_id'])

        note = Note.objects.create(**validated_data)

        # Assign note to timeslot
        if note.timeslot_id is not None:
            try:
                timeslot = TimeSlot.objects.get(pk=note.timeslot_id)
                timeslot.note_id = note.id
                timeslot.save(update_fields=["note_id"])
            except ObjectDoesNotExist:
                pass

        return note

    def update(self, instance, validated_data):
        """Update and return an existing Note instance, given the validated data."""

        instance.show = validated_data.get('show', instance.show)
        instance.timeslot = validated_data.get('timeslot', instance.timeslot)
        instance.title = validated_data.get('title', instance.title)
        instance.slug = validated_data.get('slug', instance.slug)
        instance.summary = validated_data.get('summary', instance.summary)
        instance.content = validated_data.get('content', instance.content)
        instance.image = validated_data.get('image', instance.image)
        instance.ppoi = validated_data.get('ppoi', instance.ppoi)
        instance.status = validated_data.get('status', instance.status)
        instance.host = validated_data.get('host', instance.host)
        instance.cba_id = validated_data.get('cba_id', instance.cba_id)
        instance.audio_url = Note.get_audio_url(instance.cba_id)

        instance.save()

        # Remove existing note connections from timeslots
        timeslots = TimeSlot.objects.filter(note_id=instance.id)
        for ts in timeslots:
            ts.note_id = None
            ts.save(update_fields=["note_id"])

        # Assign note to timeslot
        if instance.timeslot.id is not None:
            try:
                timeslot = TimeSlot.objects.get(pk=instance.timeslot.id)
                timeslot.note_id = instance.id
                timeslot.save(update_fields=["note_id"])
            except ObjectDoesNotExist:
                pass

        return instance