# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Test password hashing."""

from binascii import hexlify, unhexlify

import flask
import pytest
from flask_security.utils import hash_password, verify_password

from invenio_accounts.hash import _to_binary, _to_string, mysql_aes_decrypt, \
    mysql_aes_encrypt
from invenio_accounts.testutils import client_authenticated, \
    create_test_user, login_user_via_view


def test_mysql_aes_encrypt():
    """Test mysql_aes_encrypt."""
    assert hexlify(mysql_aes_encrypt("test", "key")) == \
        b'9e9ce44cd9df2b201f51947e03bccbe2'
    assert hexlify(mysql_aes_encrypt(u"test", "key")) == \
        b'9e9ce44cd9df2b201f51947e03bccbe2'
    assert hexlify(mysql_aes_encrypt("test", u"key")) == \
        b'9e9ce44cd9df2b201f51947e03bccbe2'
    assert hexlify(mysql_aes_encrypt(u"test", u"key")) == \
        b'9e9ce44cd9df2b201f51947e03bccbe2'
    pytest.raises(AssertionError, mysql_aes_encrypt, object(), "key")
    pytest.raises(AssertionError, mysql_aes_encrypt, "val", object())


def test_mysql_aes_decrypt():
    """Test mysql_aes_encrypt."""
    assert mysql_aes_decrypt(
        unhexlify(b'9e9ce44cd9df2b201f51947e03bccbe2'), "key") == "test"
    assert mysql_aes_decrypt(
        unhexlify(u"9e9ce44cd9df2b201f51947e03bccbe2"), u"key") == "test"
    pytest.raises(AssertionError, mysql_aes_decrypt, object(), "key")
    pytest.raises(AssertionError, mysql_aes_decrypt, "val", object())


def test_context(app):
    """Test passlib password context."""
    with app.app_context():
        ctx = flask.current_app.extensions['security'].pwd_context
        hashval = hash_password("test")
        assert hashval != "test"
        assert verify_password("test", hashval)
        assert not ctx.needs_update(hashval)
        assert ctx.hash("test") != ctx.hash("test")


def test_conversion():
    """Test conversion between bytes and text."""
    str = 'abcd1234'
    byte_str = b'abcd1234'
    u_str = u'abcd1234'
    assert _to_binary(str) == byte_str
    assert _to_string(byte_str) == str
    assert _to_string(str) == str
    assert _to_binary(byte_str) == byte_str
    assert _to_string(u_str) == u_str


def test_unicode_regression(app):
    """Test legacy encryption with Unicode encoding."""
    with app.app_context():
        user = create_legacy_user(password=u'κωδικός')
        assert verify_password(u'κωδικός', user.password)


def test_invenio_aes_encrypted_email(app):
    """Test legacy encryption."""
    with app.app_context():
        ctx = flask.current_app.extensions['security'].pwd_context
        user = create_legacy_user()
        assert verify_password(user.password_plaintext, user.password)
        assert ctx.needs_update(user.password)


def test_user_login(app):
    """Test users' high-level login process."""
    with app.app_context():
        user = create_test_user('test@example.org')
        with app.test_client() as client:
            login_user_via_view(client, user.email, user.password_plaintext)
            assert client_authenticated(client)


def test_legacy_user_login(app):
    """Test legacy users' high-level login process."""
    with app.app_context():
        user = create_legacy_user()
        old_hashval = user.password
        with app.test_client() as client:
            login_user_via_view(client, user.email, user.password_plaintext)
            # Verify user is authenticated
            assert client_authenticated(client)
            # Verify password hash is upgraded
            ds = flask.current_app.extensions['security'].datastore
            user2 = ds.find_user(email=user.email)
            assert old_hashval != user2.password


def test_monkey_patch(app):
    """Test monkey-patching of Flask-Security's get_hmac function."""
    with app.app_context():
        user = create_test_user('test@example.org')
        assert verify_password(user.password_plaintext, user.password)


def test_monkey_patch_legacy(app):
    """Test monkey-patching of Flask-Security's get_hmac function."""
    with app.app_context():
        user = create_legacy_user()
        assert verify_password(user.password_plaintext, user.password)


def create_legacy_user(email='test@example.org', password=u'qwert1234',
                       **kwargs):
    """Create a legacy user in the datastore.

    A legacy user's password has been encrypted with the legacy mechanism,
    namely 'invenio_aes_encrypted_email'.
    """
    assert flask.current_app.testing
    ds = flask.current_app.extensions['security'].datastore

    # Encrypt password
    ctx = flask.current_app.extensions['security'].pwd_context
    encrypted_password = ctx.encrypt(
        password,
        scheme="invenio_aes_encrypted_email",
        salt=email,
    )

    # Update datastore
    user = ds.create_user(email=email, password=encrypted_password, **kwargs)
    ds.commit()
    user.password_plaintext = password

    return user
