import logging

from django.apps import apps

from oscar.core.exceptions import (
    AppNotFoundError,
)

# loading settings via django.conf triggers registry exceptions but this
# is safe because these settings are just defaults for all ocyan projects.
# the actual settings are still constructed in the project itself.
from oscar.core.exceptions import (
    ModuleNotFoundError as OscarModuleNotFoundError,
)
from oscar.core.loading import (  # pylint: disable=unused-import
    _import_module,
    _pluck_classes,
    get_model,  # noqa: F401 - public compatibility re-export
    is_model_registered,  # noqa: F401 - public compatibility re-export
)

from ocyan.main.settings.base import _installed_apps as INSTALLED_APPS

logger = logging.getLogger(__name__)


try:
    from oscar.core.loading import _find_installed_apps_entry
except ImportError:  # in oscar 3 this function has been removed

    def _find_installed_apps_entry(module_label):
        """
        Given a module label, finds the name of the matching Oscar app from the
        Django app registry.
        """
        if "dashboard" in module_label:
            split_module_label = module_label.split(".")[:2]
            potential_label = "_".join(list(reversed(split_module_label)))
            if potential_label.endswith("_dashboard"):
                try:
                    app_config = apps.get_app_config(potential_label)
                    app_label = ".".join(split_module_label)
                    return app_config.name, app_label
                except LookupError:
                    pass

        app_label = module_label.split(".")[0]

        try:
            app_config = apps.get_app_config(app_label)
        except LookupError:
            raise AppNotFoundError(
                "Couldn't find an app to import %s from" % module_label
            )

        return app_config.name, app_label


class OcyanClassLoader(object):
    def get_extension_module_names(self, oscar_app_name, installed_apps_entry):
        """
        Retrieve the names of the extension modules that should be searched for classes.
        """
        INSTALLED_APPS_SECTION = INSTALLED_APPS.section_for_app_extension(
            oscar_app_name
        )
        if INSTALLED_APPS_SECTION is None:
            INSTALLED_APPS_SECTION = INSTALLED_APPS.section_for_app_extension(
                installed_apps_entry
            )
            if (
                INSTALLED_APPS_SECTION is None
            ):  # nog steeds? Return dan maar de gevraagde app, er zijn geen overrides.
                return [installed_apps_entry]

        if oscar_app_name == installed_apps_entry:
            return INSTALLED_APPS_SECTION.extensions_for_app(oscar_app_name)
        else:
            extensions = INSTALLED_APPS_SECTION.extensions_for_app(oscar_app_name)
            # regular app overrides don't register extensions,
            # register it here if it is missing.
            # the reason that we want it registered, is that the ExtendableInstalledAppsItem
            # is layered. it could be possible that extensions is not empty at this
            # point but that it is just missing the entire registration tree behind
            # installed_apps_entry. That is why we register it here, to make sure
            # the layers that are allready present are not discarded entirely in
            # favor of installed_apps_entry's layers
            if installed_apps_entry not in extensions:
                INSTALLED_APPS_SECTION.register_extension(
                    installed_apps_entry, oscar_app_name
                )
                return INSTALLED_APPS_SECTION.extensions_for_app(oscar_app_name)
            return extensions

    def get_extension_modules(
        self, module_label, classnames, app_name, oscar_app_name, installed_apps_entry
    ):
        """
        Get all the extension modules that should be searched for classes.
        """
        extensions = self.get_extension_module_names(
            oscar_app_name, installed_apps_entry
        )
        extension_modules = []
        for application_extension in extensions:
            sub_module = module_label.replace(app_name, "", 1)
            application_extension_module_label = "%s%s" % (
                application_extension,
                sub_module,
            )
            application_extension_module = _import_module(
                application_extension_module_label, classnames
            )
            extension_modules.append(application_extension_module)

        return extension_modules

    def oscar_class_loader_part(self, module_label, classnames, module_prefix):
        ###############################################################
        # The following code had to be COPY PASTED FROM OSCAR
        # COPIED FROM oscar.core.loading.default_class_loader
        ###############################################################

        if "." not in module_label:
            raise ValueError("Importing from top-level modules is not supported")

        oscar_module_label = "%s.%s" % (module_prefix, module_label)
        oscar_module = _import_module(oscar_module_label, classnames)

        installed_apps_entry, app_name = _find_installed_apps_entry(module_label)
        ###############################################################
        # COPY PASTED CODE STOPS HERE
        ###############################################################
        return oscar_module, installed_apps_entry, app_name

    def __call__(self, module_label, classnames, module_prefix):
        # do the same as oscar
        oscar_module, installed_apps_entry, app_name = self.oscar_class_loader_part(
            module_label, classnames, module_prefix
        )

        oscar_app_name = "%s.%s" % (module_prefix, app_name)
        extension_modules = self.get_extension_modules(
            module_label, classnames, app_name, oscar_app_name, installed_apps_entry
        )
        extension_modules.append(oscar_module)

        if not any(extension_modules):
            raise OscarModuleNotFoundError(
                "The module with label '%s' could not be imported. This either "
                "means that it indeed does not exist, or you might have a problem "
                "with a circular import." % module_label
            )

        return _pluck_classes(extension_modules, classnames)


class OcyanBaseClassLoader(OcyanClassLoader):
    def __init__(self, extension_root):
        self.extension_root = extension_root

    def get_extension_module_names(self, oscar_app_name, installed_apps_entry):
        extensions = super().get_extension_module_names(
            oscar_app_name, installed_apps_entry
        )
        try:
            index = extensions.index(self.extension_root)
            return extensions[index + 1 :]
        except ValueError:
            raise OscarModuleNotFoundError(
                "trying to load baseclass not derivative of extension_root"
            )


ocyan_class_loader = OcyanClassLoader()


def _get_baseclasses(module_label, classnames, extension_root, module_prefix):
    loader = OcyanBaseClassLoader(extension_root)
    return loader(module_label, classnames, module_prefix)


def get_baseclass(module_label, classname, extension_root, module_prefix="oscar.apps"):
    return _get_baseclasses(module_label, [classname], extension_root, module_prefix)[0]


def get_baseclasses(
    module_label, classnames, extension_root, module_prefix="oscar.apps"
):
    return _get_baseclasses(module_label, classnames, extension_root, module_prefix)


def get_ocyan_baseclass(
    module_label, classname, extension_root, module_prefix="ocyan.plugin"
):
    return _get_baseclasses(module_label, [classname], extension_root, module_prefix)[0]


def get_ocyan_baseclasses(
    module_label, classnames, extension_root, module_prefix="ocyan.plugin"
):
    return _get_baseclasses(module_label, classnames, extension_root, module_prefix)


def get_ocyan_class(module_label, classname, module_prefix="ocyan.plugin"):
    return get_ocyan_classes(module_label, [classname], module_prefix)[0]


def get_ocyan_classes(module_label, classnames, module_prefix="ocyan.plugin"):
    return ocyan_class_loader(module_label, classnames, module_prefix)
