# 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/>.

import datetime
from unittest import mock

import pytest
from django.contrib.sessions.models import Session
from django.utils.timezone import make_aware

from authentic2.a2_rbac.models import Role
from authentic2.a2_rbac.utils import get_default_ou
from authentic2.apps.journal.models import Event, _registry
from authentic2.custom_user.models import User
from authentic2.journal import journal
from authentic2.models import Service

from .utils import login, logout, text_content


def test_journal_authorization(app, db, simple_user, admin):
    response = login(app, simple_user)
    app.get('/manage/journal/', status=403)

    logout(app)
    response = login(app, admin, path='/manage/')
    assert 'Journal' in response
    app.get('/manage/journal/', status=200)


@pytest.fixture(autouse=True)
def events(db, freezer):
    session1 = Session(session_key="1234")
    session2 = Session(session_key="abcd")

    ou = get_default_ou()
    user = User.objects.create(
        username="user", email="user@example.com", ou=ou, uuid="1" * 32, first_name='Johnny', last_name='doe'
    )
    agent = User.objects.create(username="agent", email="agent@example.com", ou=ou, uuid="2" * 32)
    role_user = Role.objects.create(name="role1", ou=ou)
    role_agent = Role.objects.create(name="role2", ou=ou)
    service = Service.objects.create(name="service")

    class EventFactory:
        date = make_aware(datetime.datetime(2020, 1, 1))

        def __call__(self, name, **kwargs):
            freezer.move_to(self.date)
            journal.record(name, **kwargs)
            assert Event.objects.latest("timestamp").type.name == name
            self.date += datetime.timedelta(hours=1)

    make = EventFactory()
    make("user.registration.request", email=user.email)
    make(
        "user.registration",
        user=user,
        session=session1,
        service=service,
        how="franceconnect",
    )
    make("user.logout", user=user, session=session1)

    make("user.login.failure", username="user")
    make("user.login.failure", username="agent")
    make("user.login", user=user, session=session1, how="password")
    make("user.password.change", user=user, session=session1)
    edit_profile_form = mock.Mock(spec=["instance", "initial", "changed_data", "cleaned_data"])
    edit_profile_form.initial = {'email': "user@example.com", 'first_name': "John"}
    edit_profile_form.changed_data = ["first_name"]
    edit_profile_form.cleaned_data = {'first_name': "Jane"}
    make("user.profile.edit", user=user, session=session1, form=edit_profile_form)
    make("user.service.sso.authorization", user=user, session=session1, service=service)
    make("user.service.sso", user=user, session=session1, service=service, how="password")
    make("user.service.sso.unauthorization", user=user, session=session1, service=service)
    make("user.deletion", user=user, session=session1, service=service)

    make("user.password.reset.request", email="USER@example.com", user=user)
    make("user.password.reset.failure", email="USER@example.com")
    make("user.password.reset", user=user)

    make("user.login", user=agent, session=session2, how="saml")

    create_form = mock.Mock(spec=["instance"])
    create_form.instance = user
    make("manager.user.creation", user=agent, session=session2, form=create_form)

    edit_form = mock.Mock(spec=["instance", "initial", "changed_data", "cleaned_data"])
    edit_form.instance = user
    edit_form.initial = {'email': "user@example.com", 'first_name': "John"}
    edit_form.changed_data = ["first_name"]
    edit_form.cleaned_data = {'first_name': "Jane"}
    make("manager.user.profile.edit", user=agent, session=session2, form=edit_form)

    change_email_form = mock.Mock(spec=["instance", "cleaned_data"])
    change_email_form.instance = user
    change_email_form.cleaned_data = {'new_email': "jane@example.com"}
    make(
        "manager.user.email.change.request",
        user=agent,
        session=session2,
        form=change_email_form,
    )

    password_change_form = mock.Mock(spec=["instance", "cleaned_data"])
    password_change_form.instance = user
    password_change_form.cleaned_data = {'generate_password': False, 'send_mail': False}
    make(
        "manager.user.password.change",
        user=agent,
        session=session2,
        form=password_change_form,
    )

    password_change_form.cleaned_data["send_mail"] = True
    make(
        "manager.user.password.change",
        user=agent,
        session=session2,
        form=password_change_form,
    )

    make(
        "manager.user.password.reset.request",
        user=agent,
        session=session2,
        target_user=user,
    )

    make(
        "manager.user.password.change.force",
        user=agent,
        session=session2,
        target_user=user,
    )
    make(
        "manager.user.password.change.unforce",
        user=agent,
        session=session2,
        target_user=user,
    )

    make("manager.user.activation", user=agent, session=session2, target_user=user)
    make("manager.user.deactivation", user=agent, session=session2, target_user=user)
    make("manager.user.deletion", user=agent, session=session2, target_user=user)
    make(
        "manager.user.sso.authorization.deletion",
        user=agent,
        session=session2,
        service=service,
        target_user=user,
    )

    make("manager.role.creation", user=agent, session=session2, role=role_user)
    role_edit_form = mock.Mock(spec=["instance", "initial", "changed_data", "cleaned_data"])
    role_edit_form.instance = role_user
    role_edit_form.initial = {'name': role_user.name}
    role_edit_form.changed_data = ["name"]
    role_edit_form.cleaned_data = {'name': "changed role name"}
    make(
        "manager.role.edit",
        user=agent,
        session=session2,
        role=role_user,
        form=role_edit_form,
    )
    make("manager.role.deletion", user=agent, session=session2, role=role_user)
    make(
        "manager.role.membership.grant",
        user=agent,
        session=session2,
        role=role_user,
        member=user,
    )
    make(
        "manager.role.membership.removal",
        user=agent,
        session=session2,
        role=role_user,
        member=user,
    )

    make(
        "manager.role.inheritance.addition",
        user=agent,
        session=session2,
        parent=role_agent,
        child=role_user,
    )
    make(
        "manager.role.inheritance.removal",
        user=agent,
        session=session2,
        parent=role_agent,
        child=role_user,
    )

    make(
        "manager.role.administrator.role.addition",
        user=agent,
        session=session2,
        role=role_user,
        admin_role=role_agent,
    )
    make(
        "manager.role.administrator.role.removal",
        user=agent,
        session=session2,
        role=role_user,
        admin_role=role_agent,
    )

    make(
        "manager.role.administrator.user.addition",
        user=agent,
        session=session2,
        role=role_user,
        admin_user=user,
    )
    make(
        "manager.role.administrator.user.removal",
        user=agent,
        session=session2,
        role=role_user,
        admin_user=user,
    )
    make(
        'user.email.change.request',
        user=user,
        session=session1,
        new_email='new@example.com',
    )
    make(
        'user.email.change',
        user=user,
        session=session1,
        old_email='old@example.com',
        new_email='new@example.com',
    )
    make(
        'manager.user.deactivation',
        target_user=user,
        reason='ldap-not-present',
    )
    make(
        'manager.user.deactivation',
        target_user=user,
        reason='ldap-old-source',
    )
    make(
        'manager.user.activation',
        target_user=user,
        reason='ldap-reactivation',
    )

    # verify we created at least one event for each type
    assert set(Event.objects.values_list("type__name", flat=True)) == set(_registry)

    return locals()


def extract_journal(response):
    rows = []
    seen_event_ids = set()
    while True:
        for tr in response.pyquery("tr[data-event-type]"):
            # page can overlap when they contain less than 20 items (to prevent orphan rows)
            event_id = tr.attrib["data-event-id"]
            if event_id not in seen_event_ids:
                rows.append(response.pyquery(tr))
                seen_event_ids.add(event_id)
        if "Previous page" not in response:
            break
        response = response.click("Previous page", index=0)

    rows.reverse()
    content = [
        {
            'timestamp': text_content(row.find(".journal-list--timestamp-column")[0]).strip(),
            'type': row[0].attrib["data-event-type"],
            'user': text_content(row.find(".journal-list--user-column")[0]).strip(),
            'message': text_content(row.find(".journal-list--message-column")[0]),
        }
        for row in rows
    ]
    return content


def test_global_journal(app, superuser, events):
    response = login(app, user=superuser, path="/manage/")

    # remove event about admin login
    Event.objects.filter(user=superuser).delete()

    response = response.click("Journal")

    content = extract_journal(response)

    assert content == [
        {
            'message': 'registration request with email "user@example.com"',
            'timestamp': 'Jan. 1, 2020, midnight',
            'type': 'user.registration.request',
            'user': '-',
        },
        {
            'message': 'registration using franceconnect',
            'timestamp': 'Jan. 1, 2020, 1 a.m.',
            'type': 'user.registration',
            'user': 'Johnny doe',
        },
        {
            'message': 'logout',
            'timestamp': 'Jan. 1, 2020, 2 a.m.',
            'type': 'user.logout',
            'user': 'Johnny doe',
        },
        {
            'message': 'login failure with username "user"',
            'timestamp': 'Jan. 1, 2020, 3 a.m.',
            'type': 'user.login.failure',
            'user': '-',
        },
        {
            'message': 'login failure with username "agent"',
            'timestamp': 'Jan. 1, 2020, 4 a.m.',
            'type': 'user.login.failure',
            'user': '-',
        },
        {
            'message': 'login using password',
            'timestamp': 'Jan. 1, 2020, 5 a.m.',
            'type': 'user.login',
            'user': 'Johnny doe',
        },
        {
            'message': 'password change',
            'timestamp': 'Jan. 1, 2020, 6 a.m.',
            'type': 'user.password.change',
            'user': 'Johnny doe',
        },
        {
            'message': 'profile edit (first name)',
            'timestamp': 'Jan. 1, 2020, 7 a.m.',
            'type': 'user.profile.edit',
            'user': 'Johnny doe',
        },
        {
            'message': 'authorization of single sign on with "service"',
            'timestamp': 'Jan. 1, 2020, 8 a.m.',
            'type': 'user.service.sso.authorization',
            'user': 'Johnny doe',
        },
        {
            'message': 'service single sign on with "service"',
            'timestamp': 'Jan. 1, 2020, 9 a.m.',
            'type': 'user.service.sso',
            'user': 'Johnny doe',
        },
        {
            'message': 'unauthorization of single sign on with "service"',
            'timestamp': 'Jan. 1, 2020, 10 a.m.',
            'type': 'user.service.sso.unauthorization',
            'user': 'Johnny doe',
        },
        {
            'message': 'user deletion',
            'timestamp': 'Jan. 1, 2020, 11 a.m.',
            'type': 'user.deletion',
            'user': 'Johnny doe',
        },
        {
            'message': 'password reset request with email "user@example.com"',
            'timestamp': 'Jan. 1, 2020, noon',
            'type': 'user.password.reset.request',
            'user': 'Johnny doe',
        },
        {
            'message': 'password reset failure with email "USER@example.com"',
            'timestamp': 'Jan. 1, 2020, 1 p.m.',
            'type': 'user.password.reset.failure',
            'user': '-',
        },
        {
            'message': 'password reset',
            'timestamp': 'Jan. 1, 2020, 2 p.m.',
            'type': 'user.password.reset',
            'user': 'Johnny doe',
        },
        {
            'message': 'login using SAML',
            'timestamp': 'Jan. 1, 2020, 3 p.m.',
            'type': 'user.login',
            'user': 'agent',
        },
        {
            'message': 'creation of user "Johnny doe"',
            'timestamp': 'Jan. 1, 2020, 4 p.m.',
            'type': 'manager.user.creation',
            'user': 'agent',
        },
        {
            'message': 'edit of user "Johnny doe" (first name)',
            'timestamp': 'Jan. 1, 2020, 5 p.m.',
            'type': 'manager.user.profile.edit',
            'user': 'agent',
        },
        {
            'message': 'email change of user "Johnny doe" for email address "jane@example.com"',
            'timestamp': 'Jan. 1, 2020, 6 p.m.',
            'type': 'manager.user.email.change.request',
            'user': 'agent',
        },
        {
            'message': 'password change of user "Johnny doe"',
            'timestamp': 'Jan. 1, 2020, 7 p.m.',
            'type': 'manager.user.password.change',
            'user': 'agent',
        },
        {
            'message': 'password change of user "Johnny doe" and notification by mail',
            'timestamp': 'Jan. 1, 2020, 8 p.m.',
            'type': 'manager.user.password.change',
            'user': 'agent',
        },
        {
            'message': 'password reset request of "Johnny doe" sent to "user@example.com"',
            'timestamp': 'Jan. 1, 2020, 9 p.m.',
            'type': 'manager.user.password.reset.request',
            'user': 'agent',
        },
        {
            'message': 'mandatory password change at next login set for user "Johnny doe"',
            'timestamp': 'Jan. 1, 2020, 10 p.m.',
            'type': 'manager.user.password.change.force',
            'user': 'agent',
        },
        {
            'message': 'mandatory password change at next login unset for user "Johnny doe"',
            'timestamp': 'Jan. 1, 2020, 11 p.m.',
            'type': 'manager.user.password.change.unforce',
            'user': 'agent',
        },
        {
            'message': 'activation of user "Johnny doe"',
            'timestamp': 'Jan. 2, 2020, midnight',
            'type': 'manager.user.activation',
            'user': 'agent',
        },
        {
            'message': 'deactivation of user "Johnny doe"',
            'timestamp': 'Jan. 2, 2020, 1 a.m.',
            'type': 'manager.user.deactivation',
            'user': 'agent',
        },
        {
            'message': 'deletion of user "Johnny doe"',
            'timestamp': 'Jan. 2, 2020, 2 a.m.',
            'type': 'manager.user.deletion',
            'user': 'agent',
        },
        {
            'message': 'deletion of authorization of single sign on with "service" of ' 'user "Johnny doe"',
            'timestamp': 'Jan. 2, 2020, 3 a.m.',
            'type': 'manager.user.sso.authorization.deletion',
            'user': 'agent',
        },
        {
            'message': 'creation of role "role1"',
            'timestamp': 'Jan. 2, 2020, 4 a.m.',
            'type': 'manager.role.creation',
            'user': 'agent',
        },
        {
            'message': 'edit of role "role1" (name)',
            'timestamp': 'Jan. 2, 2020, 5 a.m.',
            'type': 'manager.role.edit',
            'user': 'agent',
        },
        {
            'message': 'deletion of role "role1"',
            'timestamp': 'Jan. 2, 2020, 6 a.m.',
            'type': 'manager.role.deletion',
            'user': 'agent',
        },
        {
            'message': 'membership grant to user "user (111111)" in role "role1"',
            'timestamp': 'Jan. 2, 2020, 7 a.m.',
            'type': 'manager.role.membership.grant',
            'user': 'agent',
        },
        {
            'message': 'membership removal of user "user (111111)" from role "role1"',
            'timestamp': 'Jan. 2, 2020, 8 a.m.',
            'type': 'manager.role.membership.removal',
            'user': 'agent',
        },
        {
            'message': 'inheritance addition from parent role "role2" to child role ' '"role1"',
            'timestamp': 'Jan. 2, 2020, 9 a.m.',
            'type': 'manager.role.inheritance.addition',
            'user': 'agent',
        },
        {
            'message': 'inheritance removal from parent role "role2" to child role ' '"role1"',
            'timestamp': 'Jan. 2, 2020, 10 a.m.',
            'type': 'manager.role.inheritance.removal',
            'user': 'agent',
        },
        {
            'message': 'addition of role "role2" as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, 11 a.m.',
            'type': 'manager.role.administrator.role.addition',
            'user': 'agent',
        },
        {
            'message': 'removal of role "role2" as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, noon',
            'type': 'manager.role.administrator.role.removal',
            'user': 'agent',
        },
        {
            'message': 'addition of user "user (111111)" as administrator of role ' '"role1"',
            'timestamp': 'Jan. 2, 2020, 1 p.m.',
            'type': 'manager.role.administrator.user.addition',
            'user': 'agent',
        },
        {
            'message': 'removal of user "user (111111)" as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, 2 p.m.',
            'type': 'manager.role.administrator.user.removal',
            'user': 'agent',
        },
        {
            'message': 'email change request for email address "new@example.com"',
            'timestamp': 'Jan. 2, 2020, 3 p.m.',
            'type': 'user.email.change.request',
            'user': 'Johnny doe',
        },
        {
            'message': 'email address changed from "old@example.com" to "new@example.com"',
            'timestamp': 'Jan. 2, 2020, 4 p.m.',
            'type': 'user.email.change',
            'user': 'Johnny doe',
        },
        {
            'timestamp': 'Jan. 2, 2020, 5 p.m.',
            'type': 'manager.user.deactivation',
            'user': '-',
            'message': 'automatic deactivation of user "Johnny doe" because the associated LDAP account does not exist anymore',
        },
        {
            'timestamp': 'Jan. 2, 2020, 6 p.m.',
            'type': 'manager.user.deactivation',
            'user': '-',
            'message': 'automatic deactivation of user "Johnny doe" because the associated LDAP source has been deleted',
        },
        {
            'message': 'automatic activation of user "Johnny doe" because the associated LDAP account reappeared',
            'timestamp': 'Jan. 2, 2020, 7 p.m.',
            'type': 'manager.user.activation',
            'user': '-',
        },
    ]


def test_user_journal(app, superuser, events):
    response = login(app, user=superuser, path="/manage/")
    user = User.objects.get(username="user")

    response = app.get("/manage/users/%s/journal/" % user.id)
    content = extract_journal(response)

    assert content == [
        {
            'message': 'registration using franceconnect',
            'timestamp': 'Jan. 1, 2020, 1 a.m.',
            'type': 'user.registration',
            'user': 'Johnny doe',
        },
        {
            'message': 'logout',
            'timestamp': 'Jan. 1, 2020, 2 a.m.',
            'type': 'user.logout',
            'user': 'Johnny doe',
        },
        {
            'message': 'login using password',
            'timestamp': 'Jan. 1, 2020, 5 a.m.',
            'type': 'user.login',
            'user': 'Johnny doe',
        },
        {
            'message': 'password change',
            'timestamp': 'Jan. 1, 2020, 6 a.m.',
            'type': 'user.password.change',
            'user': 'Johnny doe',
        },
        {
            'message': 'profile edit (first name)',
            'timestamp': 'Jan. 1, 2020, 7 a.m.',
            'type': 'user.profile.edit',
            'user': 'Johnny doe',
        },
        {
            'message': 'authorization of single sign on with "service"',
            'timestamp': 'Jan. 1, 2020, 8 a.m.',
            'type': 'user.service.sso.authorization',
            'user': 'Johnny doe',
        },
        {
            'message': 'service single sign on with "service"',
            'timestamp': 'Jan. 1, 2020, 9 a.m.',
            'type': 'user.service.sso',
            'user': 'Johnny doe',
        },
        {
            'message': 'unauthorization of single sign on with "service"',
            'timestamp': 'Jan. 1, 2020, 10 a.m.',
            'type': 'user.service.sso.unauthorization',
            'user': 'Johnny doe',
        },
        {
            'message': 'user deletion',
            'timestamp': 'Jan. 1, 2020, 11 a.m.',
            'type': 'user.deletion',
            'user': 'Johnny doe',
        },
        {
            'message': 'password reset request with email "user@example.com"',
            'timestamp': 'Jan. 1, 2020, noon',
            'type': 'user.password.reset.request',
            'user': 'Johnny doe',
        },
        {
            'message': 'password reset',
            'timestamp': 'Jan. 1, 2020, 2 p.m.',
            'type': 'user.password.reset',
            'user': 'Johnny doe',
        },
        {
            'message': 'creation by administrator',
            'timestamp': 'Jan. 1, 2020, 4 p.m.',
            'type': 'manager.user.creation',
            'user': 'agent',
        },
        {
            'message': 'edit by administrator (first name)',
            'timestamp': 'Jan. 1, 2020, 5 p.m.',
            'type': 'manager.user.profile.edit',
            'user': 'agent',
        },
        {
            'message': 'email change for email address "jane@example.com" requested by administrator',
            'timestamp': 'Jan. 1, 2020, 6 p.m.',
            'type': 'manager.user.email.change.request',
            'user': 'agent',
        },
        {
            'message': 'password change by administrator',
            'timestamp': 'Jan. 1, 2020, 7 p.m.',
            'type': 'manager.user.password.change',
            'user': 'agent',
        },
        {
            'message': 'password change by administrator and notification by mail',
            'timestamp': 'Jan. 1, 2020, 8 p.m.',
            'type': 'manager.user.password.change',
            'user': 'agent',
        },
        {
            'message': "password reset request by administrator sent to " '"user@example.com"',
            'timestamp': 'Jan. 1, 2020, 9 p.m.',
            'type': 'manager.user.password.reset.request',
            'user': 'agent',
        },
        {
            'message': 'mandatory password change at next login set by administrator',
            'timestamp': 'Jan. 1, 2020, 10 p.m.',
            'type': 'manager.user.password.change.force',
            'user': 'agent',
        },
        {
            'message': 'mandatory password change at next login unset by administrator',
            'timestamp': 'Jan. 1, 2020, 11 p.m.',
            'type': 'manager.user.password.change.unforce',
            'user': 'agent',
        },
        {
            'message': 'activation by administrator',
            'timestamp': 'Jan. 2, 2020, midnight',
            'type': 'manager.user.activation',
            'user': 'agent',
        },
        {
            'message': 'deactivation by administrator',
            'timestamp': 'Jan. 2, 2020, 1 a.m.',
            'type': 'manager.user.deactivation',
            'user': 'agent',
        },
        {
            'message': 'deletion by administrator',
            'timestamp': 'Jan. 2, 2020, 2 a.m.',
            'type': 'manager.user.deletion',
            'user': 'agent',
        },
        {
            'message': 'deletion of authorization of single sign on with "service" by ' "administrator",
            'timestamp': 'Jan. 2, 2020, 3 a.m.',
            'type': 'manager.user.sso.authorization.deletion',
            'user': 'agent',
        },
        {
            'message': 'membership grant in role "role1"',
            'timestamp': 'Jan. 2, 2020, 7 a.m.',
            'type': 'manager.role.membership.grant',
            'user': 'agent',
        },
        {
            'message': 'membership removal from role "role1"',
            'timestamp': 'Jan. 2, 2020, 8 a.m.',
            'type': 'manager.role.membership.removal',
            'user': 'agent',
        },
        {
            'message': 'addition as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, 1 p.m.',
            'type': 'manager.role.administrator.user.addition',
            'user': 'agent',
        },
        {
            'message': 'removal as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, 2 p.m.',
            'type': 'manager.role.administrator.user.removal',
            'user': 'agent',
        },
        {
            'message': 'email change request for email address "new@example.com"',
            'timestamp': 'Jan. 2, 2020, 3 p.m.',
            'type': 'user.email.change.request',
            'user': 'Johnny doe',
        },
        {
            'message': 'email address changed from "old@example.com" to "new@example.com"',
            'timestamp': 'Jan. 2, 2020, 4 p.m.',
            'type': 'user.email.change',
            'user': 'Johnny doe',
        },
        {
            'timestamp': 'Jan. 2, 2020, 5 p.m.',
            'type': 'manager.user.deactivation',
            'user': '-',
            'message': 'automatic deactivation because the associated LDAP account does not exist anymore',
        },
        {
            'timestamp': 'Jan. 2, 2020, 6 p.m.',
            'type': 'manager.user.deactivation',
            'user': '-',
            'message': 'automatic deactivation because the associated LDAP source has been deleted',
        },
        {
            'message': 'automatic activation because the associated LDAP account reappeared',
            'timestamp': 'Jan. 2, 2020, 7 p.m.',
            'type': 'manager.user.activation',
            'user': '-',
        },
    ]


def test_role_journal(app, superuser, events):
    response = login(app, user=superuser, path="/manage/")
    role1 = Role.objects.get(name="role1")
    role2 = Role.objects.get(name="role2")

    response = app.get("/manage/roles/%s/journal/" % role1.id)
    content = extract_journal(response)

    assert content == [
        {
            'message': 'creation',
            'timestamp': 'Jan. 2, 2020, 4 a.m.',
            'type': 'manager.role.creation',
            'user': 'agent',
        },
        {
            'message': 'edit (name)',
            'timestamp': 'Jan. 2, 2020, 5 a.m.',
            'type': 'manager.role.edit',
            'user': 'agent',
        },
        {
            'message': 'deletion',
            'timestamp': 'Jan. 2, 2020, 6 a.m.',
            'type': 'manager.role.deletion',
            'user': 'agent',
        },
        {
            'message': 'membership grant to user "user (111111)"',
            'timestamp': 'Jan. 2, 2020, 7 a.m.',
            'type': 'manager.role.membership.grant',
            'user': 'agent',
        },
        {
            'message': 'membership removal of user "user (111111)"',
            'timestamp': 'Jan. 2, 2020, 8 a.m.',
            'type': 'manager.role.membership.removal',
            'user': 'agent',
        },
        {
            'message': 'inheritance addition from parent role "role2"',
            'timestamp': 'Jan. 2, 2020, 9 a.m.',
            'type': 'manager.role.inheritance.addition',
            'user': 'agent',
        },
        {
            'message': 'inheritance removal from parent role "role2"',
            'timestamp': 'Jan. 2, 2020, 10 a.m.',
            'type': 'manager.role.inheritance.removal',
            'user': 'agent',
        },
        {
            'message': 'addition of role "role2" as administrator',
            'timestamp': 'Jan. 2, 2020, 11 a.m.',
            'type': 'manager.role.administrator.role.addition',
            'user': 'agent',
        },
        {
            'message': 'removal of role "role2" as administrator',
            'timestamp': 'Jan. 2, 2020, noon',
            'type': 'manager.role.administrator.role.removal',
            'user': 'agent',
        },
        {
            'message': 'addition of user "user (111111)" as administrator',
            'timestamp': 'Jan. 2, 2020, 1 p.m.',
            'type': 'manager.role.administrator.user.addition',
            'user': 'agent',
        },
        {
            'message': 'removal of user "user (111111)" as administrator',
            'timestamp': 'Jan. 2, 2020, 2 p.m.',
            'type': 'manager.role.administrator.user.removal',
            'user': 'agent',
        },
    ]

    response = app.get("/manage/roles/%s/journal/" % role2.id)
    content = extract_journal(response)

    assert content == [
        {
            'message': 'inheritance addition to child role "role1"',
            'timestamp': 'Jan. 2, 2020, 9 a.m.',
            'type': 'manager.role.inheritance.addition',
            'user': 'agent',
        },
        {
            'message': 'inheritance removal to child role "role1"',
            'timestamp': 'Jan. 2, 2020, 10 a.m.',
            'type': 'manager.role.inheritance.removal',
            'user': 'agent',
        },
        {
            'message': 'addition as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, 11 a.m.',
            'type': 'manager.role.administrator.role.addition',
            'user': 'agent',
        },
        {
            'message': 'removal as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, noon',
            'type': 'manager.role.administrator.role.removal',
            'user': 'agent',
        },
    ]


def test_roles_journal(app, superuser, events):
    response = login(app, user=superuser, path='/manage/')
    response = response.click('Role')
    response = response.click('Journal')

    content = extract_journal(response)

    assert content == [
        {
            'message': 'creation of role "role1"',
            'timestamp': 'Jan. 2, 2020, 4 a.m.',
            'type': 'manager.role.creation',
            'user': 'agent',
        },
        {
            'message': 'edit of role "role1" (name)',
            'timestamp': 'Jan. 2, 2020, 5 a.m.',
            'type': 'manager.role.edit',
            'user': 'agent',
        },
        {
            'message': 'deletion of role "role1"',
            'timestamp': 'Jan. 2, 2020, 6 a.m.',
            'type': 'manager.role.deletion',
            'user': 'agent',
        },
        {
            'message': 'membership grant to user "user (111111)" in role "role1"',
            'timestamp': 'Jan. 2, 2020, 7 a.m.',
            'type': 'manager.role.membership.grant',
            'user': 'agent',
        },
        {
            'message': 'membership removal of user "user (111111)" from role "role1"',
            'timestamp': 'Jan. 2, 2020, 8 a.m.',
            'type': 'manager.role.membership.removal',
            'user': 'agent',
        },
        {
            'message': 'inheritance addition from parent role "role2" to child role ' '"role1"',
            'timestamp': 'Jan. 2, 2020, 9 a.m.',
            'type': 'manager.role.inheritance.addition',
            'user': 'agent',
        },
        {
            'message': 'inheritance removal from parent role "role2" to child role ' '"role1"',
            'timestamp': 'Jan. 2, 2020, 10 a.m.',
            'type': 'manager.role.inheritance.removal',
            'user': 'agent',
        },
        {
            'message': 'addition of role "role2" as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, 11 a.m.',
            'type': 'manager.role.administrator.role.addition',
            'user': 'agent',
        },
        {
            'message': 'removal of role "role2" as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, noon',
            'type': 'manager.role.administrator.role.removal',
            'user': 'agent',
        },
        {
            'message': 'addition of user "user (111111)" as administrator of role ' '"role1"',
            'timestamp': 'Jan. 2, 2020, 1 p.m.',
            'type': 'manager.role.administrator.user.addition',
            'user': 'agent',
        },
        {
            'message': 'removal of user "user (111111)" as administrator of role "role1"',
            'timestamp': 'Jan. 2, 2020, 2 p.m.',
            'type': 'manager.role.administrator.user.removal',
            'user': 'agent',
        },
    ]


def test_date_navigation(app, superuser, events):
    response = login(app, user=superuser, path="/manage/journal/")
    response = response.click('2020')
    assert not response.context['form'].errors

    response = response.click('January')
    response = response.click('1')

    content = extract_journal(response)
    assert all(item['timestamp'].startswith('Jan. 1, 2020') for item in content)

    response = response.click('January 2020')
    response = response.click('2020')
    response = response.click('Journal - All dates')


def test_search(app, superuser, events):
    response = login(app, user=superuser, path="/manage/journal/")
    response.form.set('search', 'event:registration')
    response = response.form.submit()
    assert len(response.pyquery('tbody tr')) == 1

    response.form.set('search', 'username:agent event:login')
    response = response.form.submit()
    assert len(response.pyquery('tbody tr')) == 1
    assert all(
        'agent' == text_content(node) for node in response.pyquery('tbody tr td.journal-list--user-column')
    )

    response.form.set('search', 'uuid:%s event:reset' % events['user'].uuid)
    response = response.form.submit()
    assert len(response.pyquery('tbody tr')) == 1

    response.form.set('search', 'session:1234')
    response = response.form.submit()
    assert len(response.pyquery('tbody tr')) == 11
    assert all(
        text_content(node) == 'Johnny doe'
        for node in response.pyquery('tbody tr td.journal-list--user-column')
    )

    response.form.set('search', 'email:jane@example.com')
    response = response.form.submit()
    assert (
        text_content(response.pyquery('tbody tr td.journal-list--message-column')[12]).strip()
        == 'email change of user "Johnny doe" for email address "jane@example.com"'
    )

    response.form.set('search', 'jane@example.com')
    response = response.form.submit()
    assert (
        text_content(response.pyquery('tbody tr td.journal-list--message-column')[12]).strip()
        == 'email change of user "Johnny doe" for email address "jane@example.com"'
    )

    response.form.set('search', 'johny doe event:login')
    response = response.form.submit()
    pq = response.pyquery

    assert [
        list(map(text_content, p))
        for p in zip(pq('tbody td.journal-list--user-column'), pq('tbody td.journal-list--message-column'))
    ] == [['Johnny doe', 'login using password']]

    Event.objects.filter(type__name='manager.user.creation').update(api=True)
    response.form.set('search', 'api:true')
    response = response.form.submit()
    assert (
        text_content(response.pyquery('tbody tr td.journal-list--message-column')[0]).strip()
        == 'creation of user "Johnny doe"'
    )
    assert text_content(response.pyquery('tbody tr td.journal-list--session-column')[0]).strip() == 'API'

    response.form.set('search', '')
    response.form['event_type'].select(text='Profile changes')
    response = response.form.submit()

    table_content = [text_content(p) for p in response.pyquery('tbody td.journal-list--message-column')]
    assert table_content == [
        'automatic activation of user "Johnny doe" because the associated LDAP account reappeared',
        'automatic deactivation of user "Johnny doe" because the associated LDAP source has been deleted',
        'automatic deactivation of user "Johnny doe" because the associated LDAP account does not exist anymore',
        'deactivation of user "Johnny doe"',
        'activation of user "Johnny doe"',
        'mandatory password change at next login unset for user "Johnny doe"',
        'mandatory password change at next login set for user "Johnny doe"',
        'password reset request of "Johnny doe" sent to "user@example.com"',
        'password change of user "Johnny doe" and notification by mail',
        'password change of user "Johnny doe"',
        'email change of user "Johnny doe" for email address "jane@example.com"',
        'edit of user "Johnny doe" (first name)',
        'password reset',
        'password reset failure with email "USER@example.com"',
        'password reset request with email "user@example.com"',
        'user deletion',
        'profile edit (first name)',
        'password change',
    ]

    response.form['event_type'].select(text='Role management')
    response = response.form.submit()

    table_content = [text_content(p) for p in response.pyquery('tbody td.journal-list--message-column')]
    assert table_content[:3] == [
        'removal of user "user (111111)" as administrator of role "role1"',
        'addition of user "user (111111)" as administrator of role "role1"',
        'removal of role "role2" as administrator of role "role1"',
    ]


def test_delete_user(app, superuser, events):
    old_user_id = events['user'].id
    events['user'].delete()
    response = login(app, user=superuser, path="/manage/journal/")
    response.form.set('search', events['user'].email)
    response = response.form.submit()
    assert len(response.pyquery('tbody tr')) == 20
    assert 'deletion of user &quot;deleted user (#%s, user@example.com)&quot;' % old_user_id in str(response)
