from datetime import datetime, timedelta

from django.contrib.auth import get_user_model
from django.utils import timezone

from rest_framework.test import APITestCase

from event.models import Event, Place, ActivityTag, ActivityTemplate

from api.test.testcases import ModelAPITestCaseMixin
from api.test.utils import json_format

User = get_user_model()


class EventAPITests(ModelAPITestCaseMixin, APITestCase):
    model = Event
    url_label = 'event'

    list_ordering = 'beginning_date'

    @property
    def user_auth_mapping(self):
        return {
            'create': self.user,
        }

    def get_expected_data(self, instance):
        return json_format({
            'url': (
                'http://testserver/api/event/{pk}/'
                .format(pk=instance.pk)
            ),
            'id': instance.id,
            'title': instance.title,
            'slug': instance.slug,
            'description': instance.description,
            'beginning_date': instance.beginning_date,
            'ending_date': instance.ending_date,
            'created_by': instance.created_by.get_full_name(),
            'created_at': self.now,
        })

    @property
    def instances_data(self):
        return [
            {
                'title': "A first event",
                'slug': 'first-event',
                'description': "It's the first event.",
                'beginning_date': self.now + timedelta(days=100),
                'ending_date': self.now + timedelta(days=120),
                'created_by': self.user,
            },
            {
                'title': "Another event",
                'slug': 'another-event',
                'description': "It's another event.",
                'beginning_date': self.now + timedelta(days=50),
                'ending_date': self.now + timedelta(days=60),
                'created_by': self.user,
            },
        ]

    @property
    def create_data(self):
        return {
            'title': "An Event",
            'slug': 'event',
            'description': "I am an event.",
            'beginning_date': '2017-08-10 05:30:00',
            'ending_date': '2017-10-21 17:16:20',
        }

    @property
    def create_expected(self):
        return {
            **self.create_data,
            'beginning_date': timezone.make_aware(
                datetime(2017, 8, 10, 5, 30, 0)),
            'ending_date': timezone.make_aware(
                datetime(2017, 10, 21, 17, 16, 20)),
            'id': 1,
            'created_by': self.user,
            'created_at': self.now,
        }

    @property
    def update_data(self):
        return {
            'title': "An updated event.",
            'slug': 'first-event',
            'description': "It's the first updated event.",
            'beginning_date': '2017-08-10 05:30:00',
            'ending_date': '2017-10-21 17:16:20',
            'created_by': 1,
        }

    @property
    def update_expected(self):
        return {
            **self.update_data,
            'beginning_date': timezone.make_aware(
                datetime(2017, 8, 10, 5, 30, 0)),
            'ending_date': timezone.make_aware(
                datetime(2017, 10, 21, 17, 16, 20)),
            'id': 1,
            'created_by': self.user,
            'created_at': self.now,
        }


class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
    model = ActivityTemplate
    url_label = 'activitytemplate'

    list_ordering = 'title'

    def setUp(self):
        super().setUp()

        self.event = Event.objects.create(**{
            'title': "event1",
            'slug': "slug1",
            'beginning_date': self.now + timedelta(days=30),
            'description': "C'est trop cool !",
            'ending_date': self.now + timedelta(days=31),
            'created_by': self.user,
        })

        self.activity_tag = ActivityTag.objects.create(**{
            'name': "tag2",
            'is_public': False,
            'color': "#222",
        })

    def get_url_model(self, *args, **kwargs):
        kwargs['event_pk'] = 1
        return super().get_url_model(*args, **kwargs)

    def get_url_object(self, *args, **kwargs):
        kwargs['event_pk'] = 1
        return super().get_url_object(*args, **kwargs)

    def get_expected_data(self, instance):
        return json_format({
            'url': (
                'http://testserver/api/event/{event_pk}/template/{pk}/'
                .format(event_pk=instance.event.pk, pk=instance.pk)
            ),
            'id': instance.id,
            'title': instance.title,
            'is_public': instance.is_public,
            'remarks': instance.remarks,
            'event': instance.event.pk,
            'tags': [
                {
                    **(tag.event and {
                        'url': (
                            'http://testserver/api/event/{event_pk}/'
                            'tag/{tag_pk}/'
                            .format(event_pk=tag.event.pk, tag_pk=tag.pk)
                        ),
                        'event': tag.event.pk,
                    } or {
                        'url': (
                            'http://testserver/api/tag/{tag_pk}/'
                            .format(tag_pk=tag.pk)
                        ),
                        'event': None,
                    }),
                    'id': tag.id,
                    'name': tag.name,
                    'is_public': tag.is_public,
                    'color': tag.color,
                }
                for tag in instance.tags.all()
            ],
            'has_perm': instance.has_perm,
            'min_perm': instance.min_perm,
            'max_perm': instance.max_perm,
            'description': instance.description,
        })

    @property
    def instances_data(self):
        return [
            {
                'title': "act temp1",
                'is_public': True,
                'remarks': "test remark",
                'event': self.event,
            },
            {
                'title': "act temp2",
                'is_public': False,
                'remarks': "test remark",
                'event': self.event,
                'tags': [self.activity_tag]
            },
        ]

    @property
    def create_data(self):
        return {
            'title': "act temp2",
            'is_public': False,
            'remarks': "test remark",
            'tags': [
                {
                    'name': "tag2",
                    'is_public': False,
                    'color': '#222',
                },
            ],
        }

    @property
    def create_expected(self):
        return {
            **self.create_data,
            'tags': [ActivityTag.objects.get(name='tag2')],
        }

    @property
    def update_data(self):
        return {
            'title': "act temp3",
            'is_public': False,
            'remarks': "another test remark",
            'tags': [
                {
                    'name': "tag2",
                    'is_public': False,
                    'color': '#222',
                },
                {
                    'name': "body",
                    'is_public': True,
                    'color': '#555',
                },
            ],
        }

    @property
    def update_expected(self):
        tag_root = ActivityTag.objects.get(name='tag2')
        self.assertIsNone(tag_root.event)

        tag_bound = ActivityTag.objects.get(name='body')
        self.assertEqual(tag_bound.event, self.event)

        return {
            'title': "act temp3",
            'is_public': False,
            'remarks': "another test remark",
            'tags': [tag_root, tag_bound]
        }


class BaseActivityTagAPITests:
    model = ActivityTag
    url_label = 'activitytag'

    list_ordering = ('is_public', 'name')

    def setUp(self):
        super().setUp()

        self.event = Event.objects.create(**{
            'title': "event1",
            'slug': "slug1",
            'beginning_date': self.now + timedelta(days=30),
            'description': "C'est trop cool !",
            'ending_date': self.now + timedelta(days=31),
            'created_by': self.user,
        })

    def get_expected_data(self, instance):
        return {
            **(instance.event and {
                'url': (
                    'http://testserver/api/event/{event_pk}/tag/{pk}/'
                    .format(event_pk=instance.event.pk, pk=instance.pk)
                ),
                'event': instance.event.pk,
            } or {
                'url': (
                    'http://testserver/api/tag/{pk}/'
                    .format(pk=instance.pk)
                ),
                'event': None,
            }),
            'id': instance.id,
            'name': instance.name,
            'is_public': instance.is_public,
            'color': instance.color,
        }

    @property
    def instances_data(self):
        return [
            {
                'name': 'a tag',
                'is_public': False,
                'color': '#222',
                'event': None,
            },
            {
                'name': 'another tag',
                'is_public': True,
                'color': '#555',
                'event': self.event,
            }
        ]

    @property
    def create_data(self):
        return {
            'name': 'plop tag',
            'is_public': True,
            'color': '#888999',
        }

    @property
    def update_data(self):
        return {
            'name': 'this is the tag',
            'is_public': True,
            'color': '#333',
        }


class RootActivityTagAPITests(
            BaseActivityTagAPITests,
            ModelAPITestCaseMixin,
            APITestCase
        ):

    @property
    def list_expected(self):
        return [ActivityTag.objects.get(name='a tag')]

    @property
    def create_expected(self):
        return {
            **self.create_data,
            'event': None,
        }

    @property
    def instance_data(self):
        data = self.instances_data[0]
        self.assertIsNone(
            data['event'],
            msg="This test should use a tag unbound to any event.",
        )
        return data

    @property
    def update_expected(self):
        return {
            **self.update_data,
            'event': None,
        }


class EventActivityTagAPITests(
            BaseActivityTagAPITests,
            ModelAPITestCaseMixin,
            APITestCase
        ):

    def get_url_model(self, *args, **kwargs):
        kwargs['event_pk'] = 1
        return super().get_url_model(*args, **kwargs)

    def get_url_object(self, *args, **kwargs):
        kwargs['event_pk'] = 1
        return super().get_url_object(*args, **kwargs)

    @property
    def list_expected(self):
        return [
            ActivityTag.objects.get(name='a tag'),
            ActivityTag.objects.get(name='another tag'),
        ]

    @property
    def create_expected(self):
        return {
            **self.create_data,
            'event': self.event,
        }

    @property
    def instance_data(self):
        data = self.instances_data[1]
        self.assertIsNotNone(
            data['event'],
            msg="This test should use an event-bound tag.",
        )
        return data

    @property
    def update_expected(self):
        return {
            **self.update_data,
            'event': self.event,
        }


class BasePlaceAPITests:
    model = Place
    url_label = 'place'

    list_ordering = 'name'

    def setUp(self):
        super().setUp()

        self.event = Event.objects.create(**{
            'title': "event1",
            'slug': "slug1",
            'beginning_date': self.now + timedelta(days=30),
            'description': "C'est trop cool !",
            'ending_date': self.now + timedelta(days=31),
            'created_by': self.user,
        })

    def get_expected_data(self, instance):
        return {
            **(instance.event and {
                'url': (
                    'http://testserver/api/event/{event_pk}/place/{pk}/'
                    .format(event_pk=instance.event.pk, pk=instance.pk)
                ),
                'event': instance.event.pk,
            } or {
                'url': (
                    'http://testserver/api/place/{pk}/'
                    .format(pk=instance.pk)
                ),
                'event': None,
            }),
            'id': instance.id,
            'name': instance.name,
            'description': instance.description,
        }

    @property
    def instances_data(self):
        return [
            {
                'name': 'a place',
                'event': None,
                'description': 'a description',
            },
            {
                'name': 'another place',
                'event': self.event,
            }
        ]

    @property
    def create_data(self):
        return {
            'name': 'plop place',
            'description': 'the couro is a chill place',
        }

    @property
    def update_data(self):
        return {
            'name': 'this is the place',
            'description': 'to be',
        }


class RootPlaceAPITests(
            BasePlaceAPITests,
            ModelAPITestCaseMixin,
            APITestCase
        ):

    @property
    def list_expected(self):
        return [Place.objects.get(name='a place')]

    @property
    def create_expected(self):
        return {
            **self.create_data,
            'event': None,
        }

    @property
    def instance_data(self):
        data = self.instances_data[0]
        self.assertIsNone(
            data['event'],
            msg="This test should use a place unbound to any event.",
        )
        return data

    @property
    def update_expected(self):
        return {
            **self.update_data,
            'event': None,
        }


class EventPlaceTagAPITests(
            BasePlaceAPITests,
            ModelAPITestCaseMixin,
            APITestCase
        ):

    def get_url_model(self, *args, **kwargs):
        kwargs['event_pk'] = 1
        return super().get_url_model(*args, **kwargs)

    def get_url_object(self, *args, **kwargs):
        kwargs['event_pk'] = 1
        return super().get_url_object(*args, **kwargs)

    @property
    def list_expected(self):
        return [
            Place.objects.get(name='a place'),
            Place.objects.get(name='another place'),
        ]

    @property
    def create_expected(self):
        return {
            **self.create_data,
            'event': self.event,
        }

    @property
    def instance_data(self):
        data = self.instances_data[1]
        self.assertIsNotNone(
            data['event'],
            msg="This test should use an event-bound place.",
        )
        return data

    @property
    def update_expected(self):
        return {
            **self.update_data,
            'event': self.event,
        }