# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

import logging
import collections

from pyramid.exceptions import ConfigurationError

from rhodecode.lib.utils2 import safe_str
from rhodecode.model.settings import SettingsModel
from rhodecode.translation import _


log = logging.getLogger(__name__)


class AuthnResourceBase(object):
    __name__ = None
    __parent__ = None

    def get_root(self):
        current = self
        while current.__parent__ is not None:
            current = current.__parent__
        return current


class AuthnPluginResourceBase(AuthnResourceBase):
    def __init__(self, plugin):
        self.plugin = plugin
        self.__name__ = plugin.get_url_slug()
        self.display_name = plugin.get_display_name()


class AuthnRootResource(AuthnResourceBase):
    """
    This is the root traversal resource object for the authentication settings.
    """

    is_root = True

    def __init__(self):
        self._store = collections.OrderedDict()
        self._resource_name_map = {}
        self.display_name = _("Authentication Plugins")

    def __getitem__(self, key):
        """
        Customized get item function to return only items (plugins) that are
        activated.
        """
        if self._is_item_active(key):
            return self._store[key]
        else:
            raise KeyError('Authentication plugin "{}" is not active.'.format(key))

    def __iter__(self):
        for key in self._store.keys():
            if self._is_item_active(key):
                yield self._store[key]

    def _is_item_active(self, key):
        activated_plugins = SettingsModel().get_auth_plugins()
        plugin_id = self.get_plugin_id(key)
        return plugin_id in activated_plugins

    def get_plugin_id(self, resource_name):
        """
        Return the plugin id for the given traversal resource name.
        """
        # TODO: Store this info in the resource element.
        return self._resource_name_map[resource_name]

    def get_sorted_list(self, sort_key=None):
        """
        Returns a sorted list of sub resources for displaying purposes.
        """

        def default_sort_key(resource):
            return str.lower(safe_str(resource.display_name))

        active = [item for item in self]
        return sorted(active, key=sort_key or default_sort_key)

    def get_nav_list(self, sort=True):
        """
        Returns a sorted list of resources for displaying the navigation.
        """
        if sort:
            nav_list = self.get_sorted_list()
        else:
            nav_list = [item for item in self]

        nav_list.insert(0, self)
        return nav_list

    def add_authn_resource(self, config, plugin_id, resource):
        """
        Register a traversal resource as a sub element to the authentication
        settings. This method is registered as a directive on the pyramid
        configurator object and called by plugins.
        """

        def _ensure_unique_name(name, limit=100):
            counter = 1
            current = name
            while current in self._store.keys():
                current = f"{name}{counter}"
                counter += 1
                if counter > limit:
                    raise ConfigurationError(
                        'Cannot build unique name for traversal resource "%s" registered by plugin "%s"',
                        name,
                        plugin_id,
                    )
            return current

        # Allow plugin resources with identical names by rename duplicates.
        unique_name = _ensure_unique_name(resource.__name__)
        if unique_name != resource.__name__:
            log.warning(
                'Name collision for traversal resource "%s" registered by authentication plugin "%s"',
                resource.__name__,
                plugin_id,
            )
            resource.__name__ = unique_name

        log.debug('Register traversal resource "%s" for plugin "%s"', unique_name, plugin_id)
        self._resource_name_map[unique_name] = plugin_id
        resource.__parent__ = self
        self._store[unique_name] = resource


root = AuthnRootResource()


def root_factory(request=None):
    """
    Returns the root traversal resource instance used for the authentication
    settings route.
    """
    return root
