from AccessControl import Unauthorized
from plone import api
from plone.base import PloneMessageFactory as _
from plone.base.interfaces import ILoginForm
from ploneintranet.layout.utils import TEST_SITE_MARKER
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.browser.login.login import LoginForm as LoginFormPlone
from Products.Five.browser import BrowserView
from Products.statusmessages.interfaces import IStatusMessage
from urllib import parse
from z3c.form import button
from zope.component import getMultiAdapter
from zope.interface import implementer


@implementer(ILoginForm)
class LoginForm(LoginFormPlone):
    @property
    def quaive_title(self):
        return api.portal.get_registry_record(
            name="ploneintranet.layout.quaive_title", default=None
        )

    def is_2fa_enabled(self):
        return self.request.get("2fa_in_progress", False)

    @property
    def is_test_site(self):
        return bool(TEST_SITE_MARKER)

    @button.buttonAndHandler(_("Log in"), name="login")
    def handleLogin(self, action):
        data, errors = self.extractData()
        if errors:
            self.status = self.formErrorsMessage
            return
        membership_tool = getToolByName(self.context, "portal_membership")
        status_msg = IStatusMessage(self.request)
        if membership_tool.isAnonymousUser():
            self.request.response.expireCookie("__ac", path="/")
            if self.is_2fa_enabled():
                return
            if self.use_email_as_login():
                status_msg.addStatusMessage(
                    _(
                        "Login failed. Both email address and password are "
                        "case sensitive, check that caps lock is not enabled."
                    ),
                    "error",
                )
            else:
                status_msg.addStatusMessage(
                    _(
                        "Login failed. Both login name and password are case "
                        "sensitive, check that caps lock is not enabled."
                    ),
                    "error",
                )
            return

        is_initial_login = self._post_login()

        came_from = data.get("came_from", None)
        self.redirect_after_login(came_from, is_initial_login)


class RequireLoginView(BrowserView):
    def __call__(self):
        is_ajax = self.request.get("HTTP_X_REQUESTED_WITH", None) == "XMLHttpRequest"
        url = None
        portal_state = getMultiAdapter(
            (self.context, self.request), name="plone_portal_state"
        )
        portal = portal_state.portal()

        came_from = self.request.get("came_from", "")
        if portal_state.anonymous():
            # 401 error
            url = f"{portal.absolute_url():s}/login"
            if is_ajax:
                self.request.response.setStatus(401, "Unauthorized")
                # Only use the ``@@unauthorized`` or session expired page for
                # ajax requests. For non-ajax requests (like first login)
                # redirect to login page.
                url = f"{portal.absolute_url():s}/unauthorized"
            if came_from:
                url += f"?came_from={parse.quote(came_from):s}"
            return self.request.response.redirect(url)

        # Since https://github.com/zopefoundation/Products.PluggableAuthService/pull/87
        # the came from parameter is not expected to contain the portal_url
        # anymore.
        # Sanitize it in case some code is not yet aware of that
        portal_url = portal.absolute_url()
        if came_from.startswith(portal_url):
            came_from = came_from[len(portal_url) :]

        unauth_url = f"{portal_url:s}/insufficient-privileges"
        portal_path = parse.urlparse(portal_url).path + "/"
        if came_from.startswith(portal_path):
            relpath = came_from[len(portal_path) :]
        else:
            relpath = came_from.lstrip("/")

        relpath, qs = relpath.partition("?")[::2]

        try:
            resource = portal.restrictedTraverse(relpath)
        except (Unauthorized, KeyError):
            if is_ajax:
                self.request.response.setStatus(403, "Forbidden")
            return self.request.response.redirect(unauth_url)

        if isinstance(resource, self.__class__):
            # Do not fall in endless loops if the came_from points to this view!
            return self.request.response.redirect(unauth_url)

        if (
            came_from
            and callable(resource)
            and api.user.has_permission("View", obj=resource)
        ):
            # We may have permission on the resource but it may still throw
            # Unauthorized when called which would trigger an infinite redirect
            try:
                resource()
            except Unauthorized:
                if is_ajax:
                    self.request.response.setStatus(403, "Forbidden")
                return self.request.response.redirect(unauth_url)
            url = f"{portal_url}/{relpath}"
            if qs:
                url = f"{url}?{qs}"
            return self.request.response.redirect(url)

        if is_ajax:
            self.request.response.setStatus(403, "Forbidden")
        return self.request.response.redirect(unauth_url)
