from datetime import datetime

from django.test import TestCase

from smoothglue.authentication.models import PlatformUser
from smoothglue.duplication.exceptions import DuplicationError
from smoothglue.duplication.utils import (
    duplicate_object,
    get_parent_field_name,
    setup_fields_to_duplicate,
)
from smoothglue.tracker.models import APIChangeLog, AppErrorLog


class TestDuplication(TestCase):
    def setUp(self):
        self.test_item = APIChangeLog.objects.create(
            username="tester1", data={"foo": "bar"}, timestamp=datetime.now()
        )

    def test_valid_duplication_with_map(self):
        new_test_item = duplicate_object(
            self.test_item,
            include_children=False,
            included_fields=["data", "timestamp"],
            field_mapper={"username": "tester45"},
        )
        new_test_item.save()

        self.assertEqual(len(APIChangeLog.objects.all()), 2)
        self.assertEqual(self.test_item.data, new_test_item.data)
        self.assertNotEqual(self.test_item.username, new_test_item.username)

    def test_valid_duplication_without_map(self):
        new_test_item = duplicate_object(
            self.test_item,
            include_children=False,
            included_fields=["data", "username", "timestamp"],
        )
        new_test_item.save()
        self.assertEqual(len(APIChangeLog.objects.all()), 2)

        self.assertEqual(self.test_item.data, new_test_item.data)
        self.assertEqual(self.test_item.username, new_test_item.username)

        self.assertNotEqual(self.test_item.id, new_test_item.id)

    def test_invalid_duplication_with_pk_field(self):
        with self.assertRaises(DuplicationError):
            duplicate_object(
                self.test_item,
                include_children=False,
                included_fields=["id", "data", "username", "timestamp"],
            )

        self.assertEqual(len(APIChangeLog.objects.all()), 1)

    def test_invalid_duplication_with_conflicting_fields(self):
        with self.assertRaises(DuplicationError):
            duplicate_object(
                self.test_item,
                include_children=False,
                included_fields=["data", "username", "timestamp"],
                field_mapper={"username": "tester42"},
            )
        self.assertEqual(len(APIChangeLog.objects.all()), 1)

    def test_invalid_duplication_with_nonexisting_fields(self):
        with self.assertRaises(DuplicationError):
            duplicate_object(
                self.test_item,
                include_children=False,
                included_fields=["data", "username", "foo", "timestamp"],
            )

            self.assertEqual(len(APIChangeLog.objects.all()), 1)

        with self.assertRaises(DuplicationError):
            duplicate_object(
                self.test_item,
                include_children=False,
                included_fields=["data", "username", "timestamp"],
                field_mapper={"foo": 0},
            )

            self.assertEqual(len(APIChangeLog.objects.all()), 1)

    def test_invalid_duplication_with_incorrect_field_types(self):
        with self.assertRaises(DuplicationError):
            duplicate_object(
                self.test_item,
                include_children=False,
                included_fields=["username", "data"],
                field_mapper={"timestamp": "1000"},
            )

        self.assertEqual(len(APIChangeLog.objects.all()), 1)


class TestDuplicationUtils(TestCase):
    def setUp(self):
        self.test_item = PlatformUser.objects.create(email="foo@test.com")

    def test_get_parent_field_name(self):

        # valid relationship
        parent_field_name = get_parent_field_name(self.test_item, AppErrorLog)
        self.assertEqual(parent_field_name, "user")

        # invalid relationship
        parent_field_name = get_parent_field_name(self.test_item, APIChangeLog)
        self.assertIsNone(parent_field_name)

    def test_setup_fields_to_duplicate_no_children(self):

        # no excluded fields
        included_fields, field_mapper = setup_fields_to_duplicate(
            self.test_item,
            False,
            included_fields=["data", "user_image"],
            excluded_fields=None,
            field_mapper={"email": "bar@test.com"},
        )

        # we expect no change
        self.assertEqual(included_fields, ["data", "user_image"])
        self.assertEqual(field_mapper, {"email": "bar@test.com"})

        # only excluded fields
        included_fields, field_mapper = setup_fields_to_duplicate(
            self.test_item,
            False,
            included_fields=None,
            excluded_fields=["data", "user_image"],
            field_mapper=None,
        )

        # we don't have the excluded fields but we do have others
        self.assertGreater(len(included_fields), 1)
        self.assertNotIn("data", included_fields)
        self.assertNotIn("user_image", included_fields)
        self.assertIn("email", included_fields)

        # no children added
        self.assertNotIn("platformorganization", included_fields)

        # field_mapper is made a dict still
        self.assertEqual(field_mapper, {})

    def test_setup_fields_to_duplicate_with_children(self):

        # no excluded fields
        included_fields, field_mapper = setup_fields_to_duplicate(
            self.test_item,
            True,
            included_fields=["data", "user_image"],
            excluded_fields=None,
            field_mapper={"email": "bar@test.com"},
        )

        # we expect no change
        self.assertEqual(included_fields, ["data", "user_image"])
        self.assertEqual(field_mapper, {"email": "bar@test.com"})

        # only excluded fields
        included_fields, field_mapper = setup_fields_to_duplicate(
            self.test_item,
            True,
            included_fields=None,
            excluded_fields=["data", "user_image"],
            field_mapper=None,
        )

        # we don't have the excluded fields but we do have others, including children
        self.assertGreater(len(included_fields), 1)
        self.assertNotIn("data", included_fields)
        self.assertNotIn("user_image", included_fields)
        self.assertIn("email", included_fields)
        self.assertIn("platformorganization", included_fields)

        # field_mapper is made a dict still
        self.assertEqual(field_mapper, {})

    def test_setup_fields_to_duplicate_include_or_exclude_children(self):
        included_fields_with_children, _ = setup_fields_to_duplicate(
            self.test_item,
            True,
            included_fields=None,
            excluded_fields=None,
            field_mapper=None,
        )

        included_fields_without_children, _ = setup_fields_to_duplicate(
            self.test_item,
            False,
            included_fields=None,
            excluded_fields=None,
            field_mapper=None,
        )

        self.assertTrue(
            set(included_fields_without_children).issubset(
                included_fields_with_children
            )
        )
