"""
Tests to cover nested serializers.

Doesn't cover model serializers.
"""
from __future__ import unicode_literals
from django.test import TestCase
from rest_framework import serializers
from . import models


class WritableNestedSerializerBasicTests(TestCase):
    """
    Tests for deserializing nested entities.
    Basic tests that use serializers that simply restore to dicts.
    """

    def setUp(self):
        class TrackSerializer(serializers.Serializer):
            order = serializers.IntegerField()
            title = serializers.CharField(max_length=100)
            duration = serializers.IntegerField()

        class AlbumSerializer(serializers.Serializer):
            album_name = serializers.CharField(max_length=100)
            artist = serializers.CharField(max_length=100)
            tracks = TrackSerializer(many=True)

        self.AlbumSerializer = AlbumSerializer

    def test_nested_validation_success(self):
        """
        Correct nested serialization should return the input data.
        """

        data = {
            'album_name': 'Discovery',
            'artist': 'Daft Punk',
            'tracks': [
                {'order': 1, 'title': 'One More Time', 'duration': 235},
                {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
                {'order': 3, 'title': 'Digital Love', 'duration': 239}
            ]
        }

        serializer = self.AlbumSerializer(data=data)
        self.assertEqual(serializer.is_valid(), True)
        self.assertEqual(serializer.object, data)

    def test_nested_validation_error(self):
        """
        Incorrect nested serialization should return appropriate error data.
        """

        data = {
            'album_name': 'Discovery',
            'artist': 'Daft Punk',
            'tracks': [
                {'order': 1, 'title': 'One More Time', 'duration': 235},
                {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
                {'order': 3, 'title': 'Digital Love', 'duration': 'foobar'}
            ]
        }
        expected_errors = {
            'tracks': [
                {},
                {},
                {'duration': ['Enter a whole number.']}
            ]
        }

        serializer = self.AlbumSerializer(data=data)
        self.assertEqual(serializer.is_valid(), False)
        self.assertEqual(serializer.errors, expected_errors)

    def test_many_nested_validation_error(self):
        """
        Incorrect nested serialization should return appropriate error data
        when multiple entities are being deserialized.
        """

        data = [
            {
                'album_name': 'Russian Red',
                'artist': 'I Love Your Glasses',
                'tracks': [
                    {'order': 1, 'title': 'Cigarettes', 'duration': 121},
                    {'order': 2, 'title': 'No Past Land', 'duration': 198},
                    {'order': 3, 'title': 'They Don\'t Believe', 'duration': 191}
                ]
            },
            {
                'album_name': 'Discovery',
                'artist': 'Daft Punk',
                'tracks': [
                    {'order': 1, 'title': 'One More Time', 'duration': 235},
                    {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
                    {'order': 3, 'title': 'Digital Love', 'duration': 'foobar'}
                ]
            }
        ]
        expected_errors = [
            {},
            {
                'tracks': [
                    {},
                    {},
                    {'duration': ['Enter a whole number.']}
                ]
            }
        ]

        serializer = self.AlbumSerializer(data=data, many=True)
        self.assertEqual(serializer.is_valid(), False)
        self.assertEqual(serializer.errors, expected_errors)


class WritableNestedSerializerObjectTests(TestCase):
    """
    Tests for deserializing nested entities.
    These tests use serializers that restore to concrete objects.
    """

    def setUp(self):
        # Couple of concrete objects that we're going to deserialize into
        class Track(object):
            def __init__(self, order, title, duration):
                self.order, self.title, self.duration = order, title, duration

            def __eq__(self, other):
                return (
                    self.order == other.order and
                    self.title == other.title and
                    self.duration == other.duration
                )

        class Album(object):
            def __init__(self, album_name, artist, tracks):
                self.album_name, self.artist, self.tracks = album_name, artist, tracks

            def __eq__(self, other):
                return (
                    self.album_name == other.album_name and
                    self.artist == other.artist and
                    self.tracks == other.tracks
                )

        # And their corresponding serializers
        class TrackSerializer(serializers.Serializer):
            order = serializers.IntegerField()
            title = serializers.CharField(max_length=100)
            duration = serializers.IntegerField()

            def restore_object(self, attrs, instance=None):
                return Track(attrs['order'], attrs['title'], attrs['duration'])

        class AlbumSerializer(serializers.Serializer):
            album_name = serializers.CharField(max_length=100)
            artist = serializers.CharField(max_length=100)
            tracks = TrackSerializer(many=True)

            def restore_object(self, attrs, instance=None):
                return Album(attrs['album_name'], attrs['artist'], attrs['tracks'])

        self.Album, self.Track = Album, Track
        self.AlbumSerializer = AlbumSerializer

    def test_nested_validation_success(self):
        """
        Correct nested serialization should return a restored object
        that corresponds to the input data.
        """

        data = {
            'album_name': 'Discovery',
            'artist': 'Daft Punk',
            'tracks': [
                {'order': 1, 'title': 'One More Time', 'duration': 235},
                {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
                {'order': 3, 'title': 'Digital Love', 'duration': 239}
            ]
        }
        expected_object = self.Album(
            album_name='Discovery',
            artist='Daft Punk',
            tracks=[
                self.Track(order=1, title='One More Time', duration=235),
                self.Track(order=2, title='Aerodynamic', duration=184),
                self.Track(order=3, title='Digital Love', duration=239),
            ]
        )

        serializer = self.AlbumSerializer(data=data)
        self.assertEqual(serializer.is_valid(), True)
        self.assertEqual(serializer.object, expected_object)

    def test_many_nested_validation_success(self):
        """
        Correct nested serialization should return multiple restored objects
        that corresponds to the input data when multiple objects are
        being deserialized.
        """

        data = [
            {
                'album_name': 'Russian Red',
                'artist': 'I Love Your Glasses',
                'tracks': [
                    {'order': 1, 'title': 'Cigarettes', 'duration': 121},
                    {'order': 2, 'title': 'No Past Land', 'duration': 198},
                    {'order': 3, 'title': 'They Don\'t Believe', 'duration': 191}
                ]
            },
            {
                'album_name': 'Discovery',
                'artist': 'Daft Punk',
                'tracks': [
                    {'order': 1, 'title': 'One More Time', 'duration': 235},
                    {'order': 2, 'title': 'Aerodynamic', 'duration': 184},
                    {'order': 3, 'title': 'Digital Love', 'duration': 239}
                ]
            }
        ]
        expected_object = [
            self.Album(
                album_name='Russian Red',
                artist='I Love Your Glasses',
                tracks=[
                    self.Track(order=1, title='Cigarettes', duration=121),
                    self.Track(order=2, title='No Past Land', duration=198),
                    self.Track(order=3, title='They Don\'t Believe', duration=191),
                ]
            ),
            self.Album(
                album_name='Discovery',
                artist='Daft Punk',
                tracks=[
                    self.Track(order=1, title='One More Time', duration=235),
                    self.Track(order=2, title='Aerodynamic', duration=184),
                    self.Track(order=3, title='Digital Love', duration=239),
                ]
            )
        ]

        serializer = self.AlbumSerializer(data=data, many=True)
        self.assertEqual(serializer.is_valid(), True)
        self.assertEqual(serializer.object, expected_object)


class ForeignKeyNestedSerializerUpdateTests(TestCase):
    def setUp(self):
        class Artist(object):
            def __init__(self, name):
                self.name = name

            def __eq__(self, other):
                return self.name == other.name

        class Album(object):
            def __init__(self, name, artist):
                self.name, self.artist = name, artist

            def __eq__(self, other):
                return self.name == other.name and self.artist == other.artist

        class ArtistSerializer(serializers.Serializer):
            name = serializers.CharField()

            def restore_object(self, attrs, instance=None):
                if instance:
                    instance.name = attrs['name']
                else:
                    instance = Artist(attrs['name'])
                return instance

        class AlbumSerializer(serializers.Serializer):
            name = serializers.CharField()
            by = ArtistSerializer(source='artist')

            def restore_object(self, attrs, instance=None):
                if instance:
                    instance.name = attrs['name']
                    instance.artist = attrs['artist']
                else:
                    instance = Album(attrs['name'], attrs['artist'])
                return instance

        self.Artist = Artist
        self.Album = Album
        self.AlbumSerializer = AlbumSerializer

    def test_create_via_foreign_key_with_source(self):
        """
        Check that we can both *create* and *update* into objects across
        ForeignKeys that have a `source` specified.
        Regression test for #1170
        """
        data = {
            'name': 'Discovery',
            'by': {'name': 'Daft Punk'},
        }

        expected = self.Album(artist=self.Artist('Daft Punk'), name='Discovery')

        # create
        serializer = self.AlbumSerializer(data=data)
        self.assertEqual(serializer.is_valid(), True)
        self.assertEqual(serializer.object, expected)

        # update
        original = self.Album(artist=self.Artist('The Bats'), name='Free All the Monsters')
        serializer = self.AlbumSerializer(instance=original, data=data)
        self.assertEqual(serializer.is_valid(), True)
        self.assertEqual(serializer.object, expected)


class NestedModelSerializerUpdateTests(TestCase):
    def test_second_nested_level(self):
        john = models.Person.objects.create(name="john")

        post = john.blogpost_set.create(title="Test blog post")
        post.blogpostcomment_set.create(text="I hate this blog post")
        post.blogpostcomment_set.create(text="I love this blog post")

        class BlogPostCommentSerializer(serializers.ModelSerializer):
            class Meta:
                model = models.BlogPostComment

        class BlogPostSerializer(serializers.ModelSerializer):
            comments = BlogPostCommentSerializer(many=True, source='blogpostcomment_set')
            class Meta:
                model = models.BlogPost
                fields = ('id', 'title', 'comments')

        class PersonSerializer(serializers.ModelSerializer):
            posts = BlogPostSerializer(many=True, source='blogpost_set')
            class Meta:
                model = models.Person
                fields = ('id', 'name', 'age', 'posts')

        serialize = PersonSerializer(instance=john)
        deserialize = PersonSerializer(data=serialize.data, instance=john)
        self.assertTrue(deserialize.is_valid())

        result = deserialize.object
        result.save()
        self.assertEqual(result.id, john.id)
