import datetime
import decimal
import enum
import functools
import math
import os
import pathlib
import re
import sys
import uuid
from unittest import mock

try:
    import zoneinfo
except ImportError:
    from backports import zoneinfo

try:
    import pytz
except ImportError:
    pytz = None

import custom_migration_operations.more_operations
import custom_migration_operations.operations

from django import get_version
from django.conf import SettingsReference, settings
from django.core.validators import EmailValidator, RegexValidator
from django.db import migrations, models
from django.db.migrations.serializer import BaseSerializer
from django.db.migrations.writer import MigrationWriter, OperationWriter
from django.test import SimpleTestCase, ignore_warnings
from django.utils.deconstruct import deconstructible
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.functional import SimpleLazyObject
from django.utils.timezone import get_default_timezone, get_fixed_timezone
from django.utils.translation import gettext_lazy as _

from .models import FoodManager, FoodQuerySet


class DeconstructibleInstances:
    def deconstruct(self):
        return ("DeconstructibleInstances", [], {})


class Money(decimal.Decimal):
    def deconstruct(self):
        return (
            "%s.%s" % (self.__class__.__module__, self.__class__.__name__),
            [str(self)],
            {},
        )


class TestModel1:
    def upload_to(self):
        return "/somewhere/dynamic/"

    thing = models.FileField(upload_to=upload_to)


class TextEnum(enum.Enum):
    A = "a-value"
    B = "value-b"


class TextTranslatedEnum(enum.Enum):
    A = _("a-value")
    B = _("value-b")


class BinaryEnum(enum.Enum):
    A = b"a-value"
    B = b"value-b"


class IntEnum(enum.IntEnum):
    A = 1
    B = 2


class IntFlagEnum(enum.IntFlag):
    A = 1
    B = 2


class OperationWriterTests(SimpleTestCase):
    def test_empty_signature(self):
        operation = custom_migration_operations.operations.TestOperation()
        buff, imports = OperationWriter(operation, indentation=0).serialize()
        self.assertEqual(imports, {"import custom_migration_operations.operations"})
        self.assertEqual(
            buff,
            "custom_migration_operations.operations.TestOperation(\n),",
        )

    def test_args_signature(self):
        operation = custom_migration_operations.operations.ArgsOperation(1, 2)
        buff, imports = OperationWriter(operation, indentation=0).serialize()
        self.assertEqual(imports, {"import custom_migration_operations.operations"})
        self.assertEqual(
            buff,
            "custom_migration_operations.operations.ArgsOperation(\n"
            "    arg1=1,\n"
            "    arg2=2,\n"
            "),",
        )

    def test_kwargs_signature(self):
        operation = custom_migration_operations.operations.KwargsOperation(kwarg1=1)
        buff, imports = OperationWriter(operation, indentation=0).serialize()
        self.assertEqual(imports, {"import custom_migration_operations.operations"})
        self.assertEqual(
            buff,
            "custom_migration_operations.operations.KwargsOperation(\n"
            "    kwarg1=1,\n"
            "),",
        )

    def test_args_kwargs_signature(self):
        operation = custom_migration_operations.operations.ArgsKwargsOperation(
            1, 2, kwarg2=4
        )
        buff, imports = OperationWriter(operation, indentation=0).serialize()
        self.assertEqual(imports, {"import custom_migration_operations.operations"})
        self.assertEqual(
            buff,
            "custom_migration_operations.operations.ArgsKwargsOperation(\n"
            "    arg1=1,\n"
            "    arg2=2,\n"
            "    kwarg2=4,\n"
            "),",
        )

    def test_nested_args_signature(self):
        operation = custom_migration_operations.operations.ArgsOperation(
            custom_migration_operations.operations.ArgsOperation(1, 2),
            custom_migration_operations.operations.KwargsOperation(kwarg1=3, kwarg2=4),
        )
        buff, imports = OperationWriter(operation, indentation=0).serialize()
        self.assertEqual(imports, {"import custom_migration_operations.operations"})
        self.assertEqual(
            buff,
            "custom_migration_operations.operations.ArgsOperation(\n"
            "    arg1=custom_migration_operations.operations.ArgsOperation(\n"
            "        arg1=1,\n"
            "        arg2=2,\n"
            "    ),\n"
            "    arg2=custom_migration_operations.operations.KwargsOperation(\n"
            "        kwarg1=3,\n"
            "        kwarg2=4,\n"
            "    ),\n"
            "),",
        )

    def test_multiline_args_signature(self):
        operation = custom_migration_operations.operations.ArgsOperation(
            "test\n    arg1", "test\narg2"
        )
        buff, imports = OperationWriter(operation, indentation=0).serialize()
        self.assertEqual(imports, {"import custom_migration_operations.operations"})
        self.assertEqual(
            buff,
            "custom_migration_operations.operations.ArgsOperation(\n"
            "    arg1='test\\n    arg1',\n"
            "    arg2='test\\narg2',\n"
            "),",
        )

    def test_expand_args_signature(self):
        operation = custom_migration_operations.operations.ExpandArgsOperation([1, 2])
        buff, imports = OperationWriter(operation, indentation=0).serialize()
        self.assertEqual(imports, {"import custom_migration_operations.operations"})
        self.assertEqual(
            buff,
            "custom_migration_operations.operations.ExpandArgsOperation(\n"
            "    arg=[\n"
            "        1,\n"
            "        2,\n"
            "    ],\n"
            "),",
        )

    def test_nested_operation_expand_args_signature(self):
        operation = custom_migration_operations.operations.ExpandArgsOperation(
            arg=[
                custom_migration_operations.operations.KwargsOperation(
                    kwarg1=1,
                    kwarg2=2,
                ),
            ]
        )
        buff, imports = OperationWriter(operation, indentation=0).serialize()
        self.assertEqual(imports, {"import custom_migration_operations.operations"})
        self.assertEqual(
            buff,
            "custom_migration_operations.operations.ExpandArgsOperation(\n"
            "    arg=[\n"
            "        custom_migration_operations.operations.KwargsOperation(\n"
            "            kwarg1=1,\n"
            "            kwarg2=2,\n"
            "        ),\n"
            "    ],\n"
            "),",
        )


class WriterTests(SimpleTestCase):
    """
    Tests the migration writer (makes migration files from Migration instances)
    """

    class NestedEnum(enum.IntEnum):
        A = 1
        B = 2

    class NestedChoices(models.TextChoices):
        X = "X", "X value"
        Y = "Y", "Y value"

    def safe_exec(self, string, value=None):
        d = {}
        try:
            exec(string, globals(), d)
        except Exception as e:
            if value:
                self.fail(
                    "Could not exec %r (from value %r): %s" % (string.strip(), value, e)
                )
            else:
                self.fail("Could not exec %r: %s" % (string.strip(), e))
        return d

    def serialize_round_trip(self, value):
        string, imports = MigrationWriter.serialize(value)
        return self.safe_exec(
            "%s\ntest_value_result = %s" % ("\n".join(imports), string), value
        )["test_value_result"]

    def assertSerializedEqual(self, value):
        self.assertEqual(self.serialize_round_trip(value), value)

    def assertSerializedResultEqual(self, value, target):
        self.assertEqual(MigrationWriter.serialize(value), target)

    def assertSerializedFieldEqual(self, value):
        new_value = self.serialize_round_trip(value)
        self.assertEqual(value.__class__, new_value.__class__)
        self.assertEqual(value.max_length, new_value.max_length)
        self.assertEqual(value.null, new_value.null)
        self.assertEqual(value.unique, new_value.unique)

    def test_serialize_numbers(self):
        self.assertSerializedEqual(1)
        self.assertSerializedEqual(1.2)
        self.assertTrue(math.isinf(self.serialize_round_trip(float("inf"))))
        self.assertTrue(math.isinf(self.serialize_round_trip(float("-inf"))))
        self.assertTrue(math.isnan(self.serialize_round_trip(float("nan"))))

        self.assertSerializedEqual(decimal.Decimal("1.3"))
        self.assertSerializedResultEqual(
            decimal.Decimal("1.3"), ("Decimal('1.3')", {"from decimal import Decimal"})
        )

        self.assertSerializedEqual(Money("1.3"))
        self.assertSerializedResultEqual(
            Money("1.3"),
            ("migrations.test_writer.Money('1.3')", {"import migrations.test_writer"}),
        )

    def test_serialize_constants(self):
        self.assertSerializedEqual(None)
        self.assertSerializedEqual(True)
        self.assertSerializedEqual(False)

    def test_serialize_strings(self):
        self.assertSerializedEqual(b"foobar")
        string, imports = MigrationWriter.serialize(b"foobar")
        self.assertEqual(string, "b'foobar'")
        self.assertSerializedEqual("föobár")
        string, imports = MigrationWriter.serialize("foobar")
        self.assertEqual(string, "'foobar'")

    def test_serialize_multiline_strings(self):
        self.assertSerializedEqual(b"foo\nbar")
        string, imports = MigrationWriter.serialize(b"foo\nbar")
        self.assertEqual(string, "b'foo\\nbar'")
        self.assertSerializedEqual("föo\nbár")
        string, imports = MigrationWriter.serialize("foo\nbar")
        self.assertEqual(string, "'foo\\nbar'")

    def test_serialize_collections(self):
        self.assertSerializedEqual({1: 2})
        self.assertSerializedEqual(["a", 2, True, None])
        self.assertSerializedEqual({2, 3, "eighty"})
        self.assertSerializedEqual({"lalalala": ["yeah", "no", "maybe"]})
        self.assertSerializedEqual(_("Hello"))

    def test_serialize_builtin_types(self):
        self.assertSerializedEqual([list, tuple, dict, set, frozenset])
        self.assertSerializedResultEqual(
            [list, tuple, dict, set, frozenset],
            ("[list, tuple, dict, set, frozenset]", set()),
        )

    def test_serialize_lazy_objects(self):
        pattern = re.compile(r"^foo$")
        lazy_pattern = SimpleLazyObject(lambda: pattern)
        self.assertEqual(self.serialize_round_trip(lazy_pattern), pattern)

    def test_serialize_enums(self):
        self.assertSerializedResultEqual(
            TextEnum.A,
            ("migrations.test_writer.TextEnum['A']", {"import migrations.test_writer"}),
        )
        self.assertSerializedResultEqual(
            TextTranslatedEnum.A,
            (
                "migrations.test_writer.TextTranslatedEnum['A']",
                {"import migrations.test_writer"},
            ),
        )
        self.assertSerializedResultEqual(
            BinaryEnum.A,
            (
                "migrations.test_writer.BinaryEnum['A']",
                {"import migrations.test_writer"},
            ),
        )
        self.assertSerializedResultEqual(
            IntEnum.B,
            ("migrations.test_writer.IntEnum['B']", {"import migrations.test_writer"}),
        )
        self.assertSerializedResultEqual(
            self.NestedEnum.A,
            (
                "migrations.test_writer.WriterTests.NestedEnum['A']",
                {"import migrations.test_writer"},
            ),
        )
        self.assertSerializedEqual(self.NestedEnum.A)

        field = models.CharField(
            default=TextEnum.B, choices=[(m.value, m) for m in TextEnum]
        )
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(
            string,
            "models.CharField(choices=["
            "('a-value', migrations.test_writer.TextEnum['A']), "
            "('value-b', migrations.test_writer.TextEnum['B'])], "
            "default=migrations.test_writer.TextEnum['B'])",
        )
        field = models.CharField(
            default=TextTranslatedEnum.A,
            choices=[(m.value, m) for m in TextTranslatedEnum],
        )
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(
            string,
            "models.CharField(choices=["
            "('a-value', migrations.test_writer.TextTranslatedEnum['A']), "
            "('value-b', migrations.test_writer.TextTranslatedEnum['B'])], "
            "default=migrations.test_writer.TextTranslatedEnum['A'])",
        )
        field = models.CharField(
            default=BinaryEnum.B, choices=[(m.value, m) for m in BinaryEnum]
        )
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(
            string,
            "models.CharField(choices=["
            "(b'a-value', migrations.test_writer.BinaryEnum['A']), "
            "(b'value-b', migrations.test_writer.BinaryEnum['B'])], "
            "default=migrations.test_writer.BinaryEnum['B'])",
        )
        field = models.IntegerField(
            default=IntEnum.A, choices=[(m.value, m) for m in IntEnum]
        )
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(
            string,
            "models.IntegerField(choices=["
            "(1, migrations.test_writer.IntEnum['A']), "
            "(2, migrations.test_writer.IntEnum['B'])], "
            "default=migrations.test_writer.IntEnum['A'])",
        )

    def test_serialize_enum_flags(self):
        self.assertSerializedResultEqual(
            IntFlagEnum.A,
            (
                "migrations.test_writer.IntFlagEnum['A']",
                {"import migrations.test_writer"},
            ),
        )
        self.assertSerializedResultEqual(
            IntFlagEnum.B,
            (
                "migrations.test_writer.IntFlagEnum['B']",
                {"import migrations.test_writer"},
            ),
        )
        field = models.IntegerField(
            default=IntFlagEnum.A, choices=[(m.value, m) for m in IntFlagEnum]
        )
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(
            string,
            "models.IntegerField(choices=["
            "(1, migrations.test_writer.IntFlagEnum['A']), "
            "(2, migrations.test_writer.IntFlagEnum['B'])], "
            "default=migrations.test_writer.IntFlagEnum['A'])",
        )
        self.assertSerializedResultEqual(
            IntFlagEnum.A | IntFlagEnum.B,
            (
                "migrations.test_writer.IntFlagEnum['A'] | "
                "migrations.test_writer.IntFlagEnum['B']",
                {"import migrations.test_writer"},
            ),
        )

    def test_serialize_choices(self):
        class TextChoices(models.TextChoices):
            A = "A", "A value"
            B = "B", "B value"

        class IntegerChoices(models.IntegerChoices):
            A = 1, "One"
            B = 2, "Two"

        class DateChoices(datetime.date, models.Choices):
            DATE_1 = 1969, 7, 20, "First date"
            DATE_2 = 1969, 11, 19, "Second date"

        self.assertSerializedResultEqual(TextChoices.A, ("'A'", set()))
        self.assertSerializedResultEqual(IntegerChoices.A, ("1", set()))
        self.assertSerializedResultEqual(
            DateChoices.DATE_1,
            ("datetime.date(1969, 7, 20)", {"import datetime"}),
        )
        field = models.CharField(default=TextChoices.B, choices=TextChoices.choices)
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(
            string,
            "models.CharField(choices=[('A', 'A value'), ('B', 'B value')], "
            "default='B')",
        )
        field = models.IntegerField(
            default=IntegerChoices.B, choices=IntegerChoices.choices
        )
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(
            string,
            "models.IntegerField(choices=[(1, 'One'), (2, 'Two')], default=2)",
        )
        field = models.DateField(
            default=DateChoices.DATE_2, choices=DateChoices.choices
        )
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(
            string,
            "models.DateField(choices=["
            "(datetime.date(1969, 7, 20), 'First date'), "
            "(datetime.date(1969, 11, 19), 'Second date')], "
            "default=datetime.date(1969, 11, 19))",
        )

    def test_serialize_nested_class(self):
        for nested_cls in [self.NestedEnum, self.NestedChoices]:
            cls_name = nested_cls.__name__
            with self.subTest(cls_name):
                self.assertSerializedResultEqual(
                    nested_cls,
                    (
                        "migrations.test_writer.WriterTests.%s" % cls_name,
                        {"import migrations.test_writer"},
                    ),
                )

    def test_serialize_uuid(self):
        self.assertSerializedEqual(uuid.uuid1())
        self.assertSerializedEqual(uuid.uuid4())

        uuid_a = uuid.UUID("5c859437-d061-4847-b3f7-e6b78852f8c8")
        uuid_b = uuid.UUID("c7853ec1-2ea3-4359-b02d-b54e8f1bcee2")
        self.assertSerializedResultEqual(
            uuid_a,
            ("uuid.UUID('5c859437-d061-4847-b3f7-e6b78852f8c8')", {"import uuid"}),
        )
        self.assertSerializedResultEqual(
            uuid_b,
            ("uuid.UUID('c7853ec1-2ea3-4359-b02d-b54e8f1bcee2')", {"import uuid"}),
        )

        field = models.UUIDField(
            choices=((uuid_a, "UUID A"), (uuid_b, "UUID B")), default=uuid_a
        )
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(
            string,
            "models.UUIDField(choices=["
            "(uuid.UUID('5c859437-d061-4847-b3f7-e6b78852f8c8'), 'UUID A'), "
            "(uuid.UUID('c7853ec1-2ea3-4359-b02d-b54e8f1bcee2'), 'UUID B')], "
            "default=uuid.UUID('5c859437-d061-4847-b3f7-e6b78852f8c8'))",
        )

    def test_serialize_pathlib(self):
        # Pure path objects work in all platforms.
        self.assertSerializedEqual(pathlib.PurePosixPath())
        self.assertSerializedEqual(pathlib.PureWindowsPath())
        path = pathlib.PurePosixPath("/path/file.txt")
        expected = ("pathlib.PurePosixPath('/path/file.txt')", {"import pathlib"})
        self.assertSerializedResultEqual(path, expected)
        path = pathlib.PureWindowsPath("A:\\File.txt")
        expected = ("pathlib.PureWindowsPath('A:/File.txt')", {"import pathlib"})
        self.assertSerializedResultEqual(path, expected)
        # Concrete path objects work on supported platforms.
        if sys.platform == "win32":
            self.assertSerializedEqual(pathlib.WindowsPath.cwd())
            path = pathlib.WindowsPath("A:\\File.txt")
            expected = ("pathlib.PureWindowsPath('A:/File.txt')", {"import pathlib"})
            self.assertSerializedResultEqual(path, expected)
        else:
            self.assertSerializedEqual(pathlib.PosixPath.cwd())
            path = pathlib.PosixPath("/path/file.txt")
            expected = ("pathlib.PurePosixPath('/path/file.txt')", {"import pathlib"})
            self.assertSerializedResultEqual(path, expected)

        field = models.FilePathField(path=pathlib.PurePosixPath("/home/user"))
        string, imports = MigrationWriter.serialize(field)
        self.assertEqual(
            string,
            "models.FilePathField(path=pathlib.PurePosixPath('/home/user'))",
        )
        self.assertIn("import pathlib", imports)

    def test_serialize_path_like(self):
        with os.scandir(os.path.dirname(__file__)) as entries:
            path_like = list(entries)[0]
        expected = (repr(path_like.path), {})
        self.assertSerializedResultEqual(path_like, expected)

        field = models.FilePathField(path=path_like)
        string = MigrationWriter.serialize(field)[0]
        self.assertEqual(string, "models.FilePathField(path=%r)" % path_like.path)

    def test_serialize_functions(self):
        with self.assertRaisesMessage(ValueError, "Cannot serialize function: lambda"):
            self.assertSerializedEqual(lambda x: 42)
        self.assertSerializedEqual(models.SET_NULL)
        string, imports = MigrationWriter.serialize(models.SET(42))
        self.assertEqual(string, "models.SET(42)")
        self.serialize_round_trip(models.SET(42))

    def test_serialize_datetime(self):
        self.assertSerializedEqual(datetime.datetime.now())
        self.assertSerializedEqual(datetime.datetime.now)
        self.assertSerializedEqual(datetime.datetime.today())
        self.assertSerializedEqual(datetime.datetime.today)
        self.assertSerializedEqual(datetime.date.today())
        self.assertSerializedEqual(datetime.date.today)
        self.assertSerializedEqual(datetime.datetime.now().time())
        self.assertSerializedEqual(
            datetime.datetime(2014, 1, 1, 1, 1, tzinfo=get_default_timezone())
        )
        self.assertSerializedEqual(
            datetime.datetime(2013, 12, 31, 22, 1, tzinfo=get_fixed_timezone(180))
        )
        self.assertSerializedResultEqual(
            datetime.datetime(2014, 1, 1, 1, 1),
            ("datetime.datetime(2014, 1, 1, 1, 1)", {"import datetime"}),
        )
        with ignore_warnings(category=RemovedInDjango50Warning):
            from django.utils.timezone import utc
        for tzinfo in (utc, datetime.timezone.utc):
            with self.subTest(tzinfo=tzinfo):
                self.assertSerializedResultEqual(
                    datetime.datetime(2012, 1, 1, 1, 1, tzinfo=tzinfo),
                    (
                        "datetime.datetime"
                        "(2012, 1, 1, 1, 1, tzinfo=datetime.timezone.utc)",
                        {"import datetime"},
                    ),
                )

        self.assertSerializedResultEqual(
            datetime.datetime(
                2012, 1, 1, 2, 1, tzinfo=zoneinfo.ZoneInfo("Europe/Paris")
            ),
            (
                "datetime.datetime(2012, 1, 1, 1, 1, tzinfo=datetime.timezone.utc)",
                {"import datetime"},
            ),
        )
        if pytz:
            self.assertSerializedResultEqual(
                pytz.timezone("Europe/Paris").localize(
                    datetime.datetime(2012, 1, 1, 2, 1)
                ),
                (
                    "datetime.datetime(2012, 1, 1, 1, 1, tzinfo=datetime.timezone.utc)",
                    {"import datetime"},
                ),
            )

    def test_serialize_fields(self):
        self.assertSerializedFieldEqual(models.CharField(max_length=255))
        self.assertSerializedResultEqual(
            models.CharField(max_length=255),
            ("models.CharField(max_length=255)", {"from django.db import models"}),
        )
        self.assertSerializedFieldEqual(models.TextField(null=True, blank=True))
        self.assertSerializedResultEqual(
            models.TextField(null=True, blank=True),
            (
                "models.TextField(blank=True, null=True)",
                {"from django.db import models"},
            ),
        )

    def test_serialize_settings(self):
        self.assertSerializedEqual(
            SettingsReference(settings.AUTH_USER_MODEL, "AUTH_USER_MODEL")
        )
        self.assertSerializedResultEqual(
            SettingsReference("someapp.model", "AUTH_USER_MODEL"),
            ("settings.AUTH_USER_MODEL", {"from django.conf import settings"}),
        )

    def test_serialize_iterators(self):
        self.assertSerializedResultEqual(
            ((x, x * x) for x in range(3)), ("((0, 0), (1, 1), (2, 4))", set())
        )

    def test_serialize_compiled_regex(self):
        """
        Make sure compiled regex can be serialized.
        """
        regex = re.compile(r"^\w+$")
        self.assertSerializedEqual(regex)

    def test_serialize_class_based_validators(self):
        """
        Ticket #22943: Test serialization of class-based validators, including
        compiled regexes.
        """
        validator = RegexValidator(message="hello")
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(
            string, "django.core.validators.RegexValidator(message='hello')"
        )
        self.serialize_round_trip(validator)

        # Test with a compiled regex.
        validator = RegexValidator(regex=re.compile(r"^\w+$"))
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(
            string,
            "django.core.validators.RegexValidator(regex=re.compile('^\\\\w+$'))",
        )
        self.serialize_round_trip(validator)

        # Test a string regex with flag
        validator = RegexValidator(r"^[0-9]+$", flags=re.S)
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(
            string,
            "django.core.validators.RegexValidator('^[0-9]+$', "
            "flags=re.RegexFlag['DOTALL'])",
        )
        self.serialize_round_trip(validator)

        # Test message and code
        validator = RegexValidator("^[-a-zA-Z0-9_]+$", "Invalid", "invalid")
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(
            string,
            "django.core.validators.RegexValidator('^[-a-zA-Z0-9_]+$', 'Invalid', "
            "'invalid')",
        )
        self.serialize_round_trip(validator)

        # Test with a subclass.
        validator = EmailValidator(message="hello")
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(
            string, "django.core.validators.EmailValidator(message='hello')"
        )
        self.serialize_round_trip(validator)

        validator = deconstructible(path="migrations.test_writer.EmailValidator")(
            EmailValidator
        )(message="hello")
        string = MigrationWriter.serialize(validator)[0]
        self.assertEqual(
            string, "migrations.test_writer.EmailValidator(message='hello')"
        )

        validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(
            message="hello"
        )
        with self.assertRaisesMessage(ImportError, "No module named 'custom'"):
            MigrationWriter.serialize(validator)

        validator = deconstructible(path="django.core.validators.EmailValidator2")(
            EmailValidator
        )(message="hello")
        with self.assertRaisesMessage(
            ValueError,
            "Could not find object EmailValidator2 in django.core.validators.",
        ):
            MigrationWriter.serialize(validator)

    def test_serialize_complex_func_index(self):
        index = models.Index(
            models.Func("rating", function="ABS"),
            models.Case(
                models.When(name="special", then=models.Value("X")),
                default=models.Value("other"),
            ),
            models.ExpressionWrapper(
                models.F("pages"),
                output_field=models.IntegerField(),
            ),
            models.OrderBy(models.F("name").desc()),
            name="complex_func_index",
        )
        string, imports = MigrationWriter.serialize(index)
        self.assertEqual(
            string,
            "models.Index(models.Func('rating', function='ABS'), "
            "models.Case(models.When(name='special', then=models.Value('X')), "
            "default=models.Value('other')), "
            "models.ExpressionWrapper("
            "models.F('pages'), output_field=models.IntegerField()), "
            "models.OrderBy(models.OrderBy(models.F('name'), descending=True)), "
            "name='complex_func_index')",
        )
        self.assertEqual(imports, {"from django.db import models"})

    def test_serialize_empty_nonempty_tuple(self):
        """
        Ticket #22679: makemigrations generates invalid code for (an empty
        tuple) default_permissions = ()
        """
        empty_tuple = ()
        one_item_tuple = ("a",)
        many_items_tuple = ("a", "b", "c")
        self.assertSerializedEqual(empty_tuple)
        self.assertSerializedEqual(one_item_tuple)
        self.assertSerializedEqual(many_items_tuple)

    def test_serialize_range(self):
        string, imports = MigrationWriter.serialize(range(1, 5))
        self.assertEqual(string, "range(1, 5)")
        self.assertEqual(imports, set())

    def test_serialize_builtins(self):
        string, imports = MigrationWriter.serialize(range)
        self.assertEqual(string, "range")
        self.assertEqual(imports, set())

    def test_serialize_unbound_method_reference(self):
        """An unbound method used within a class body can be serialized."""
        self.serialize_round_trip(TestModel1.thing)

    def test_serialize_local_function_reference(self):
        """A reference in a local scope can't be serialized."""

        class TestModel2:
            def upload_to(self):
                return "somewhere dynamic"

            thing = models.FileField(upload_to=upload_to)

        with self.assertRaisesMessage(
            ValueError, "Could not find function upload_to in migrations.test_writer"
        ):
            self.serialize_round_trip(TestModel2.thing)

    def test_serialize_managers(self):
        self.assertSerializedEqual(models.Manager())
        self.assertSerializedResultEqual(
            FoodQuerySet.as_manager(),
            (
                "migrations.models.FoodQuerySet.as_manager()",
                {"import migrations.models"},
            ),
        )
        self.assertSerializedEqual(FoodManager("a", "b"))
        self.assertSerializedEqual(FoodManager("x", "y", c=3, d=4))

    def test_serialize_frozensets(self):
        self.assertSerializedEqual(frozenset())
        self.assertSerializedEqual(frozenset("let it go"))

    def test_serialize_set(self):
        self.assertSerializedEqual(set())
        self.assertSerializedResultEqual(set(), ("set()", set()))
        self.assertSerializedEqual({"a"})
        self.assertSerializedResultEqual({"a"}, ("{'a'}", set()))

    def test_serialize_timedelta(self):
        self.assertSerializedEqual(datetime.timedelta())
        self.assertSerializedEqual(datetime.timedelta(minutes=42))

    def test_serialize_functools_partial(self):
        value = functools.partial(datetime.timedelta, 1, seconds=2)
        result = self.serialize_round_trip(value)
        self.assertEqual(result.func, value.func)
        self.assertEqual(result.args, value.args)
        self.assertEqual(result.keywords, value.keywords)

    def test_serialize_functools_partialmethod(self):
        value = functools.partialmethod(datetime.timedelta, 1, seconds=2)
        result = self.serialize_round_trip(value)
        self.assertIsInstance(result, functools.partialmethod)
        self.assertEqual(result.func, value.func)
        self.assertEqual(result.args, value.args)
        self.assertEqual(result.keywords, value.keywords)

    def test_serialize_type_none(self):
        self.assertSerializedEqual(type(None))

    def test_serialize_type_model(self):
        self.assertSerializedEqual(models.Model)
        self.assertSerializedResultEqual(
            MigrationWriter.serialize(models.Model),
            ("('models.Model', {'from django.db import models'})", set()),
        )

    def test_simple_migration(self):
        """
        Tests serializing a simple migration.
        """
        fields = {
            "charfield": models.DateTimeField(default=datetime.datetime.now),
            "datetimefield": models.DateTimeField(default=datetime.datetime.now),
        }

        options = {
            "verbose_name": "My model",
            "verbose_name_plural": "My models",
        }

        migration = type(
            "Migration",
            (migrations.Migration,),
            {
                "operations": [
                    migrations.CreateModel(
                        "MyModel", tuple(fields.items()), options, (models.Model,)
                    ),
                    migrations.CreateModel(
                        "MyModel2", tuple(fields.items()), bases=(models.Model,)
                    ),
                    migrations.CreateModel(
                        name="MyModel3",
                        fields=tuple(fields.items()),
                        options=options,
                        bases=(models.Model,),
                    ),
                    migrations.DeleteModel("MyModel"),
                    migrations.AddField(
                        "OtherModel", "datetimefield", fields["datetimefield"]
                    ),
                ],
                "dependencies": [("testapp", "some_other_one")],
            },
        )
        writer = MigrationWriter(migration)
        output = writer.as_string()
        # We don't test the output formatting - that's too fragile.
        # Just make sure it runs for now, and that things look alright.
        result = self.safe_exec(output)
        self.assertIn("Migration", result)

    def test_migration_path(self):
        test_apps = [
            "migrations.migrations_test_apps.normal",
            "migrations.migrations_test_apps.with_package_model",
            "migrations.migrations_test_apps.without_init_file",
        ]

        base_dir = os.path.dirname(os.path.dirname(__file__))

        for app in test_apps:
            with self.modify_settings(INSTALLED_APPS={"append": app}):
                migration = migrations.Migration("0001_initial", app.split(".")[-1])
                expected_path = os.path.join(
                    base_dir, *(app.split(".") + ["migrations", "0001_initial.py"])
                )
                writer = MigrationWriter(migration)
                self.assertEqual(writer.path, expected_path)

    def test_custom_operation(self):
        migration = type(
            "Migration",
            (migrations.Migration,),
            {
                "operations": [
                    custom_migration_operations.operations.TestOperation(),
                    custom_migration_operations.operations.CreateModel(),
                    migrations.CreateModel("MyModel", (), {}, (models.Model,)),
                    custom_migration_operations.more_operations.TestOperation(),
                ],
                "dependencies": [],
            },
        )
        writer = MigrationWriter(migration)
        output = writer.as_string()
        result = self.safe_exec(output)
        self.assertIn("custom_migration_operations", result)
        self.assertNotEqual(
            result["custom_migration_operations"].operations.TestOperation,
            result["custom_migration_operations"].more_operations.TestOperation,
        )

    def test_sorted_imports(self):
        """
        #24155 - Tests ordering of imports.
        """
        migration = type(
            "Migration",
            (migrations.Migration,),
            {
                "operations": [
                    migrations.AddField(
                        "mymodel",
                        "myfield",
                        models.DateTimeField(
                            default=datetime.datetime(
                                2012, 1, 1, 1, 1, tzinfo=datetime.timezone.utc
                            ),
                        ),
                    ),
                ]
            },
        )
        writer = MigrationWriter(migration)
        output = writer.as_string()
        self.assertIn(
            "import datetime\nfrom django.db import migrations, models\n",
            output,
        )

    def test_migration_file_header_comments(self):
        """
        Test comments at top of file.
        """
        migration = type("Migration", (migrations.Migration,), {"operations": []})
        dt = datetime.datetime(2015, 7, 31, 4, 40, 0, 0, tzinfo=datetime.timezone.utc)
        with mock.patch("django.db.migrations.writer.now", lambda: dt):
            for include_header in (True, False):
                with self.subTest(include_header=include_header):
                    writer = MigrationWriter(migration, include_header)
                    output = writer.as_string()

                    self.assertEqual(
                        include_header,
                        output.startswith(
                            "# Generated by Django %s on 2015-07-31 04:40\n\n"
                            % get_version()
                        ),
                    )
                    if not include_header:
                        # Make sure the output starts with something that's not
                        # a comment or indentation or blank line
                        self.assertRegex(
                            output.splitlines(keepends=True)[0], r"^[^#\s]+"
                        )

    def test_models_import_omitted(self):
        """
        django.db.models shouldn't be imported if unused.
        """
        migration = type(
            "Migration",
            (migrations.Migration,),
            {
                "operations": [
                    migrations.AlterModelOptions(
                        name="model",
                        options={
                            "verbose_name": "model",
                            "verbose_name_plural": "models",
                        },
                    ),
                ]
            },
        )
        writer = MigrationWriter(migration)
        output = writer.as_string()
        self.assertIn("from django.db import migrations\n", output)

    def test_deconstruct_class_arguments(self):
        # Yes, it doesn't make sense to use a class as a default for a
        # CharField. It does make sense for custom fields though, for example
        # an enumfield that takes the enum class as an argument.
        string = MigrationWriter.serialize(
            models.CharField(default=DeconstructibleInstances)
        )[0]
        self.assertEqual(
            string,
            "models.CharField(default=migrations.test_writer.DeconstructibleInstances)",
        )

    def test_register_serializer(self):
        class ComplexSerializer(BaseSerializer):
            def serialize(self):
                return "complex(%r)" % self.value, {}

        MigrationWriter.register_serializer(complex, ComplexSerializer)
        self.assertSerializedEqual(complex(1, 2))
        MigrationWriter.unregister_serializer(complex)
        with self.assertRaisesMessage(ValueError, "Cannot serialize: (1+2j)"):
            self.assertSerializedEqual(complex(1, 2))

    def test_register_non_serializer(self):
        with self.assertRaisesMessage(
            ValueError, "'TestModel1' must inherit from 'BaseSerializer'."
        ):
            MigrationWriter.register_serializer(complex, TestModel1)
