from __future__ import unicode_literals

import time

from django.core.management.base import BaseCommand, CommandError
from django.core.management.sql import emit_pre_migrate_signal, emit_post_migrate_signal
from django.db import DEFAULT_DB_ALIAS, connections
from django.db.migrations.executor import MigrationExecutor
from django.db.migrations.loader import AmbiguityError
from importlib import import_module

APPLY_OFFLINE_FUNCTION = 'apply_offline'


class Command(BaseCommand):  # pragma: no cover
    help = "Runs an expensive migration offline"

    def add_arguments(self, parser):
        parser.add_argument('app_label',
                            help='App label of an application to synchronize the state.')
        parser.add_argument('migration_name',
                            help='Database state will be brought to the state after that migration.')

    def handle(self, app_label, migration_name, *args, **options):
        self.verbosity = options.get('verbosity')
        self.interactive = options.get('interactive')

        connection = connections[DEFAULT_DB_ALIAS]
        connection.prepare_database()
        executor = MigrationExecutor(connection, None)

        # before anything else, see if there's conflicting apps and drop out hard if there are any
        conflicts = executor.loader.detect_conflicts()
        if conflicts:
            name_str = "; ".join("%s in %s" % (", ".join(names), app) for app, names in conflicts.items())
            raise CommandError(
                "Conflicting migrations detected (%s).\nTo fix them run "
                "'python manage.py makemigrations --merge'" % name_str
            )

        if app_label not in executor.loader.migrated_apps:
            raise CommandError(
                "App '%s' does not have migrations (you cannot selectively sync unmigrated apps)" % app_label
            )
        try:
            migration = executor.loader.get_migration_by_prefix(app_label, migration_name)
        except AmbiguityError:
            raise CommandError(
                "More than one migration matches '%s' in app '%s'. Please be more specific." %
                (migration_name, app_label)
            )
        except KeyError:
            raise CommandError("Cannot find a migration matching '%s' from app '%s'." % (migration_name, app_label))

        migration_module = import_module(migration.__module__)

        # check migration can be run offline
        apply_function = getattr(migration_module, APPLY_OFFLINE_FUNCTION, None)
        if not apply_function or not callable(apply_function):
            raise CommandError("Migration %s does not contain function named '%s'" % (migration, APPLY_OFFLINE_FUNCTION))

        plan = executor.migration_plan([(app_label, migration.name)])
        if not plan:
            raise CommandError("Migration %s has already been applied" % migration)

        emit_pre_migrate_signal([], self.verbosity, self.interactive, connection.alias)

        self.stdout.write(self.style.MIGRATE_HEADING("Operation to perform:"))
        self.stdout.write("  Apply migration %s offline and fake" % migration)
        self.stdout.write(self.style.MIGRATE_HEADING("Offline migration:"))

        self.apply_migration(migration, apply_function)

        self.fake_migration(migration, executor)

        # send the post_migrate signal, so individual apps can do whatever they need to do at this point.
        emit_post_migrate_signal([], self.verbosity, self.interactive, connection.alias)

    def apply_migration(self, migration, apply_function):
        compute_time = self.verbosity > 1

        self.stdout.write("  Applying %s... " % migration, ending="")
        self.stdout.flush()

        if compute_time:
            self.start = time.time()

        apply_function()

        elapsed = " (%.3fs)" % (time.time() - self.start) if compute_time else ""

        self.stdout.write(self.style.MIGRATE_SUCCESS("OK" + elapsed))

    def fake_migration(self, migration, executor):
        self.stdout.write("  Faking %s... " % migration, ending="")
        self.stdout.flush()

        executor.recorder.record_applied(migration.app_label, migration.name)

        self.stdout.write(self.style.MIGRATE_SUCCESS("DONE"))

@@ -3079,25 +3079,39 @@
 ut.write(%22  
-A
+Fake and then a
 pply migrati
@@ -3127,17 +3127,8 @@
 line
- and fake
 %22 %25 
@@ -3228,21 +3228,20 @@
    self.
-apply
+fake
 _migrati
@@ -3254,30 +3254,24 @@
 ration, 
-apply_function
+executor
 )%0A%0A     
@@ -3270,36 +3270,37 @@
 )%0A%0A        self.
-fake
+apply
 _migration(migra
@@ -3301,32 +3301,38 @@
 (migration, 
-executor
+apply_function
 )%0A%0A        #
@@ -3514,24 +3514,314 @@
 ion.alias)%0A%0A
+    def fake_migration(self, migration, executor):%0A        self.stdout.write(%22  Faking %25s... %22 %25 migration, ending=%22%22)%0A        self.stdout.flush()%0A%0A        executor.recorder.record_applied(migration.app_label, migration.name)%0A%0A        self.stdout.write(self.style.MIGRATE_SUCCESS(%22DONE%22))%0A%0A
     def appl
@@ -4254,294 +4254,4 @@
 d))%0A
-%0A    def fake_migration(self, migration, executor):%0A        self.stdout.write(%22  Faking %25s... %22 %25 migration, ending=%22%22)%0A        self.stdout.flush()%0A%0A        executor.recorder.record_applied(migration.app_label, migration.name)%0A%0A        self.stdout.write(self.style.MIGRATE_SUCCESS(%22DONE%22))%0A

Switch order of faking and applying