# -*- coding: utf-8 -*-

from enum import Enum
from typing import Dict, List, Optional, Set, no_type_check

import cherrypy
from mgr_module import CLICommand, Option
from mgr_util import CLIWarning

from ..controllers.cephfs import CephFS
from ..controllers.iscsi import Iscsi, IscsiTarget
from ..controllers.nfs import NFSGaneshaExports, NFSGaneshaUi
from ..controllers.rbd import Rbd, RbdSnapshot, RbdTrash
from ..controllers.rbd_mirroring import RbdMirroringPoolMode, \
    RbdMirroringPoolPeer, RbdMirroringSummary
from ..controllers.rgw import Rgw, RgwBucket, RgwDaemon, RgwUser
from . import PLUGIN_MANAGER as PM
from . import interfaces as I  # noqa: E741,N812
from .ttl_cache import ttl_cache


class Features(Enum):
    RBD = 'rbd'
    MIRRORING = 'mirroring'
    ISCSI = 'iscsi'
    CEPHFS = 'cephfs'
    RGW = 'rgw'
    NFS = 'nfs'
    DASHBOARD = 'dashboard'

    # if we want to add any custom warning message when enabling a feature
    # we can add it here as key-value pair in warn_msg.
    # eg: Features.ISCSI.value: 'iscsi warning message'
    @property
    def warning(self):
        warn_msg: Dict[str, str] = {}
        return warn_msg.get(self.value, None)


PREDISABLED_FEATURES = set()  # type: Set[str]

Feature2Controller = {
    Features.RBD: [Rbd, RbdSnapshot, RbdTrash],
    Features.MIRRORING: [
        RbdMirroringSummary, RbdMirroringPoolMode, RbdMirroringPoolPeer],
    Features.ISCSI: [Iscsi, IscsiTarget],
    Features.CEPHFS: [CephFS],
    Features.RGW: [Rgw, RgwDaemon, RgwBucket, RgwUser],
    Features.NFS: [NFSGaneshaUi, NFSGaneshaExports],
}


class Actions(Enum):
    ENABLE = 'enable'
    DISABLE = 'disable'
    STATUS = 'status'


# pylint: disable=too-many-ancestors
@PM.add_plugin
class FeatureToggles(I.CanMgr, I.Setupable, I.HasOptions,
                     I.HasCommands, I.FilterRequest.BeforeHandler,
                     I.HasControllers):
    OPTION_FMT = 'FEATURE_TOGGLE_{.name}'
    CACHE_MAX_SIZE = 128  # Optimum performance with 2^N sizes
    CACHE_TTL = 10  # seconds

    @PM.add_hook
    def setup(self):
        # pylint: disable=attribute-defined-outside-init
        self.Controller2Feature = {
            controller: feature
            for feature, controllers in Feature2Controller.items()
            for controller in controllers}  # type: ignore

    @PM.add_hook
    def get_options(self):
        return [Option(
            name=self.OPTION_FMT.format(feature),
            default=(feature not in PREDISABLED_FEATURES),
            type='bool',) for feature in Features]

    @PM.add_hook
    def register_commands(self):
        @CLICommand("dashboard feature")
        def cmd(mgr,
                action: Actions = Actions.STATUS,
                features: Optional[List[Features]] = None):
            '''
            Enable or disable features in Ceph-Mgr Dashboard
            '''
            ret = 0
            msg = []
            if action in [Actions.ENABLE, Actions.DISABLE]:
                if features is None:
                    ret = 1
                    msg = ["At least one feature must be specified"]
                else:
                    for feature in features:
                        mgr.set_module_option(
                            self.OPTION_FMT.format(feature),
                            action == Actions.ENABLE)
                        if action == Actions.ENABLE and feature.warning:
                            msg += [CLIWarning(feature.warning)]
                        msg += ["Feature '{.value}': {}".format(
                            feature,
                            'enabled' if action == Actions.ENABLE else
                            'disabled')]
            else:
                for feature in features or list(Features):
                    enabled = mgr.get_module_option(self.OPTION_FMT.format(feature))
                    msg += ["Feature '{.value}': {}".format(
                        feature,
                        'enabled' if enabled else 'disabled')]
            return ret, '\n'.join(msg), ''
        return {'handle_command': cmd}

    @no_type_check  # https://github.com/python/mypy/issues/7806
    def _get_feature_from_request(self, request):
        try:
            return self.Controller2Feature[
                request.handler.callable.__self__]
        except (AttributeError, KeyError):
            return None

    @ttl_cache(ttl=CACHE_TTL, maxsize=CACHE_MAX_SIZE)
    @no_type_check  # https://github.com/python/mypy/issues/7806
    def _is_feature_enabled(self, feature):
        return self.mgr.get_module_option(self.OPTION_FMT.format(feature))

    @PM.add_hook
    def filter_request_before_handler(self, request):
        feature = self._get_feature_from_request(request)
        if feature is None:
            return

        if not self._is_feature_enabled(feature):
            raise cherrypy.HTTPError(
                404, "Feature='{}' disabled by option '{}'".format(
                    feature.value,
                    self.OPTION_FMT.format(feature),
                )
            )

    @PM.add_hook
    def get_controllers(self):
        from ..controllers import APIDoc, APIRouter, EndpointDoc, RESTController

        FEATURES_SCHEMA = {
            "rbd": (bool, ''),
            "mirroring": (bool, ''),
            "iscsi": (bool, ''),
            "cephfs": (bool, ''),
            "rgw": (bool, ''),
            "nfs": (bool, ''),
            "dashboard": (bool, '')
        }

        @APIRouter('/feature_toggles')
        @APIDoc("Manage Features API", "FeatureTogglesEndpoint")
        class FeatureTogglesEndpoint(RESTController):
            @EndpointDoc("Get List Of Features",
                         responses={200: FEATURES_SCHEMA})
            def list(_):  # pylint: disable=no-self-argument  # noqa: N805
                return {
                    # pylint: disable=protected-access
                    feature.value: self._is_feature_enabled(feature)
                    for feature in Features
                }
        return [FeatureTogglesEndpoint]
