# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


import functools

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db import models

from authentic2.backends import get_user_queryset
from authentic2.user_login_failure import user_login_failure, user_login_success

from .. import app_settings


def upn(username, realm):
    '''Build an UPN from a username and a realm'''
    return f'{username}@{realm}'


PROXY_USER_MODEL = None


class ModelBackend(ModelBackend):
    """
    Authenticates against settings.AUTH_USER_MODEL.
    """

    def get_query(self, username, realm=None, ou=None):
        UserModel = get_user_model()
        username_field = 'username'
        queries = []
        try:
            if app_settings.A2_ACCEPT_EMAIL_AUTHENTICATION and UserModel._meta.get_field('email'):
                queries.append(models.Q(**{'email__iexact': username}))
        except models.FieldDoesNotExist:
            pass

        if realm is None:
            queries.append(models.Q(**{username_field: username}))
            if '@' not in username:
                if app_settings.REALMS:
                    for realm, desc in app_settings.REALMS:
                        queries.append(models.Q(**{username_field: upn(username, realm)}))
        else:
            queries.append(models.Q(**{username_field: upn(username, realm)}))
        queries = functools.reduce(models.Q.__or__, queries)
        if ou:
            queries &= models.Q(ou=ou)
        return queries

    def must_reset_password(self, user):
        from .. import models

        return bool(models.PasswordReset.filter(user=user).count())

    def authenticate(self, request, username=None, password=None, realm=None, ou=None):
        UserModel = get_user_model()
        if not username:
            return
        query = self.get_query(username=username, realm=realm, ou=ou)
        users = get_user_queryset().filter(query)
        # order by username to make username without realm come before usernames with realms
        # i.e. "toto" should come before "toto@example.com"
        users = users.order_by('-is_active', UserModel.USERNAME_FIELD, 'id')
        for user in users:
            if user.check_password(password):
                user_login_success(user.get_username())
                return user
            else:
                user_login_failure(user.get_username())
                if hasattr(request, 'failed_logins'):
                    request.failed_logins.add(user)

    def get_user(self, user_id):
        UserModel = get_user_model()
        try:
            user = UserModel._default_manager.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
        return user

    def get_saml2_authn_context(self):
        import lasso

        return lasso.SAML2_AUTHN_CONTEXT_PASSWORD


class DummyModelBackend(ModelBackend):
    def authenticate(self, request, user=None):
        if user is not None:
            return user
