"""Ocyan plugins support

Uses the straight.plugin loader mechanism to load modules from the
ocyan.plugin namespace.
ie the modules ocyan.plugin.wagtail and ocyan.plugin.oscar will be loaded
automatically and installed in Django for you.
"""

from straight.plugin import load

from ocyan.core.sassfunctions import static
from ocyan.core.utils import (
    CSPSetting,
    FixtureAccumulator,
    SegmentedInstalledApps,
    unique_everseen,
)

PLUGINS_NAMESPACE = "ocyan.plugin"


class OcyanPluginConfig:
    """Base class for plugins.
    Used to load plugin configurations in ocyan.main
    """

    def contribute_to_urlpatterns(self, urlpatterns):
        """Mangle urlpatterns to work for this plugin.
        This is a NOOP implementation to allow piping with straight.plugin
        """
        return urlpatterns

    def contribute_to_installed_apps(self, installed_apps: SegmentedInstalledApps):
        """Mangle INSTALLED_APPS to work for this plugin.
        This is a NOOP implementation to allow piping with straight.plugin
        """
        return installed_apps

    def contribute_to_middleware(self, middleware):
        """Mangle MIDDLEWARE to work for this plugin.
        This is a NOOP implementation to allow piping with straight.plugin
        """
        return middleware

    def contribute_to_settings(self, settings):
        """Return the settings for this plugin.
        This is a NOOP implementation to allow piping with straight.plugin
        """
        return settings

    def contribute_to_i18n_fields(self, fields_dict):
        """
        Hook for registering translatable fields for use by the i18n plugin.

        The dictionary looks like this::

            {"app_label.model.field": to_fallback_or_not_to_fallback }

        to_fallback_or_not_to_fallback can be True or False depending if you need empty
        translations to return the base language value instead of None. In most
        cases you want the fallback, but you can disable it if needed.
        """
        return fields_dict

    def contribute_to_fixtures(self, fixtures):
        """
        Hook for adding fixtures to the demo data.

        Usage::

            fixtures.add("app_label", "app_label/things")
            return fixtures

        If you want to override some existing fixture, specify a priority::

            fixtures.add("app_label", "app_label/things", 1)
            return fixtures
        """
        return fixtures

    def get_smoketest_modules(self):
        """Return the test modules from this plugin that should be ran in a
        live site test context.
        """
        return []

    def get_spooler_modules(self, modules):
        "Append spooler modules to the list if they exist"
        return modules

    def contribute_to_sass_functions(self, sass_functions):
        "Add sass functions to be used in your templates to the context"
        return sass_functions

    def contribute_to_csp_settings(self, csp_settings):
        "Add configuration to the CSPSetting object"
        return csp_settings

    def contribute_to_maintenance(self, maintenances):
        """
        Add manintenance jobs

        Maintenance jobs can be added like this::

        def contribute_to_maintenance(self, maintenances):
            priority = 1000
            return maintenances + [
                (priority, "ocyan.core.cool_maintence_function"),
            ]
        """
        return maintenances


def get_plugins(cls=OcyanPluginConfig):
    """Load the plugins."""
    return load(PLUGINS_NAMESPACE, cls).produce()


def get_installed_apps(**initial_apps):
    _installed_apps = SegmentedInstalledApps(**initial_apps)
    plugins = get_plugins()
    return plugins.pipe("contribute_to_installed_apps", _installed_apps)


def get_middleware(*initial_middlewares):
    plugins = get_plugins()
    middleware = plugins.pipe("contribute_to_middleware", list(initial_middlewares))
    return list(unique_everseen(middleware))


def get_fixtures():
    plugins = get_plugins()
    return plugins.pipe("contribute_to_fixtures", FixtureAccumulator())


def add_plugin_settings(settings):
    plugins = get_plugins()
    new_settings = plugins.pipe("contribute_to_settings", settings)
    # because the settings are the local variables of the settings module the
    # result of the settings call should be used to update the settings, because
    # some of the plugin might not mutate the settings dict.
    settings.update(new_settings)


def get_spooler_mdodules():
    plugins = get_plugins()
    return plugins.pipe("get_spooler_modules", [])


def get_spooler_modules():
    return get_spooler_mdodules()


def get_i18n_fields():
    plugins = get_plugins()
    return plugins.pipe("contribute_to_i18n_fields", {})


def get_sass_functions():
    plugins = get_plugins()
    return plugins.pipe("contribute_to_sass_functions", {"static": static})


def get_csp_settings():
    plugins = get_plugins()
    return plugins.pipe("contribute_to_csp_settings", CSPSetting())


def get_maintenances():
    plugins = get_plugins()
    return [m[1] for m in sorted(plugins.pipe("contribute_to_maintenance", []))]
