import datetime
from unittest import mock

import pytest
import time_machine
from flask import g

from canaille.app import models
from canaille.app.session import UserSession


def test_index(testclient, user, backend):
    res = testclient.get("/", status=302)
    assert res.location == "/login"

    g.session = UserSession(user=user)
    res = testclient.get("/", status=302)
    assert res.location == "/profile/user"

    testclient.app.config["CANAILLE"]["ACL"]["DEFAULT"]["PERMISSIONS"] = []
    backend.reload(g.session.user)
    res = testclient.get("/", status=302)
    assert res.location == "/about"


def test_user_deleted_in_session(testclient, backend):
    user = models.User(
        formatted_name="Jake Doe",
        family_name="Jake",
        user_name="jake",
        emails=["jake@doe.test"],
        password="correct horse battery staple",
    )
    backend.save(user)

    with testclient.session_transaction() as session:
        session["sessions"] = [
            UserSession(
                user=user,
                last_login_datetime=datetime.datetime.now(datetime.timezone.utc),
            ).serialize()
        ]

    testclient.get("/profile/jake", status=200)

    backend.delete(user)
    del g.session

    testclient.get("/profile/jake", status=404)
    with testclient.session_transaction() as session:
        assert not session.get("sessions")


def test_impersonate(testclient, logged_admin, user):
    res = testclient.get("/", status=302).follow(status=200).click("Account settings")
    assert "admin" == res.form["user_name"].value

    res = (
        testclient.get("/impersonate/user", status=302)
        .follow(status=302)
        .follow(status=200)
        .click("Account settings")
    )
    assert "user" == res.form["user_name"].value

    testclient.get("/logout", status=302).follow(status=302).follow(status=200)

    res = testclient.get("/", status=302).follow(status=200).click("Account settings")
    assert "admin" == res.form["user_name"].value


def test_admin_self_deletion(testclient, backend):
    admin = models.User(
        formatted_name="Temp admin",
        family_name="admin",
        user_name="temp",
        emails=["temp@temp.test"],
        password="admin",
    )
    backend.save(admin)
    with testclient.session_transaction() as sess:
        sess["sessions"] = [
            UserSession(
                user=admin,
                last_login_datetime=datetime.datetime.now(datetime.timezone.utc),
            ).serialize()
        ]

    res = testclient.get("/profile/temp/settings")
    res = res.form.submit(name="action", value="confirm-delete")
    res = (
        res.form.submit(name="action", value="delete", status=302)
        .follow(status=302)
        .follow(status=200)
    )

    assert backend.get(models.User, user_name="temp") is None

    with testclient.session_transaction() as sess:
        assert not sess.get("sessions")


def test_user_self_deletion(testclient, backend):
    user = models.User(
        formatted_name="Temp user",
        family_name="user",
        user_name="temp",
        emails=["temp@temp.test"],
        password="correct horse battery staple",
    )
    backend.save(user)
    with testclient.session_transaction() as sess:
        sess["sessions"] = [
            UserSession(
                user=user,
                last_login_datetime=datetime.datetime.now(datetime.timezone.utc),
            ).serialize()
        ]

    testclient.app.config["CANAILLE"]["ACL"]["DEFAULT"]["PERMISSIONS"] = ["edit_self"]
    res = testclient.get("/profile/temp/settings")
    res.mustcontain(no="Delete my account")

    testclient.app.config["CANAILLE"]["ACL"]["DEFAULT"]["PERMISSIONS"] = [
        "edit_self",
        "delete_account",
    ]
    # Simulate an app restart
    backend.reload(user)

    res = testclient.get("/profile/temp/settings")
    res.mustcontain("Delete my account")
    res = res.form.submit(name="action", value="confirm-delete")
    res = (
        res.form.submit(name="action", value="delete", status=302)
        .follow(status=302)
        .follow(status=200)
    )

    assert backend.get(models.User, user_name="temp") is None

    with testclient.session_transaction() as sess:
        assert not sess.get("sessions")

    testclient.app.config["CANAILLE"]["ACL"]["DEFAULT"]["PERMISSIONS"] = []


def test_account_locking(user, backend):
    assert not user.locked
    assert not user.lock_date
    assert backend.check_user_password(user, "correct horse battery staple") == (
        True,
        None,
    )

    user.lock_date = datetime.datetime.now(datetime.timezone.utc)
    assert user.locked
    backend.save(user)
    assert user.locked
    assert backend.get(models.User, id=user.id).locked
    assert backend.check_user_password(user, "correct horse battery staple") == (
        False,
        "Your account has been locked.",
    )

    user.lock_date = None
    backend.save(user)
    assert not user.locked
    assert not backend.get(models.User, id=user.id).locked
    assert backend.check_user_password(user, "correct horse battery staple") == (
        True,
        None,
    )


def test_account_locking_past_date(user, backend):
    assert not user.locked
    assert not user.lock_date
    assert backend.check_user_password(user, "correct horse battery staple") == (
        True,
        None,
    )

    user.lock_date = datetime.datetime.now(datetime.timezone.utc).replace(
        microsecond=0
    ) - datetime.timedelta(days=30)
    backend.save(user)
    assert user.locked
    assert backend.get(models.User, id=user.id).locked
    assert backend.check_user_password(user, "correct horse battery staple") == (
        False,
        "Your account has been locked.",
    )


def test_account_locking_future_date(user, backend):
    assert not user.locked
    assert not user.lock_date
    assert backend.check_user_password(user, "correct horse battery staple") == (
        True,
        None,
    )

    user.lock_date = datetime.datetime.now(datetime.timezone.utc).replace(
        microsecond=0
    ) + datetime.timedelta(days=365 * 4)
    backend.save(user)
    assert not user.locked
    assert not backend.get(models.User, id=user.id).locked
    assert backend.check_user_password(user, "correct horse battery staple") == (
        True,
        None,
    )


def test_account_locked_during_session(testclient, logged_user, backend):
    logged_user.lock_date = datetime.datetime.now(datetime.timezone.utc)
    backend.save(logged_user)
    testclient.get("/profile/user/settings", status=403)


def test_expired_password_redirection_and_register_new_password_for_memory_and_sql(
    testclient,
    logged_user,
    user,
    backend,
    admin,
):
    """time_machine does not work with ldap."""
    if "ldap" in backend.__class__.__module__:
        pytest.skip()

    testclient.app.config["WTF_CSRF_ENABLED"] = False
    backend.reload(logged_user)
    res = testclient.get("/profile/user/settings", status=200)
    res.form["password1"] = "123456789"
    res.form["password2"] = "123456789"

    with time_machine.travel("2020-01-01 01:00:00+00:00", tick=False) as traveller:
        res = res.form.submit(name="action", value="edit-settings")

        testclient.app.config["CANAILLE"]["PASSWORD_LIFETIME"] = "P5D"

        traveller.shift(datetime.timedelta(days=5, minutes=1))

        backend.reload(g.session.user)

        res = testclient.get("/profile/user/settings")

        testclient.get("/reset/admin", status=403)

        assert (
            "info",
            "Your password has expired, please choose a new password.",
        ) in res.flashes
        assert res.location == "/reset/user"

        backend.reload(logged_user)
        res = testclient.get("/reset/user")

        res.form["password"] = "foobarbaz"
        res.form["confirmation"] = "foobarbaz"
        res = res.form.submit()
        assert ("success", "Your password has been updated successfully") in res.flashes


@mock.patch("canaille.core.models.User.has_expired_password")
def test_expired_password_redirection_and_register_new_password_for_ldap_sql_and_memory(
    has_expired,
    testclient,
    logged_user,
    user,
    backend,
    admin,
):
    """time_machine does not work with ldap."""
    has_expired.return_value = False
    assert user.password_last_update is None
    res = testclient.get("/profile/user/settings", status=200)
    res.form["password1"] = "123456789"
    res.form["password2"] = "123456789"
    res = res.form.submit(name="action", value="edit-settings")
    backend.reload(logged_user)
    assert user.password_last_update is not None

    has_expired.return_value = True
    res = testclient.get("/profile/user/settings")
    testclient.get("/reset/admin", status=403)
    assert (
        "info",
        "Your password has expired, please choose a new password.",
    ) in res.flashes
    assert res.location == "/reset/user"
    backend.reload(logged_user)
    backend.reload(g.session.user)
    backend.reload(user)

    res = testclient.get("/reset/user")

    res.form["password"] = "foobarbaz"
    res.form["confirmation"] = "foobarbaz"
    res = res.form.submit()
    assert ("success", "Your password has been updated successfully") in res.flashes


def test_not_expired_password_or_wrong_user_redirection(
    testclient, logged_user, user, backend, admin
):
    assert user.password_last_update is None
    res = testclient.get("/profile/user/settings", status=200)
    res.form["password1"] = "123456789"
    res.form["password2"] = "123456789"
    res = res.form.submit(name="action", value="edit-settings")
    backend.reload(logged_user)
    assert user.password_last_update is not None

    def test_two_redirections(password_lifetime):
        testclient.app.config["CANAILLE"]["PASSWORD_LIFETIME"] = password_lifetime
        testclient.get("/reset/user", status=403)
        testclient.get("/reset/admin", status=403)

    test_two_redirections(None)

    testclient.app.config["CANAILLE"]["PASSWORD_LIFETIME"] = "PT0S"
    res = testclient.get("/profile/user/settings")
    assert (
        "info",
        "Your password has expired, please choose a new password.",
    ) in res.flashes
    assert res.location == "/reset/user"

    testclient.app.config["CANAILLE"]["PASSWORD_LIFETIME"] = "P1D"
    res = testclient.get("/profile/user/settings")
    res.form["password1"] = "123456789"
    res.form["password2"] = "123456789"
    res = res.form.submit(name="action", value="edit-settings")

    test_two_redirections("P1D")


def test_expired_password_needed_without_current_user(testclient, user):
    testclient.get("/reset/user", status=403)
