import argparse
import pytest

from pytest_mock import MockerFixture
from unittest.mock import call as MockCall

from ahriman.application.handlers.users import Users
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import PasswordError
from ahriman.models.action import Action
from ahriman.models.user import User
from ahriman.models.user_access import UserAccess


def _default_args(args: argparse.Namespace) -> argparse.Namespace:
    """
    default arguments for these test cases

    Args:
        args(argparse.Namespace): command line arguments fixture

    Returns:
        argparse.Namespace: generated arguments for these test cases
    """
    args.username = "user"
    args.action = Action.Update
    args.exit_code = False
    args.key = "key"
    args.packager = "packager"
    args.password = "pa55w0rd"
    args.role = UserAccess.Reporter
    return args


def test_run(args: argparse.Namespace, configuration: Configuration, database: SQLite, mocker: MockerFixture) -> None:
    """
    must run command
    """
    args = _default_args(args)
    user = User(username=args.username, password=args.password, access=args.role,
                packager_id=args.packager, key=args.key)
    mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
    mocker.patch("ahriman.models.user.User.hash_password", return_value=user)
    create_user_mock = mocker.patch("ahriman.application.handlers.users.Users.user_create", return_value=user)
    update_mock = mocker.patch("ahriman.core.database.SQLite.user_update")

    _, repository_id = configuration.check_loaded()
    Users.run(args, repository_id, configuration, report=False)
    create_user_mock.assert_called_once_with(args)
    update_mock.assert_called_once_with(user)


def test_run_empty_salt(args: argparse.Namespace, configuration: Configuration, database: SQLite,
                        mocker: MockerFixture) -> None:
    """
    must process users with empty password salt
    """
    configuration.remove_option("auth", "salt")
    args = _default_args(args)
    user = User(username=args.username, password=args.password, access=args.role,
                packager_id=args.packager, key=args.key)
    mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
    mocker.patch("ahriman.models.user.User.hash_password", return_value=user)
    create_user_mock = mocker.patch("ahriman.application.handlers.users.Users.user_create", return_value=user)
    update_mock = mocker.patch("ahriman.core.database.SQLite.user_update")

    _, repository_id = configuration.check_loaded()
    Users.run(args, repository_id, configuration, report=False)
    create_user_mock.assert_called_once_with(args)
    update_mock.assert_called_once_with(user)


def test_run_empty_salt_without_password(args: argparse.Namespace, configuration: Configuration, database: SQLite,
                                         mocker: MockerFixture) -> None:
    """
    must skip salt option in case if password was empty
    """
    configuration.remove_option("auth", "salt")
    args = _default_args(args)
    user = User(username=args.username, password="", access=args.role,
                packager_id=args.packager, key=args.key)
    mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
    mocker.patch("ahriman.models.user.User.hash_password", return_value=user)
    create_user_mock = mocker.patch("ahriman.application.handlers.users.Users.user_create", return_value=user)
    update_mock = mocker.patch("ahriman.core.database.SQLite.user_update")

    _, repository_id = configuration.check_loaded()
    Users.run(args, repository_id, configuration, report=False)
    create_user_mock.assert_called_once_with(args)
    update_mock.assert_called_once_with(user)


def test_run_list(args: argparse.Namespace, configuration: Configuration, database: SQLite, user: User,
                  mocker: MockerFixture) -> None:
    """
    must list available users
    """
    args = _default_args(args)
    args.action = Action.List
    mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
    check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
    list_mock = mocker.patch("ahriman.core.database.SQLite.user_list", return_value=[user])

    _, repository_id = configuration.check_loaded()
    Users.run(args, repository_id, configuration, report=False)
    list_mock.assert_called_once_with("user", args.role)
    check_mock.assert_called_once_with(False, [user])


def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, database: SQLite,
                             mocker: MockerFixture) -> None:
    """
    must raise ExitCode exception on empty user list
    """
    args = _default_args(args)
    args.action = Action.List
    args.exit_code = True
    mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
    mocker.patch("ahriman.core.database.SQLite.user_list", return_value=[])
    check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")

    _, repository_id = configuration.check_loaded()
    Users.run(args, repository_id, configuration, report=False)
    check_mock.assert_called_once_with(True, [])


def test_run_remove(args: argparse.Namespace, configuration: Configuration, database: SQLite,
                    mocker: MockerFixture) -> None:
    """
    must remove user if remove flag supplied
    """
    args = _default_args(args)
    args.action = Action.Remove
    mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
    remove_mock = mocker.patch("ahriman.core.database.SQLite.user_remove")

    _, repository_id = configuration.check_loaded()
    Users.run(args, repository_id, configuration, report=False)
    remove_mock.assert_called_once_with(args.username)


def test_user_create(args: argparse.Namespace, user: User) -> None:
    """
    must create user
    """
    args = _default_args(args)
    generated = Users.user_create(args)
    assert generated.username == user.username
    assert generated.access == user.access


def test_user_create_getpass(args: argparse.Namespace, mocker: MockerFixture) -> None:
    """
    must create user and get password from command line
    """
    args = _default_args(args)
    args.password = None
    getpass_mock = mocker.patch("getpass.getpass", return_value="password")

    generated = Users.user_create(args)
    getpass_mock.assert_has_calls([MockCall(), MockCall("Repeat password: ")])
    assert generated.password == "password"


def test_user_create_getpass_exception(args: argparse.Namespace, mocker: MockerFixture) -> None:
    """
    must raise password error in case if password doesn't match
    """
    args = _default_args(args)
    args.password = None
    mocker.patch("getpass.getpass", side_effect=lambda *_: User.generate_password(10))

    with pytest.raises(PasswordError):
        Users.user_create(args)


def test_disallow_multi_architecture_run() -> None:
    """
    must not allow multi architecture run
    """
    assert not Users.ALLOW_MULTI_ARCHITECTURE_RUN
