import inspect

from defence360agent.rpc_tools import ValidationError
from defence360agent.rpc_tools.lookup import RootEndpoints, bind
from defence360agent.utils import Scope
from im360.subsys.features import get_applicable_features
from defence360agent.subsys.features.abstract_feature import (
    FeatureError,
    FeatureStatus,
)


def _supported_kwargs(method, kwargs):
    """Drop arguments the implementation does not accept.

    Features that refuse removal outright keep a bare remove(), so passing
    force on would raise TypeError instead of their own message.
    """
    if not kwargs:
        return kwargs
    params = inspect.signature(method).parameters
    if any(
        param.kind is inspect.Parameter.VAR_KEYWORD
        for param in params.values()
    ):
        return kwargs
    return {name: value for name, value in kwargs.items() if name in params}


class FeaturesEndpoints(RootEndpoints):
    SCOPE = Scope.IM360

    @bind("features", "list")
    async def feature_list(self):
        return {"items": list(get_applicable_features().keys())}

    @bind("features", "install")
    async def feature_install(self, name):
        return await self.call("install", name)

    @bind("features", "remove")
    async def feature_remove(self, name, force=False):
        # passed only when set, so features whose remove() takes no
        # arguments keep working untouched
        return await self.call(
            "remove", name, **({"force": True} if force else {})
        )

    @bind("features", "status")
    async def feature_status(self, name):
        return await self.call("status", name)

    async def call(self, action, name, **kwargs):
        try:
            _cls = get_applicable_features()[name]
        except KeyError:
            raise ValidationError("Feature is not available: {}".format(name))
        else:
            if _cls is None:
                raise ValidationError(f"No implementation for feature: {name}")

        try:
            feature = await _cls(sink=self._sink).init()
            method = getattr(feature, action)
            return await method(**_supported_kwargs(method, kwargs))
        except FeatureError as e:
            # implemented new format for status, but not for install/remove yet
            # also will be nice to move log file path to status response
            # because now, if status returned "installing", UI have to call
            # "install" (!) to get log file path
            if action == "status":
                return {
                    "items": {
                        "status": FeatureStatus.ERROR,
                        "message": str(e),
                    }
                }
            else:
                raise ValidationError(str(e))
