# Unittests for fixtures.
import json
import os
import re
from io import StringIO

from django.core import management, serializers
from django.core.exceptions import ImproperlyConfigured
from django.core.serializers.base import DeserializationError
from django.db import IntegrityError, transaction
from django.db.models import signals
from django.test import (
    TestCase, TransactionTestCase, override_settings, skipIfDBFeature,
    skipUnlessDBFeature,
)

from .models import (
    Absolute, Animal, Article, Book, Child, Circle1, Circle2, Circle3,
    ExternalDependency, M2MCircular1ThroughAB, M2MCircular1ThroughBC,
    M2MCircular1ThroughCA, M2MCircular2ThroughAB, M2MComplexA, M2MComplexB,
    M2MComplexCircular1A, M2MComplexCircular1B, M2MComplexCircular1C,
    M2MComplexCircular2A, M2MComplexCircular2B, M2MSimpleA, M2MSimpleB,
    M2MSimpleCircularA, M2MSimpleCircularB, M2MThroughAB, NKChild, Parent,
    Person, RefToNKChild, Store, Stuff, Thingy, Widget,
)

_cur_dir = os.path.dirname(os.path.abspath(__file__))


class TestFixtures(TestCase):

    def animal_pre_save_check(self, signal, sender, instance, **kwargs):
        self.pre_save_checks.append(
            (
                'Count = %s (%s)' % (instance.count, type(instance.count)),
                'Weight = %s (%s)' % (instance.weight, type(instance.weight)),
            )
        )

    def test_duplicate_pk(self):
        """
        This is a regression test for ticket #3790.
        """
        # Load a fixture that uses PK=1
        management.call_command(
            'loaddata',
            'sequence',
            verbosity=0,
        )

        # Create a new animal. Without a sequence reset, this new object
        # will take a PK of 1 (on Postgres), and the save will fail.

        animal = Animal(
            name='Platypus',
            latin_name='Ornithorhynchus anatinus',
            count=2,
            weight=2.2,
        )
        animal.save()
        self.assertGreater(animal.id, 1)

    def test_loaddata_not_found_fields_not_ignore(self):
        """
        Test for ticket #9279 -- Error is raised for entries in
        the serialized data for fields that have been removed
        from the database when not ignored.
        """
        with self.assertRaises(DeserializationError):
            management.call_command(
                'loaddata',
                'sequence_extra',
                verbosity=0,
            )

    def test_loaddata_not_found_fields_ignore(self):
        """
        Test for ticket #9279 -- Ignores entries in
        the serialized data for fields that have been removed
        from the database.
        """
        management.call_command(
            'loaddata',
            'sequence_extra',
            ignore=True,
            verbosity=0,
        )
        self.assertEqual(Animal.specimens.all()[0].name, 'Lion')

    def test_loaddata_not_found_fields_ignore_xml(self):
        """
        Test for ticket #19998 -- Ignore entries in the XML serialized data
        for fields that have been removed from the model definition.
        """
        management.call_command(
            'loaddata',
            'sequence_extra_xml',
            ignore=True,
            verbosity=0,
        )
        self.assertEqual(Animal.specimens.all()[0].name, 'Wolf')

    @skipIfDBFeature('interprets_empty_strings_as_nulls')
    def test_pretty_print_xml(self):
        """
        Regression test for ticket #4558 -- pretty printing of XML fixtures
        doesn't affect parsing of None values.
        """
        # Load a pretty-printed XML fixture with Nulls.
        management.call_command(
            'loaddata',
            'pretty.xml',
            verbosity=0,
        )
        self.assertIsNone(Stuff.objects.all()[0].name)
        self.assertIsNone(Stuff.objects.all()[0].owner)

    @skipUnlessDBFeature('interprets_empty_strings_as_nulls')
    def test_pretty_print_xml_empty_strings(self):
        """
        Regression test for ticket #4558 -- pretty printing of XML fixtures
        doesn't affect parsing of None values.
        """
        # Load a pretty-printed XML fixture with Nulls.
        management.call_command(
            'loaddata',
            'pretty.xml',
            verbosity=0,
        )
        self.assertEqual(Stuff.objects.all()[0].name, '')
        self.assertIsNone(Stuff.objects.all()[0].owner)

    def test_absolute_path(self):
        """
        Regression test for ticket #6436 --
        os.path.join will throw away the initial parts of a path if it
        encounters an absolute path.
        This means that if a fixture is specified as an absolute path,
        we need to make sure we don't discover the absolute path in every
        fixture directory.
        """
        load_absolute_path = os.path.join(
            os.path.dirname(__file__),
            'fixtures',
            'absolute.json'
        )
        management.call_command(
            'loaddata',
            load_absolute_path,
            verbosity=0,
        )
        self.assertEqual(Absolute.objects.count(), 1)

    def test_relative_path(self, path=['fixtures', 'absolute.json']):
        relative_path = os.path.join(*path)
        cwd = os.getcwd()
        try:
            os.chdir(_cur_dir)
            management.call_command(
                'loaddata',
                relative_path,
                verbosity=0,
            )
        finally:
            os.chdir(cwd)
        self.assertEqual(Absolute.objects.count(), 1)

    @override_settings(FIXTURE_DIRS=[os.path.join(_cur_dir, 'fixtures_1')])
    def test_relative_path_in_fixture_dirs(self):
        self.test_relative_path(path=['inner', 'absolute.json'])

    def test_path_containing_dots(self):
        management.call_command(
            'loaddata',