import pytest
import unicodedata
from flask import url_for

from app import config
from app.db import Session
from app.models import User, AccountActivation
from tests.utils import random_email

PASSWORD_1 = "Aurélie"
PASSWORD_2 = unicodedata.normalize("NFKD", PASSWORD_1)
assert PASSWORD_1 != PASSWORD_2


def setup_module():
    config.SKIP_MX_LOOKUP_ON_CHECK = True


def teardown_module():
    config.SKIP_MX_LOOKUP_ON_CHECK = False


@pytest.mark.parametrize("mfa", (True, False), ids=("MFA", "no MFA"))
def test_auth_login_success(flask_client, mfa: bool):
    email = random_email()
    User.create(
        email=email,
        password=PASSWORD_1,
        name="Test User",
        activated=True,
        enable_otp=mfa,
    )
    Session.commit()

    r = flask_client.post(
        "/api/auth/login",
        json={
            "email": email,
            "password": PASSWORD_2,
            "device": "Test Device",
        },
    )

    assert r.status_code == 200
    assert r.json["name"] == "Test User"
    assert r.json["email"]

    if mfa:
        assert r.json["api_key"] is None
        assert r.json["mfa_enabled"]
        assert r.json["mfa_key"]
    else:
        assert r.json["api_key"]
        assert not r.json["mfa_enabled"]
        assert r.json["mfa_key"] is None


def test_auth_login_device_exist(flask_client):
    email = random_email()
    User.create(email=email, password="password", name="Test User", activated=True)
    Session.commit()

    r = flask_client.post(
        url_for("api.auth_login"),
        json={
            "email": email,
            "password": "password",
            "device": "Test Device",
        },
    )

    assert r.status_code == 200
    api_key = r.json["api_key"]
    assert not r.json["mfa_enabled"]
    assert r.json["mfa_key"] is None
    assert r.json["name"] == "Test User"

    # same device, should return same api_key
    r = flask_client.post(
        url_for("api.auth_login"),
        json={
            "email": email,
            "password": "password",
            "device": "Test Device",
        },
    )
    assert r.json["api_key"] == api_key


def test_auth_register_success(flask_client):
    email = random_email()
    assert AccountActivation.first() is None

    r = flask_client.post(
        url_for("api.auth_register"),
        json={"email": email, "password": "password"},
    )

    assert r.status_code == 200
    assert r.json["msg"]

    # make sure an activation code is created
    act_code = AccountActivation.first()
    assert act_code
    assert len(act_code.code) == 6
    assert act_code.tries == 3


def test_auth_register_too_short_password(flask_client):
    email = random_email()
    r = flask_client.post(
        url_for("api.auth_register"),
        json={"email": email, "password": "short"},
    )

    assert r.status_code == 400
    assert r.json["error"] == "password too short"


def test_auth_register_too_long_password(flask_client):
    email = random_email()
    r = flask_client.post(
        url_for("api.auth_register"),
        json={"email": email, "password": "0123456789" * 11},
    )

    assert r.status_code == 400
    assert r.json["error"] == "password too long"


def test_auth_activate_success(flask_client):
    email = random_email()
    r = flask_client.post(
        url_for("api.auth_register"),
        json={"email": email, "password": "password"},
    )

    assert r.status_code == 200
    assert r.json["msg"]

    # get the activation code
    act_code = AccountActivation.first()
    assert act_code
    assert len(act_code.code) == 6

    r = flask_client.post(
        url_for("api.auth_activate"),
        json={"email": email, "code": act_code.code},
    )
    assert r.status_code == 200


def test_auth_activate_wrong_email(flask_client):
    r = flask_client.post(
        url_for("api.auth_activate"), json={"email": "abcd@gmail.com", "code": "123456"}
    )
    assert r.status_code == 400


def test_auth_activate_user_already_activated(flask_client):
    email = random_email()
    User.create(email=email, password="password", name="Test User", activated=True)
    Session.commit()

    r = flask_client.post(
        url_for("api.auth_activate"), json={"email": email, "code": "123456"}
    )
    assert r.status_code == 400


def test_auth_activate_wrong_code(flask_client):
    email = random_email()
    r = flask_client.post(
        url_for("api.auth_register"),
        json={"email": email, "password": "password"},
    )

    assert r.status_code == 200
    assert r.json["msg"]

    # get the activation code
    act_code = AccountActivation.first()
    assert act_code
    assert len(act_code.code) == 6
    assert act_code.tries == 3

    # make sure to create a wrong code
    wrong_code = act_code.code + "123"

    r = flask_client.post(
        url_for("api.auth_activate"),
        json={"email": email, "code": wrong_code},
    )
    assert r.status_code == 400

    # make sure the nb tries decrements
    act_code = AccountActivation.first()
    assert act_code.tries == 2


def test_auth_activate_too_many_wrong_code(flask_client):
    email = random_email()
    r = flask_client.post(
        url_for("api.auth_register"),
        json={"email": email, "password": "password"},
    )

    assert r.status_code == 200
    assert r.json["msg"]

    # get the activation code
    act_code = AccountActivation.first()
    assert act_code
    assert len(act_code.code) == 6
    assert act_code.tries == 3

    # make sure to create a wrong code
    wrong_code = act_code.code + "123"

    for _ in range(2):
        r = flask_client.post(
            url_for("api.auth_activate"),
            json={"email": email, "code": wrong_code},
        )
        assert r.status_code == 400

    # the activation code is deleted
    r = flask_client.post(
        url_for("api.auth_activate"),
        json={"email": email, "code": wrong_code},
    )

    assert r.status_code == 410

    # make sure the nb tries decrements
    assert AccountActivation.first() is None


def test_auth_reactivate_success(flask_client):
    email = random_email()
    User.create(email=email, password="password", name="Test User")
    Session.commit()

    r = flask_client.post(url_for("api.auth_reactivate"), json={"email": email})
    assert r.status_code == 200

    # make sure an activation code is created
    act_code = AccountActivation.first()
    assert act_code
    assert len(act_code.code) == 6
    assert act_code.tries == 3


def test_auth_login_forgot_password(flask_client):
    email = random_email()
    User.create(email=email, password="password", name="Test User", activated=True)
    Session.commit()

    r = flask_client.post(
        url_for("api.forgot_password"),
        json={"email": email},
    )

    assert r.status_code == 200

    # No such email, still return 200
    r = flask_client.post(
        url_for("api.forgot_password"),
        json={"email": random_email()},
    )

    assert r.status_code == 200
