# authentic2 - versatile identity manager
# Copyright (C) 2010-2020 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/>.

from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _

from authentic2.apps.journal.models import EventTypeDefinition
from authentic2.apps.journal.utils import form_to_old_new
from authentic2.backends.ldap_backend import (
    LDAP_DEACTIVATION_REASON_NOT_PRESENT,
    LDAP_DEACTIVATION_REASON_OLD_SOURCE,
)
from authentic2.custom_user.models import DeletedUser
from authentic2.journal_event_types import EventTypeWithService, get_attributes_label
from django_rbac.utils import get_role_model

User = get_user_model()
Role = get_role_model()


def user_to_str(user):
    if hasattr(user, 'get_full_name'):
        return user.get_full_name()
    return str(user)


class ManagerUserCreation(EventTypeDefinition):
    name = 'manager.user.creation'
    label = _('user creation')

    @classmethod
    def record(cls, user, session, form, api=False):
        super().record(user=user, session=session, references=[form.instance], api=api)

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        # user journal page
        if context and context == user:
            return _('creation by administrator')
        elif user:
            # manager gloabal journal page
            return _('creation of user "%s"') % user_to_str(user)
        return super().get_message(event, context)


class ManagerUserProfileEdit(EventTypeDefinition):
    name = 'manager.user.profile.edit'
    label = _('user profile edit')

    @classmethod
    def record(cls, user, session, form, api=False):
        super().record(
            user=user, session=session, references=[form.instance], data=form_to_old_new(form), api=api
        )

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        new = event.get_data('new') or {}
        edited_attributes = ', '.join(get_attributes_label(new)) or ''
        if context and context == user:
            return _('edit by administrator (%s)') % edited_attributes
        elif user:
            user_full_name = user_to_str(user)
            return _('edit of user "{0}" ({1})').format(user_full_name, edited_attributes)
        return super().get_message(event, context)


class ManagerUserEmailChangeRequest(EventTypeDefinition):
    name = 'manager.user.email.change.request'
    label = _('email change request')

    @classmethod
    def record(cls, user, session, form):
        data = {
            'old_email': form.instance.email,
            'email': form.cleaned_data.get('new_email'),
        }
        super().record(user=user, session=session, references=[form.instance], data=data)

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        new_email = event.get_data('email')
        if context and context == user:
            return _('email change for email address "%s" requested by administrator') % new_email
        elif user:
            user_full_name = user_to_str(user)
            return _('email change of user "{0}" for email address "{1}"').format(user_full_name, new_email)
        return super().get_message(event, context)


class ManagerUserPasswordChange(EventTypeDefinition):
    name = 'manager.user.password.change'
    label = _('user password change')

    @classmethod
    def record(cls, user, session, form, api=False):
        cleaned_data = getattr(form, 'cleaned_data', {})
        data = {
            'generate_password': cleaned_data.get('generate_password', False),
            'send_mail': cleaned_data.get('send_mail', False),
        }
        super().record(user=user, session=session, references=[form.instance], data=data, api=api)

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        send_mail = event.get_data('send_mail')
        if context and context == user:
            if send_mail:
                return _('password change by administrator and notification by mail')
            else:
                return _('password change by administrator')
        elif user:
            user_full_name = user_to_str(user)
            if send_mail:
                return _('password change of user "%s" and notification by mail') % user_full_name
            else:
                return _('password change of user "%s"') % user_full_name
        return super().get_message(event, context)


class ManagerUserPasswordResetRequest(EventTypeDefinition):
    name = 'manager.user.password.reset.request'
    label = _('user password reset request')

    @classmethod
    def record(cls, user, session, target_user, api=False):
        super().record(
            user=user, session=session, references=[target_user], data={'email': target_user.email}, api=api
        )

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        email = event.get_data('email')
        if context and context == user:
            return _('password reset request by administrator sent to "%s"') % email
        elif user:
            return _('password reset request of "{0}" sent to "{1}"').format(user_to_str(user), email)
        return super().get_message(event, context)


class ManagerUserPasswordChangeForce(EventTypeDefinition):
    name = 'manager.user.password.change.force'
    label = _('mandatory password change at next login set')

    @classmethod
    def record(cls, user, session, target_user):
        super().record(user=user, session=session, references=[target_user])

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        if context and context == user:
            return _('mandatory password change at next login set by administrator')
        elif user:
            return _('mandatory password change at next login set for user "%s"') % user_to_str(user)
        return super().get_message(event, context)


class ManagerUserPasswordChangeUnforce(EventTypeDefinition):
    name = 'manager.user.password.change.unforce'
    label = _('mandatory password change at next login unset')

    @classmethod
    def record(cls, user, session, target_user):
        super().record(user=user, session=session, references=[target_user])

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        if context and context == user:
            return _('mandatory password change at next login unset by administrator')
        elif user:
            return _('mandatory password change at next login unset for user "%s"') % user_to_str(user)
        return super().get_message(event, context)


class ManagerUserActivation(EventTypeDefinition):
    name = 'manager.user.activation'
    label = _('user activation')

    @classmethod
    def record(cls, target_user, user=None, session=None, origin=None, reason=None):
        data = {'origin': origin, 'reason': reason}
        super().record(user=user, session=session, references=[target_user], data=data)

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        reason = event.get_data('reason')
        if context and context == user:
            if reason == 'ldap-reactivation':
                return _('automatic activation because the associated LDAP account reappeared')
            else:
                return _('activation by administrator')
        elif user:
            if reason == 'ldap-reactivation':
                return _(
                    'automatic activation of user "%s" because the associated LDAP account reappeared'
                ) % user_to_str(user)
            else:
                return _('activation of user "%s"') % user_to_str(user)
        return super().get_message(event, context)


class ManagerUserDeactivation(EventTypeDefinition):
    name = 'manager.user.deactivation'
    label = _('user deactivation')

    @classmethod
    def record(cls, target_user, user=None, session=None, origin=None, reason=None):
        data = {'reason': reason, 'origin': origin}
        super().record(user=user, session=session, references=[target_user], data=data)

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        reason = event.get_data('reason')
        if context and context == user:
            if reason == LDAP_DEACTIVATION_REASON_NOT_PRESENT:
                return _('automatic deactivation because the associated LDAP account does not exist anymore')
            elif reason == LDAP_DEACTIVATION_REASON_OLD_SOURCE:
                return _('automatic deactivation because the associated LDAP source has been deleted')
            else:
                return _('deactivation by administrator')
        elif user:
            if reason == LDAP_DEACTIVATION_REASON_NOT_PRESENT:
                return _(
                    'automatic deactivation of user "%s" because the associated LDAP account does not exist anymore'
                ) % user_to_str(user)
            elif reason == LDAP_DEACTIVATION_REASON_OLD_SOURCE:
                return _(
                    'automatic deactivation of user "%s" because the associated LDAP source has been deleted'
                ) % user_to_str(user)
            else:
                return _('deactivation of user "%s"') % user_to_str(user)
        return super().get_message(event, context)


class ManagerUserDeletion(EventTypeDefinition):
    name = 'manager.user.deletion'
    label = _('user deletion')

    @classmethod
    def record(cls, user, session, target_user, api=False):
        super().record(user=user, session=session, references=[target_user], api=api)

    @classmethod
    def get_message(cls, event, context):
        (user,) = event.get_typed_references((DeletedUser, User))
        if context and context == user:
            return _('deletion by administrator')
        elif user:
            return _('deletion of user "%s"') % user_to_str(user)
        return super().get_message(event, context)


class ManagerUserSSOAuthorizationDeletion(EventTypeWithService):
    name = 'manager.user.sso.authorization.deletion'
    label = _('delete authorization')

    @classmethod
    def record(cls, user, session, service, target_user):
        super().record(user=user, session=session, service=service, references=[target_user])

    @classmethod
    def get_message(cls, event, context):
        # first reference is to the service
        __, user = event.get_typed_references(None, (DeletedUser, User))
        service_name = cls.get_service_name(event)
        if context and context == user:
            return _('deletion of authorization of single sign on with "{service}" by administrator').format(
                service=service_name
            )
        elif user:
            return _('deletion of authorization of single sign on with "{service}" of user "{user}"').format(
                service=service_name,
                user=user_to_str(user),
            )
        return super().get_message(event, context)


class RoleEventsMixin(EventTypeDefinition):
    @classmethod
    def record(self, user, role, session=None, references=None, data=None, api=False):
        references = references or []
        references = [role] + references
        data = data or {}
        data.update({'role_name': str(role), 'role_uuid': role.uuid})
        super().record(
            user=user,
            session=session,
            references=references,
            data=data,
            api=api,
        )


class ManagerRoleCreation(RoleEventsMixin):
    name = 'manager.role.creation'
    label = _('role creation')

    @classmethod
    def get_message(cls, event, context):
        (role,) = event.get_typed_references(Role)
        role = role or event.get_data('role_name')
        if context != role:
            return _('creation of role "%s"') % role
        else:
            return _('creation')


class ManagerRoleEdit(RoleEventsMixin):
    name = 'manager.role.edit'
    label = _('role edit')

    @classmethod
    def record(cls, user, session, role, form, api=False):
        super().record(user=user, session=session, role=role, data=form_to_old_new(form), api=api)

    @classmethod
    def get_message(cls, event, context):
        (role,) = event.get_typed_references(Role)
        role = role or event.get_data('role_name')
        new = event.get_data('new')
        edited_attributes = ', '.join(get_attributes_label(new)) or ''
        if context != role:
            return _('edit of role "{role}" ({change})').format(role=role, change=edited_attributes)
        else:
            return _('edit ({change})').format(change=edited_attributes)


class ManagerRoleDeletion(RoleEventsMixin):
    name = 'manager.role.deletion'
    label = _('role deletion')

    @classmethod
    def get_message(cls, event, context):
        (role,) = event.get_typed_references(Role)
        role = role or event.get_data('role_name')
        if context != role:
            return _('deletion of role "%s"') % role
        else:
            return _('deletion')


class ManagerRoleMembershipGrant(RoleEventsMixin):
    name = 'manager.role.membership.grant'
    label = _('role membership grant')

    @classmethod
    def record(cls, user, session, role, member, api=False):
        data = {'member_name': member.get_full_name()}
        super().record(user=user, session=session, role=role, references=[member], data=data, api=api)

    @classmethod
    def get_message(cls, event, context):
        role, member = event.get_typed_references(Role, (DeletedUser, User))
        role = role or event.get_data('role_name')
        member = member or event.get_data('member_name')
        if context == member:
            return _('membership grant in role "%s"') % role
        elif context == role:
            return _('membership grant to user "%s"') % member
        else:
            return _('membership grant to user "{member}" in role "{role}"').format(member=member, role=role)


class ManagerRoleMembershipRemoval(RoleEventsMixin):
    name = 'manager.role.membership.removal'
    label = _('role membership removal')

    @classmethod
    def record(cls, user, session, role, member, api=False):
        data = {'member_name': member.get_full_name()}
        super().record(user=user, session=session, role=role, references=[member], data=data, api=api)

    @classmethod
    def get_message(cls, event, context):
        role, member = event.get_typed_references(Role, (DeletedUser, User))
        role = role or event.get_data('role_name')
        member = member or event.get_data('member_name')
        if context == member:
            return _('membership removal from role "%s"') % role
        elif context == role:
            return _('membership removal of user "%s"') % member
        else:
            return _('membership removal of user "{member}" from role "{role}"').format(
                member=member, role=role
            )


class ManagerRoleInheritanceAddition(RoleEventsMixin):
    name = 'manager.role.inheritance.addition'
    label = _('role inheritance addition')

    @classmethod
    def record(cls, user, session, parent, child):
        data = {
            'child_name': str(child),
            'child_uuid': child.uuid,
        }
        super().record(user=user, session=session, role=parent, references=[child], data=data)

    @classmethod
    def get_message(cls, event, context):
        parent, child = event.get_typed_references(Role, Role)
        parent = parent or event.get_data('role_name')
        child = child or event.get_data('child_name')
        if context == child:
            return _('inheritance addition from parent role "%s"') % parent
        elif context == parent:
            return _('inheritance addition to child role "%s"') % child
        else:
            return _('inheritance addition from parent role "{parent}" to child role "{child}"').format(
                parent=parent, child=child
            )


class ManagerRoleInheritanceRemoval(ManagerRoleInheritanceAddition):
    name = 'manager.role.inheritance.removal'
    label = _('role inheritance removal')

    @classmethod
    def get_message(cls, event, context):
        parent, child = event.get_typed_references(Role, Role)
        parent = parent or event.get_data('role_name')
        child = child or event.get_data('child_name')
        if context == child:
            return _('inheritance removal from parent role "%s"') % parent
        elif context == parent:
            return _('inheritance removal to child role "%s"') % child
        else:
            return _('inheritance removal from parent role "{parent}" to child role "{child}"').format(
                parent=parent, child=child
            )


class ManagerRoleAdministratorRoleAddition(RoleEventsMixin):
    name = 'manager.role.administrator.role.addition'
    label = _('role administrator role addition')

    @classmethod
    def record(cls, user, session, role, admin_role):
        data = {
            'admin_role_name': str(admin_role),
            'admin_role_uuid': admin_role.uuid,
        }
        super().record(user=user, session=session, role=role, references=[admin_role], data=data)

    @classmethod
    def get_message(cls, event, context):
        role, admin_role = event.get_typed_references(Role, Role)
        role = role or event.get_data('role_name')
        admin_role = admin_role or event.get('admin_role_name')
        if context == role:
            return _('addition of role "%s" as administrator') % admin_role
        elif context == admin_role:
            return _('addition as administrator of role "%s"') % role
        else:
            return _('addition of role "{admin_role}" as administrator of role "{role}"').format(
                admin_role=admin_role, role=role
            )


class ManagerRoleAdministratorRoleRemoval(ManagerRoleAdministratorRoleAddition):
    name = 'manager.role.administrator.role.removal'
    label = _('role administrator role removal')

    @classmethod
    def get_message(cls, event, context):
        role, admin_role = event.get_typed_references(Role, Role)
        role = role or event.get_data('role_name')
        admin_role = admin_role or event.get('admin_role_name')
        if context == role:
            return _('removal of role "%s" as administrator') % admin_role
        elif context == admin_role:
            return _('removal as administrator of role "%s"') % role
        else:
            return _('removal of role "{admin_role}" as administrator of role "{role}"').format(
                admin_role=admin_role, role=role
            )


class ManagerRoleAdministratorUserAddition(RoleEventsMixin):
    name = 'manager.role.administrator.user.addition'
    label = _('role administrator user addition')

    @classmethod
    def record(cls, user, session, role, admin_user):
        data = {
            'admin_user_name': user_to_str(admin_user),
            'admin_user_uuid': admin_user.uuid,
        }
        super().record(user=user, session=session, role=role, references=[admin_user], data=data)

    @classmethod
    def get_message(cls, event, context):
        role, admin_user = event.get_typed_references(Role, (DeletedUser, User))
        role = role or event.get_data('role_name')
        admin_user = admin_user or event.get_data('admin_user_name')
        if context == role:
            return _('addition of user "%s" as administrator') % admin_user
        elif context == admin_user:
            return _('addition as administrator of role "%s"') % role
        else:
            return _('addition of user "{admin_user}" as administrator of role "{role}"').format(
                admin_user=admin_user, role=role
            )


class ManagerRoleAdministratorUserRemoval(ManagerRoleAdministratorUserAddition):
    name = 'manager.role.administrator.user.removal'
    label = _('role administrator user removal')

    @classmethod
    def get_message(cls, event, context):
        role, admin_user = event.get_typed_references(Role, (DeletedUser, User))
        role = role or event.get_data('role_name')
        admin_user = admin_user or event.get_data('admin_user_name')
        if context == role:
            return _('removal of user "%s" as administrator') % admin_user
        elif context == admin_user:
            return _('removal as administrator of role "%s"') % role
        else:
            return _('removal of user "{admin_user}" as administrator of role "{role}"').format(
                admin_user=admin_user, role=role
            )
