from hashlib import sha1
from logging import getLogger
from plone import api
from plone.memoize import ram
from ploneintranet import api as pi_api
from ploneintranet.layout.browser.proto import _forever_cache_key
from ploneintranet.userprofile.content.userprofile import UserProfile
from Products.Five import BrowserView
from typing import Any
from zope.interface import implementer
from zope.publisher.interfaces import IPublishTraverse

logger = getLogger(__name__)


@implementer(IPublishTraverse)
class View(BrowserView):
    """"""

    _action = ""
    exchange_keys = (
        "ploneintranet.layout.notification_exchange",
        "ploneintranet.layout.push_exchange",
    )

    def publishTraverse(self, request: Any, name: str) -> None:
        """Setup the _action instance variable,
        used to decide how to handle the request
        """
        self._action = name
        return self

    @property
    def user(self) -> UserProfile | None:
        """Get the user based on the requested username"""
        return pi_api.userprofile.get(self.request.form.get("username"))

    @ram.cache(_forever_cache_key)
    def password_for_username(self, username: str, token: str) -> str:
        """Simple token based authentication for a user"""
        return sha1(f"{username}:{token}".encode()).hexdigest()

    @property
    def valid_exchanges(self) -> set[str]:
        """Return the exchanges we are aware of"""
        return {
            api.portal.get_registry_record(key, default="")
            for key in self.exchange_keys
        }

    def allow_user(self, username: str, password: str, **kwargs: str) -> bool:
        """Handle authentication requests.
        The form must have:

        - username
        - password
        """
        if kwargs:
            logger.warning("Unexpected extra parameters: %r", kwargs)

        # Validate the username and password to not be empty
        if not username and password:
            return False

        token = api.portal.get_registry_record(
            "ploneintranet.layout.push_token", default=""
        )
        if not token:
            return False
        expected = self.password_for_username(username, token)
        return password == expected

    def allow_vhost(self, username: str, vhost: str, ip: str, tags: str) -> bool:
        """
        - username
        - vhost, e.g. "/"
        - ip
        - tags, must be ""
        """
        if tags:
            # We do not want to allow any tags
            return False
        if username and vhost and ip:
            return True

    def allow_topic(
        self,
        username: str,
        vhost: str,
        resource: str,
        name: str,
        permission: str,
        tags: str,
        routing_key: str,
        **kwargs: str,
    ) -> bool:
        """Allow the access to a topic based on permissions"""
        if tags:
            return False
        if not (vhost and username):
            return False
        if resource != "topic":
            return False
        if not (permission == "read" and name in self.valid_exchanges):
            return False
        if routing_key not in (
            f"{username}.#",
            "undefined.#",
        ):
            # XXX: I am not sure why we have an undefined here...
            # it seems to come from javascript, this should probably be fixed
            # Probably it is related to notification_filter not set
            # in the push header viewlet
            return False
        if set(kwargs) != {
            "variable_map.username",
            "variable_map.vhost",
        }:
            return False
        return (
            kwargs["variable_map.username"] == username
            and kwargs["variable_map.vhost"] == vhost
        )

    def allow_resource(
        self,
        username: str,
        vhost: str,
        resource: str,
        name: str,
        permission: str,
        tags: str,
    ) -> bool:
        """Allow the access to a resource based on permissions"""
        if tags:
            return False
        if not (vhost and username):
            return False
        if resource not in ("exchange", "queue"):
            return False
        if resource == "exchange":
            return permission == "read" and name in self.valid_exchanges
        elif resource == "queue":
            return permission in ("configure", "read", "write") and name.startswith(
                "stomp-subscription-"
            )
        return False

    def do_action(self) -> bool:
        """Pick the proper method to decide if we should return allow or deny"""
        method = getattr(self, f"allow_{self._action}")
        try:
            return method(**self.request.form)
        except TypeError as e:
            # Most probably the request does not match the method signature
            logger.info("Failed to call %r: %s", method, e)
        return False

    def __call__(self) -> str:
        """This view should return only "allow" or "deny" """
        # Do not even think allowing something if we have no user
        user = self.user
        if not user:
            return "deny"

        try:
            # This will call the proper method
            # based on the _action instance variable setup
            # set up by the traversal
            allow = self.do_action()
            logger.debug("%r, %r -> %r", self._action, self.request.form, allow)
        except AttributeError as e:
            logger.warning("Failed to execute action: %s", e)
            return "deny"
        return "allow" if allow is True else "deny"
