# encoding: utf-8
#
# ** header v3.0
# This file is a part of the CaosDB Project.
#
# Copyright (C) 2018 Research Group Biomedical Physics,
# Max-Planck-Institute for Dynamics and Self-Organization Göttingen
#
# 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 <https://www.gnu.org/licenses/>.
#
# ** end header
#
"""Created on 27.02.2017.

@author: tf
"""

from caosdb import administration as admin, Info, get_config
from caosdb.connection.connection import configure_connection, get_connection
from caosdb.exceptions import (HTTPClientError, HTTPForbiddenError,
                               LoginFailedError, HTTPResourceNotFoundError)
from pytest import raises, mark

test_role = "test_role"
test_user = "test_user"
test_pw = "secret1P!"
test_role_desc = "This is a test role."


def setup_module():
    teardown_module()
    admin._insert_user(
        name=test_user,
        password=test_pw,
        status="ACTIVE",
        email=None,
        entity=None)


def teardown_module():
    switch_to_admin_user()
    admin.set_server_property("AUTH_OPTIONAL", "FALSE")
    try:
        admin._delete_user(name=test_user)
    except Exception as e:
        print(e)


def setup_function(function):
    switch_to_admin_user()


def teardown_function(function):
    switch_to_admin_user()
    try:
        admin._delete_user(name=test_user + "2")
    except Exception as e:
        print(e)
    try:
        admin._delete_user(name="first.last")
    except Exception as e:
        print(e)
    try:
        admin._delete_role(name=test_role)
    except Exception as e:
        print(e)


def switch_to_normal_user():
    configure_connection(username=test_user, password=test_pw,
                         password_method="plain")


def switch_to_admin_user():
    configure_connection()
    assert Info().user_info.name == get_config().get("Connection", "username")
    assert Info().user_info.roles == ["administration"]


def test_create_user_with_dot():
    admin._insert_user(
        name="first.last",
        password=test_pw,
        status="ACTIVE",
        email=None,
        entity=None)
    configure_connection(username="first.last", password=test_pw, password_method="plain")
    get_connection()._login()


def test_get_server_properties():
    props = admin.get_server_properties()
    assert isinstance(props, dict)
    assert "CaosDB Admin" == props["ADMIN_NAME"]


def test_set_server_property():
    admin.set_server_property("AUTH_OPTIONAL", "FALSE")
    assert admin.get_server_property("AUTH_OPTIONAL") == "FALSE"
    admin.set_server_property("AUTH_OPTIONAL", "TRUE")
    assert admin.get_server_property("AUTH_OPTIONAL") == "TRUE"
    admin.set_server_property("AUTH_OPTIONAL", "FALSE")
    assert admin.get_server_property("AUTH_OPTIONAL") == "FALSE"


def test_insert_role_success():
    assert Info().user_info.name == get_config().get("Connection", "username")
    assert Info().user_info.roles == ["administration"]
    assert admin._insert_role(name=test_role, description=test_role_desc)


def test_insert_role_failure_permission():
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._insert_role(name=test_role, description=test_role_desc)
    assert cm.value.msg == "You are not permitted to insert a new role."


def test_insert_role_failure_name_duplicates():
    assert Info().user_info.name == get_config().get("Connection", "username")
    assert Info().user_info.roles == ["administration"]
    test_insert_role_success()
    with raises(HTTPClientError) as cm:
        admin._insert_role(name=test_role, description=test_role_desc)
    assert cm.value.msg == "Role name is already in use. Choose a different name."


def test_update_role_success():
    test_insert_role_success()
    assert admin._update_role(
        name=test_role,
        description=test_role_desc +
        "asdf") is not None


def test_update_role_failure_permissions():
    test_insert_role_success()
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._update_role(name=test_role, description=test_role_desc + "asdf")
    assert cm.value.msg == "You are not permitted to update this role."


def test_update_role_failure_non_existing():
    with raises(HTTPResourceNotFoundError) as cm:
        admin._update_role(name=test_role, description=test_role_desc + "asdf")
    assert cm.value.msg == "Role does not exist."


def test_delete_role_success():
    test_insert_role_success()
    assert admin._delete_role(name=test_role)


def test_delete_role_failure_permissions():
    test_insert_role_success()
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._delete_role(name=test_role)
    assert cm.value.msg == "You are not permitted to delete this role."


def test_delete_role_failure_non_existing():
    with raises(HTTPResourceNotFoundError) as cm:
        admin._delete_role(name=test_role)
    assert cm.value.msg == "Role does not exist."


def test_retrieve_role_success():
    test_insert_role_success()
    r = admin._retrieve_role(test_role)
    assert r is not None


def test_retrieve_role_failure_permission():
    test_insert_role_success()
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._retrieve_role(name=test_role)
    assert cm.value.msg == "You are not permitted to retrieve this role."


def test_retrieve_role_failure_non_existing():
    with raises(HTTPResourceNotFoundError) as cm:
        admin._retrieve_role(name=test_role)
    assert cm.value.msg == "Role does not exist."


def test_set_permissions_success():
    test_insert_role_success()
    assert admin._set_permissions(
        role=test_role,
        permission_rules=[
            admin.PermissionRule(
                "Grant",
                "BLA:BLA:BLA")])


def test_set_permissions_failure_permissions():
    test_insert_role_success()
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._set_permissions(
            role=test_role, permission_rules=[
                admin.PermissionRule(
                    "Grant", "BLA:BLA:BLA")])
    assert cm.value.msg == "You are not permitted to set this role's permissions."


def test_set_permissions_failure_non_existing():
    with raises(HTTPResourceNotFoundError) as cm:
        admin._set_permissions(
            role=test_role, permission_rules=[
                admin.PermissionRule(
                    "Grant", "BLA:BLA:BLA")])
    assert cm.value.msg == "Role does not exist."


def test_get_permissions_success():
    test_set_permissions_success()
    r = admin._get_permissions(role=test_role)
    assert {admin.PermissionRule("Grant", "BLA:BLA:BLA")} == r
    assert r is not None


def test_get_permissions_failure_permissions():
    test_set_permissions_success()
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._get_permissions(role=test_role)
    assert cm.value.msg == "You are not permitted to retrieve this role's permissions."


def test_get_permissions_failure_non_existing():
    with raises(HTTPResourceNotFoundError) as cm:
        admin._get_permissions(role="non-existing-role")
    assert cm.value.msg == "Role does not exist."


def test_get_roles_success():
    test_insert_role_success()
    r = admin._get_roles(username=test_user)
    assert r is not None

    return r


def test_get_roles_failure_permissions():
    test_insert_role_success()
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._get_roles(username=test_user)
    assert cm.value.msg == "You are not permitted to retrieve this user's roles."


def test_get_roles_failure_non_existing():
    with raises(HTTPResourceNotFoundError) as cm:
        admin._get_roles(username="non-existing-user")
    assert cm.value.msg == "User does not exist."


def test_set_roles_success():
    roles_old = test_get_roles_success()
    roles = {test_role}
    roles.union(roles_old)
    assert admin._set_roles(username=test_user, roles=roles_old) is not None
    assert admin._set_roles(username=test_user, roles=roles) is not None
    assert admin._set_roles(username=test_user, roles=roles_old) is not None


def test_set_roles_success_with_warning():
    test_insert_role_success()
    roles = {test_role}
    r = admin._set_roles(username=test_user, roles=roles)
    assert r is not None
    assert admin._set_roles(username=test_user, roles=[]) is not None


def test_set_roles_failure_permissions():
    roles_old = test_get_roles_success()
    roles = {test_role}
    roles.union(roles_old)
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._set_roles(username=test_user, roles=roles_old)
    assert cm.value.msg == "You are not permitted to set this user's roles."


def test_set_roles_failure_non_existing_role():
    roles = {"non-existing-role"}
    with raises(HTTPClientError) as cm:
        admin._set_roles(username=test_user, roles=roles)
    assert cm.value.msg == "Role does not exist."


def test_set_roles_failure_non_existing_user():
    test_insert_role_success()
    roles = {test_role}
    with raises(HTTPResourceNotFoundError) as cm:
        admin._set_roles(username="non-existing-user", roles=roles)
    assert cm.value.msg == "User does not exist."


def test_insert_user_success():
    admin._insert_user(
        name=test_user + "2",
        password="secret1P!",
        status="ACTIVE",
        email="email@example.com",
        entity=None)


def test_insert_user_failure_password_invalid():
    with raises(HTTPClientError) as cm:
        admin._insert_user(
            name=test_user + "2",
            password="1234")
    assert cm.value.status == 400
    assert cm.value.msg[:70] == "The password does not comply with the current policies for passwords: "


def test_insert_user_failure_permissions():
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._insert_user(
            name=test_user,
            password="secret1P!",
            status="ACTIVE",
            email="email@example.com",
            entity=None)
    assert cm.value.msg == "You are not permitted to insert a new user."


def test_insert_user_failure_name_in_use():
    test_insert_user_success()
    with raises(HTTPClientError) as cm:
        test_insert_user_success()
    assert cm.value.msg == "This user name is already in use. Please choose another one."


def test_delete_user_success():
    test_insert_user_success()
    assert admin._delete_user(name=test_user + "2") is not None


def test_delete_user_failure_permissions():
    test_insert_user_success()
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._delete_user(name="non_existing_user")
    assert cm.value.msg == "You are not permitted to delete this user."


def test_delete_user_failure_non_existing():
    with raises(HTTPResourceNotFoundError) as cm:
        admin._delete_user(name="non_existing_user")
    assert cm.value.msg == "User does not exist."


def test_update_user_success_status():
    assert admin._insert_user(
        name=test_user + "2",
        password="secret1P!",
        status="INACTIVE",
        email="email@example.com",
        entity=None) is not None
    admin._update_user(
        realm=None,
        name=test_user + "2",
        password=None,
        status="ACTIVE",
        email=None,
        entity=None)


def test_update_user_success_email():
    assert admin._insert_user(
        name=test_user + "2",
        password="secret1P!",
        status="ACTIVE",
        email="email@example.com",
        entity=None) is not None
    admin._update_user(
        realm=None,
        name=test_user + "2",
        password=None,
        status=None,
        email="newemail@example.com",
        entity=None)


def test_update_user_success_entity():
    assert admin._insert_user(
        name=test_user + "2",
        password="secret1P!",
        status="ACTIVE",
        email="email@example.com",
        entity=None) is not None
    admin._update_user(realm=None, name=test_user + "2", password=None,
                       status=None, email=None, entity="21")


def test_update_user_success_password():
    assert admin._insert_user(
        name=test_user + "2",
        password="secret1P!",
        status="ACTIVE",
        email="email@example.com",
        entity=None) is not None
    admin._update_user(
        realm=None,
        name=test_user + "2",
        password="newsecret1P!",
        status=None,
        email=None,
        entity=None)


def test_update_user_failure_permissions_status():
    assert admin._insert_user(name=test_user + "2",
                              password="secret1P!",
                              status="INACTIVE",
                              email="email@example.com", entity=None) is not None
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._update_user(
            realm=None,
            name=test_user + "2",
            password=None,
            status="ACTIVE",
            email=None,
            entity=None)
    assert cm.value.msg == "You are not permitted to update this user."


def test_update_user_failure_permissions_email():
    assert admin._insert_user(name=test_user + "2",
                              password="secret1P!", status="ACTIVE",
                              email="email@example.com", entity=None) is not None
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._update_user(
            realm=None,
            name=test_user + "2",
            password=None,
            status=None,
            email="newemail@example.com",
            entity=None)
    assert cm.value.msg == "You are not permitted to update this user."


def test_update_user_failure_permissions_entity():
    assert admin._insert_user(name=test_user + "2",
                              password="secret1P!", status="ACTIVE",
                              email="email@example.com", entity=None) is not None
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._update_user(
            realm=None,
            name=test_user + "2",
            password=None,
            status=None,
            email=None,
            entity=21)
    assert cm.value.msg == "You are not permitted to update this user."


def test_update_user_failure_permissions_password():
    assert admin._insert_user(name=test_user + "2",
                              password="secret1P!", status="ACTIVE",
                              email="email@example.com", entity=None) is not None
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._update_user(
            realm=None,
            name=test_user + "2",
            password="newsecret1P!",
            status=None,
            email=None,
            entity=None)
    assert cm.value.msg == "You are not permitted to update this user."


def test_update_user_failure_non_existing_user():
    with raises(HTTPResourceNotFoundError) as cm:
        admin._update_user(
            realm=None,
            name="non-existing-user",
            password="newsecret1P!",
            status="ACTIVE",
            email="email@example.com",
            entity=None)
    assert cm.value.msg == "User does not exist."


def test_update_user_failure_non_existing_entity():
    assert admin._insert_user(name=test_user + "2",
                              password="secret1P!", status="ACTIVE",
                              email="email@example.com", entity=None) is not None
    with raises(HTTPClientError) as cm:
        admin._update_user(
            realm=None,
            name=test_user + "2",
            password=None,
            status=None,
            email=None,
            entity=100000)
    assert cm.value.msg == "Entity does not exist."


def test_retrieve_user_success():
    test_insert_user_success()
    assert admin._retrieve_user(realm=None, name=test_user + "2") is not None


def test_retrieve_user_failure_permissions():
    test_insert_user_success()
    switch_to_normal_user()
    with raises(HTTPForbiddenError) as cm:
        admin._retrieve_user(realm=None, name=test_user + "2")
    assert cm.value.msg == "You are not permitted to retrieve this user."


def test_retrieve_user_failure_non_existing():
    with raises(HTTPResourceNotFoundError) as cm:
        admin._retrieve_user(realm=None, name="non_existing")
    assert cm.value.msg == "User does not exist."


def test_login_with_inactive_user_failure():
    assert admin._insert_user(
        name=test_user + "2",
        password="secret1P!",
        status="INACTIVE",
        email="email@example.com",
        entity=None) is not None
    configure_connection(username=test_user + "2", password="secret1P!",
                         password_method="plain")
    with raises(LoginFailedError):
        get_connection()._login()