# -*- coding: utf-8 -*-
"""
test_recoverable
~~~~~~~~~~~~~~~~

Recoverable functionality tests
"""

import time

import pytest
from flask import Flask
from utils import authenticate, logout

from flask_security.core import UserMixin
from flask_security.forms import LoginForm
from flask_security.signals import password_reset, reset_password_instructions_sent
from flask_security.utils import capture_reset_password_requests

pytestmark = pytest.mark.recoverable()


def test_recoverable_flag(app, client, get_message):
    recorded_resets = []
    recorded_instructions_sent = []

    @password_reset.connect_via(app)
    def on_password_reset(app, user):
        recorded_resets.append(user)

    @reset_password_instructions_sent.connect_via(app)
    def on_instructions_sent(app, user, token):
        assert isinstance(app, Flask)
        assert isinstance(user, UserMixin)
        assert isinstance(token, str)
        recorded_instructions_sent.append(user)

    # Test the reset view
    response = client.get("/reset")
    assert b"<h1>Send password reset instructions</h1>" in response.data

    # Test submitting email to reset password creates a token and sends email
    with capture_reset_password_requests() as requests:
        with app.mail.record_messages() as outbox:
            response = client.post(
                "/reset", data=dict(email="joe@lp.com"), follow_redirects=True
            )

    assert len(recorded_instructions_sent) == 1
    assert len(outbox) == 1
    assert response.status_code == 200
    assert get_message("PASSWORD_RESET_REQUEST", email="joe@lp.com") in response.data
    token = requests[0]["token"]

    # Test view for reset token
    response = client.get("/reset/" + token)
    assert b"<h1>Reset password</h1>" in response.data

    # Test submitting a new password
    response = client.post(
        "/reset/" + token,
        data={"password": "newpassword", "password_confirm": "newpassword"},
        follow_redirects=True,
    )

    assert get_message("PASSWORD_RESET") in response.data
    assert len(recorded_resets) == 1

    logout(client)

    # Test logging in with the new password
    response = authenticate(client, "joe@lp.com", "newpassword", follow_redirects=True)
    assert b"Hello joe@lp.com" in response.data

    logout(client)

    # Test invalid email
    response = client.post(
        "/reset", data=dict(email="bogus@lp.com"), follow_redirects=True
    )
    assert get_message("USER_DOES_NOT_EXIST") in response.data

    logout(client)

    # Test invalid token
    response = client.post(
        "/reset/bogus",
        data={"password": "newpassword", "password_confirm": "newpassword"},
        follow_redirects=True,
    )
    assert get_message("INVALID_RESET_PASSWORD_TOKEN") in response.data

    # Test mangled token
    token = (
        "WyIxNjQ2MzYiLCIxMzQ1YzBlZmVhM2VhZjYwODgwMDhhZGU2YzU0MzZjMiJd."
        "BZEw_Q.lQyo3npdPZtcJ_sNHVHP103syjM"
        "&url_id=fbb89a8328e58c181ea7d064c2987874bc54a23d"
    )
    response = client.post(
        "/reset/" + token,
        data={"password": "newpassword", "password_confirm": "newpassword"},
        follow_redirects=True,
    )
    assert get_message("INVALID_RESET_PASSWORD_TOKEN") in response.data


def test_login_form_description(sqlalchemy_app):
    app = sqlalchemy_app()
    with app.test_request_context("/login"):
        login_form = LoginForm()
        expected = '<a href="/reset">Forgot password?</a>'
        assert login_form.password.description == expected


@pytest.mark.settings(reset_password_within="1 milliseconds")
def test_expired_reset_token(client, get_message):
    with capture_reset_password_requests() as requests:
        client.post("/reset", data=dict(email="joe@lp.com"), follow_redirects=True)

    user = requests[0]["user"]
    token = requests[0]["token"]

    time.sleep(1)

    response = client.post(
        "/reset/" + token,
        data={"password": "newpassword", "password_confirm": "newpassword"},
        follow_redirects=True,
    )

    msg = get_message(
        "PASSWORD_RESET_EXPIRED", within="1 milliseconds", email=user.email
    )
    assert msg in response.data


def test_reset_token_deleted_user(app, client, get_message, sqlalchemy_datastore):
    with capture_reset_password_requests() as requests:
        client.post("/reset", data=dict(email="gene@lp.com"), follow_redirects=True)

    user = requests[0]["user"]
    token = requests[0]["token"]

    # Delete user
    sqlalchemy_datastore.delete(user)
    sqlalchemy_datastore.commit()

    response = client.post(
        "/reset/" + token,
        data={"password": "newpassword", "password_confirm": "newpassword"},
        follow_redirects=True,
    )

    msg = get_message("INVALID_RESET_PASSWORD_TOKEN")
    assert msg in response.data


def test_used_reset_token(client, get_message):
    with capture_reset_password_requests() as requests:
        client.post("/reset", data=dict(email="joe@lp.com"), follow_redirects=True)

    token = requests[0]["token"]

    # use the token
    response = client.post(
        "/reset/" + token,
        data={"password": "newpassword", "password_confirm": "newpassword"},
        follow_redirects=True,
    )

    assert get_message("PASSWORD_RESET") in response.data

    logout(client)

    # attempt to use it a second time
    response2 = client.post(
        "/reset/" + token,
        data={"password": "otherpassword", "password_confirm": "otherpassword"},
        follow_redirects=True,
    )

    msg = get_message("INVALID_RESET_PASSWORD_TOKEN")
    assert msg in response2.data


def test_reset_passwordless_user(client, get_message):
    with capture_reset_password_requests() as requests:
        client.post("/reset", data=dict(email="jess@lp.com"), follow_redirects=True)

    token = requests[0]["token"]

    # use the token
    response = client.post(
        "/reset/" + token,
        data={"password": "newpassword", "password_confirm": "newpassword"},
        follow_redirects=True,
    )

    assert get_message("PASSWORD_RESET") in response.data


@pytest.mark.settings(reset_url="/custom_reset")
def test_custom_reset_url(client):
    response = client.get("/custom_reset")
    assert response.status_code == 200


@pytest.mark.settings(
    reset_password_template="custom_security/reset_password.html",
    forgot_password_template="custom_security/forgot_password.html",
)
def test_custom_reset_templates(client):
    response = client.get("/reset")
    assert b"CUSTOM FORGOT PASSWORD" in response.data

    with capture_reset_password_requests() as requests:
        client.post("/reset", data=dict(email="joe@lp.com"), follow_redirects=True)
        token = requests[0]["token"]

    response = client.get("/reset/" + token)
    assert b"CUSTOM RESET PASSWORD" in response.data
