# -*- coding: utf-8; -*-
################################################################################
#
#  Rattail -- Retail Software Framework
#  Copyright © 2010-2024 Lance Edgar
#
#  This file is part of Rattail.
#
#  Rattail is free software: you can redistribute it and/or modify it under the
#  terms of the GNU General Public License as published by the Free Software
#  Foundation, either version 3 of the License, or (at your option) any later
#  version.
#
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
#  details.
#
#  You should have received a copy of the GNU General Public License along with
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Tailbone Web API - Auth Views
"""

from cornice import Service

from tailbone.api import APIView, api
from tailbone.db import Session
from tailbone.auth import login_user, logout_user


class AuthenticationView(APIView):

    @api
    def check_session(self):
        """
        View to serve as "no-op" / ping action to check current user's session.
        This will establish a server-side web session for the user if none
        exists.  Note that this also resets the user's session timer.
        """
        data = {'ok': True, 'permissions': []}
        if self.request.user:
            data['user'] = self.get_user_info(self.request.user)
            data['permissions'] = list(self.request.user_permissions)

        # background color may be set per-request, by some apps
        if hasattr(self.request, 'background_color') and self.request.background_color:
            data['background_color'] = self.request.background_color
        else: # otherwise we use the one from config
            data['background_color'] = self.rattail_config.get(
                'tailbone', 'background_color')

        # TODO: this seems the best place to return some global app
        # settings, but maybe not desirable in all cases..in which
        # case should caller need to ask for these explicitly?  or
        # make a different call altogether to get them..?
        app = self.get_rattail_app()
        customer_handler = app.get_clientele_handler()
        data['settings'] = {
            'customer_field_dropdown': customer_handler.choice_uses_dropdown(),
        }

        return data

    @api
    def login(self):
        """
        API login view.
        """
        if self.request.method == 'OPTIONS':
            return self.request.response

        username = self.request.json.get('username')
        password = self.request.json.get('password')
        if not (username and password):
            return {'error': "Invalid username or password"}

        # make sure credentials are valid
        user = self.authenticate_user(username, password)
        if not user:
            return {'error': "Invalid username or password"}

        # is there some reason this user should not login?
        error = self.why_cant_user_login(user)
        if error:
            return {'error': error}

        app = self.get_rattail_app()
        auth = app.get_auth_handler()

        login_user(self.request, user)
        return {
            'ok': True,
            'user': self.get_user_info(user),
            'permissions': list(auth.get_permissions(Session(), user)),
        }

    def authenticate_user(self, username, password):
        app = self.get_rattail_app()
        auth = app.get_auth_handler()
        return auth.authenticate_user(Session(), username, password)

    def why_cant_user_login(self, user):
        """
        This method is given a ``User`` instance, which represents someone who
        is just now trying to login, and has already cleared the basic hurdle
        of providing the correct credentials for a user on file.  This method
        is responsible then, for further verification that this user *should*
        in fact be allowed to login to this app node.  If the method determines
        a reason the user should *not* be allowed to login, then it should
        return that reason as a simple string.
        """

    @api
    def logout(self):
        """
        API logout view.
        """
        if self.request.method == 'OPTIONS':
            return self.request.response

        logout_user(self.request)
        return {'ok': True}

    @api
    def become_root(self):
        """
        Elevate the current request to 'root' for full system access.
        """
        if not self.request.is_admin:
            raise self.forbidden()
        self.request.user.record_event(self.enum.USER_EVENT_BECOME_ROOT)
        self.request.session['is_root'] = True
        return {
            'ok': True,
            'user': self.get_user_info(self.request.user),
        }

    @api
    def stop_root(self):
        """
        Lower the current request from 'root' back to normal access.
        """
        if not self.request.is_admin:
            raise self.forbidden()
        self.request.user.record_event(self.enum.USER_EVENT_STOP_ROOT)
        self.request.session['is_root'] = False
        return {
            'ok': True,
            'user': self.get_user_info(self.request.user),
        }

    @api
    def change_password(self):
        """
        View which allows a user to change their password.
        """
        if self.request.method == 'OPTIONS':
            return self.request.response

        if not self.request.user:
            raise self.forbidden()

        if self.request.user.prevent_password_change and not self.request.is_root:
            raise self.forbidden()

        data = self.request.json_body

        # first make sure "current" password is accurate
        if not self.authenticate_user(self.request.user, data['current_password']):
            return {'error': "The current/old password you provided is incorrect"}

        # okay then, set new password
        auth = self.app.get_auth_handler()
        auth.set_user_password(self.request.user, data['new_password'])
        return {
            'ok': True,
            'user': self.get_user_info(self.request.user),
        }

    @classmethod
    def defaults(cls, config):
        cls._auth_defaults(config)

    @classmethod
    def _auth_defaults(cls, config):

        # session
        check_session = Service(name='check_session', path='/session')
        check_session.add_view('GET', 'check_session', klass=cls)
        config.add_cornice_service(check_session)

        # login
        login = Service(name='login', path='/login')
        login.add_view('POST', 'login', klass=cls)
        config.add_cornice_service(login)

        # logout
        logout = Service(name='logout', path='/logout')
        logout.add_view('POST', 'logout', klass=cls)
        config.add_cornice_service(logout)

        # become root
        become_root = Service(name='become_root', path='/become-root')
        become_root.add_view('POST', 'become_root', klass=cls)
        config.add_cornice_service(become_root)

        # stop root
        stop_root = Service(name='stop_root', path='/stop-root')
        stop_root.add_view('POST', 'stop_root', klass=cls)
        config.add_cornice_service(stop_root)

        # change password
        change_password = Service(name='change_password', path='/change-password')
        change_password.add_view('POST', 'change_password', klass=cls)
        config.add_cornice_service(change_password)


def defaults(config, **kwargs):
    base = globals()

    AuthenticationView = kwargs.get('AuthenticationView', base['AuthenticationView'])
    AuthenticationView.defaults(config)


def includeme(config):
    defaults(config)
