"""
Check if there are apps that override something and have missing migrations.
"""

import importlib
import os
import os.path
from copy import deepcopy
from shutil import copy

from django.apps.registry import Apps
from django.conf import settings
from django.core.management.commands.makemigrations import Command as MakeMigrations
from django.db.migrations.exceptions import BadMigrationError
from django.db.migrations.loader import MIGRATIONS_MODULE_NAME

from ocyan.core.utils import SegmentedInstalledApps, unique_everseen


def get_segmented_installed_apps():
    settings_module = importlib.import_module(settings.SETTINGS_MODULE)
    for _, value in vars(settings_module).items():
        if isinstance(value, SegmentedInstalledApps):
            SEGMENTED_INSTALLED_APPS = value
            break
    else:
        from ocyan.main.settings.base import _installed_apps as SEGMENTED_INSTALLED_APPS

    return SEGMENTED_INSTALLED_APPS


def get_migrations(app_module_name, skip_replaced=True):
    module_name = "%s.%s" % (app_module_name, MIGRATIONS_MODULE_NAME)
    try:
        module = importlib.import_module(module_name)
    except ImportError:
        return set()
    else:
        directory = os.path.dirname(module.__file__)
        migration_names = set()
        for name in os.listdir(directory):
            if name.endswith(".py"):
                import_name = name.rsplit(".", 1)[0]
                if import_name[0] not in "_.~":
                    migration_names.add(import_name)

        # remove all replaced migrations
        if skip_replaced:
            for migration_name in deepcopy(migration_names):
                migration_module = importlib.import_module(
                    "%s.%s" % (module_name, migration_name)
                )
                if not hasattr(migration_module, "Migration"):
                    raise BadMigrationError(
                        "Migration %s in app %s has no Migration class"
                        % (migration_name, app_module_name)
                    )
                if hasattr(migration_module.Migration, "replaces"):
                    _migration_names = [
                        _migration_name
                        for app_label, _migration_name in migration_module.Migration.replaces
                        if app_module_name.endswith(app_label)
                    ]
                    migration_names -= set(_migration_names)

        return migration_names


INSTALLED_APPS = get_segmented_installed_apps()


class Command(MakeMigrations):
    help = __doc__

    def add_arguments(self, parser):
        parser.add_argument(
            "--dry-run",
            action="store_true",
            dest="dry_run",
            help="Just show what migrations would be made; don't actually write them.",
        )
        parser.add_argument(
            "--include-replaced",
            action="store_true",
            dest="include_replaced",
            help="Also copy replaced migrations, usually not needed",
        )
        parser.add_argument(
            "segment",
            choices=("front", "django", "ocyan", "apps", "oscar", "wagtail"),
            help="Which segment would you like to check?",
        )
        parser.add_argument(
            "--no-header",
            action="store_false",
            dest="include_header",
            help="Do not add header comments to new migration file(s).",
        )

    def handle(self, *_, **options):
        segment = options.pop("segment")
        self.segment = getattr(  # pylint: disable=attribute-defined-outside-init
            INSTALLED_APPS, segment
        )
        self.skip_replaced = not options.pop(  # pylint: disable=attribute-defined-outside-init
            "include_replaced"
        )
        self.apps = Apps(  # pylint: disable=attribute-defined-outside-init
            unique_everseen(self.segment)
        )
        app_labels = self.apps.app_configs.keys()

        configuration = dict(
            options,
            interactive=True,
            merge=False,
            empty=False,
            name=None,
            check_changes=False,
        )
        super().handle(*app_labels, **configuration)
        self.stdout.write(self.style.HTTP_REDIRECT("KLAAR DUS"))

    # pylint: disable=unused-argument
    def write_migration_files(self, changes, update_previous_migration_paths=None):
        if bool(changes):
            self.stdout.write(
                self.style.ERROR(
                    "Missing migrations detected, checking ancestor apps for missing migrations"
                )
            )

            for app_label, _ in changes.items():
                app_config = self.apps.app_configs[app_label]
                app_migrations = get_migrations(app_config.name)
                for ancestor_app in self.segment.extensions_from_app_root(
                    app_config.name
                ):
                    ancestor_migrations = get_migrations(
                        ancestor_app, skip_replaced=self.skip_replaced
                    )
                    missing_migrations = ancestor_migrations.difference(app_migrations)

                    # find first app that has migrations not present in the current app.
                    if missing_migrations:
                        self.stdout.write(
                            self.style.SQL_TABLE(
                                "Missing migrations for %s found in %s."
                                % (app_config.name, ancestor_app)
                            )
                        )
                        for migration in missing_migrations:
                            # copy the migration to the app that needs it
                            migration_module_name = "%s.%s.%s" % (
                                ancestor_app,
                                MIGRATIONS_MODULE_NAME,
                                migration,
                            )
                            self.stdout.write(
                                self.style.HTTP_SUCCESS(
                                    "Copying %s to migration directory of %s"
                                    % (migration, app_config.name)
                                )
                            )
                            try:
                                migration_module = importlib.import_module(
                                    migration_module_name
                                )
                            except ImportError:
                                continue
                            else:
                                if not self.dry_run:
                                    migration_file = migration_module.__file__
                                    target_path = os.path.join(
                                        app_config.path, MIGRATIONS_MODULE_NAME
                                    )
                                    if not os.path.isdir(target_path):
                                        os.mkdir(target_path)
                                    copy(migration_file, target_path)
                        break  # do maximum of 1 app at a time.
        else:
            self.stdout.write(
                self.style.SUCCESS("No changes found everything is up to date.")
            )
