"""
The idea of this PAS plugin is quite simple. It should check the user profile for the user being logged in
and if user has enabled two-step verification for his account (``enable_two_step_verification`` is set to
True), then redirect him further to a another page, where he would enter his SMS Authenticator token, after
successful validation of which the user would be definitely logged in.

If user has not enabled the two-step verification for his account (``enable_two_step_verification``
is set to False), then do nothing so that Plone continues logging in the user normal way.
"""
import logging

from Globals import InitializeClass
from AccessControl.SecurityInfo import ClassSecurityInfo

from zope.i18nmessageid import MessageFactory

from plone import api

from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
from Products.PluggableAuthService.utils import classImplements
from Products.PluggableAuthService.interfaces.plugins import IAuthenticationPlugin
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.CMFCore.utils import getToolByName
from Products.statusmessages.interfaces import IStatusMessage

from collective.smsauthenticator.adapter import ICameFrom
from collective.smsauthenticator.helpers import (
    sign_user_data, is_whitelisted_client, generate_code, send_login_code_sms
    )

logger = logging.getLogger("collective.smsauthenticator")

_ = MessageFactory('collective.smsauthenticator')

manage_addSMSAuthenticatorPluginForm = PageTemplateFile(
    './www/add_sms_authenticator_form',
    globals(),
    __name__ = 'manage_addSMSAuthenticatorPluginForm'
    )

def addSMSAuthenticatorPlugin(self, id, title='', REQUEST=None):
    """
    Add a SMS Authenticator PAS Plugin to Plone PAS
    """
    o = SMSAuthenticatorPlugin(id, title)
    self._setObject(o.getId(), o)

    if REQUEST is not None:
        REQUEST['RESPONSE'].redirect(
            '{0}/manage_main?manage_tabs_message=SMS+Authentiactor+PAS+Plugin+added.'.format(self.absolute_url())
            )

class SMSAuthenticatorPlugin(BasePlugin):
    """
    SMS Authenticator PAS Plugin
    """
    meta_type = 'Collective SMS Authenticator PAS'
    security = ClassSecurityInfo()

    def __init__(self, id, title=None):
        self._setId(id)
        self.title = title

    def authenticateCredentials(self, credentials):
        """
        Place to actually validate the user credentials specified and return a tuple (login, login)
        on success or (None, None) on failure.

        If we find one and two-step verification is not enabled for the account, we consider
        the authentication passed and log the user in. If two-step verification has been enabled
        for the account, the first step of authentication is considered to be passed and we go to
        the next page (having the user and pass remembered), where we check for the token generated by
        the token generator (SMS Authenticator). If the token is valid too, we log the user in.
        """
        login = credentials['login']
        password = credentials['password']

        if not login:
            return (None, None)

        user = api.user.get(username=login)

        #logger.debug("Found user: {0}".format(user.getProperty('username')))

        two_step_verification_enabled = user.getProperty('enable_two_step_verification')
        #logger.debug("Two-step verification enabled: {0}".format(two_step_verification_enabled))

        if two_step_verification_enabled:
            # First see, if the password is correct.
            mt = getToolByName(self, 'portal_membership')
            plone_auth_user = None
            try:
                # This is where the password check is made.
                plone_auth_user = mt.authenticate(login, password, self.REQUEST)

            except Exception as e:
                plone_auth_user = None

            if not plone_auth_user:
                # Password check failed.
                return None

            if is_whitelisted_client():
                return (None, None)

            # Setting the data in the session doesn't seem to work. That's why we use the `ska` package.
            # The secret key would be then a combination of username, secret stored in users' profile
            # and the browser version.
            request = self.REQUEST
            response = request['RESPONSE']
            response.setCookie('__ac', '', path='/')

            # Redirect to token thing...
            signed_url = sign_user_data(request=request, user=user, url='@@sms-authenticator-token')

            came_from_adapter = ICameFrom(request)
            # Appending possible `came_from`, but give it another name.
            came_from = came_from_adapter.getCameFrom()
            if came_from:
                signed_url = '{0}&next_url={1}'.format(signed_url, came_from)

            # ****************************************
            # Generate the login code and send the SMS
            uuu = api.user.get(username=login)
            mobile_number = uuu.getProperty('mobile_number')
            mobile_number_authentication_code = uuu.getProperty('mobile_number_authentication_code')

            # If user doesn't have yet a mobile numer specified, redirect him to the reset mobile
            # number view.
            if not mobile_number:
                IStatusMessage(request).addStatusMessage(
                    _("Two-step verification is enabled for your account, but you haven't specified "
                      "a mobile number yet! In order to be able to log in, you have to go through mobile "
                      "number recovery procedure first."), 'warning'
                    )
                response.redirect('@@request-mobile-number-reset', lock=1)
                return None

            # If code was not yet sent, generate a code and send an SMS.
            if not mobile_number_authentication_code:
                mobile_number_authentication_code = generate_code(uuu)

                # Send the SMS
                sms_sent = send_login_code_sms(
                    mobile_number = mobile_number,
                    code = mobile_number_authentication_code
                    )

                if sms_sent:
                    # Save the `signature` value to the `mobile_number_reset_token`.
                    uuu.setMemberProperties(
                        mapping = {
                            'mobile_number_authentication_code': mobile_number_authentication_code,
                        }
                        )
                #else:
                #    IStatusMessage(request).addStatusMessage(
                #        _("An error occured while sending the SMS to the number given."), 'error'
                #        )
                #    response.redirect('login', lock=1)
                #    return None

                    #logger.debug("mobile number: {0}".format(mobile_number))

            # Redirecting user to authentication code validation page.
            response.redirect(signed_url, lock=1)

            return None

        if credentials.get('extractor') != self.getId():
            return (None, None)

        return (None, None)


classImplements(SMSAuthenticatorPlugin, IAuthenticationPlugin)
InitializeClass(SMSAuthenticatorPlugin)
