from datetime import datetime
from unittest.mock import patch

import pytest

from szurubooru import db, errors, model
from szurubooru.func import auth, files, users, util

EMPTY_PIXEL = (
    b"\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00"
    b"\xff\xff\xff\x21\xf9\x04\x01\x00\x00\x01\x00\x2c\x00\x00\x00\x00"
    b"\x01\x00\x01\x00\x00\x02\x02\x4c\x01\x00\x3b"
)


@pytest.mark.parametrize("user_name", ["test", "TEST"])
def test_get_avatar_path(user_name):
    assert users.get_avatar_path(user_name) == "avatars/test.png"


@pytest.mark.parametrize(
    "user_name,user_email,avatar_style,expected_url",
    [
        (
            "user",
            None,
            model.User.AVATAR_GRAVATAR,
            (
                "https://gravatar.com/avatar/"
                + "ee11cbb19052e40b07aac0ca060c23ee?d=retro&s=100"
            ),
        ),
        (
            None,
            "user@example.com",
            model.User.AVATAR_GRAVATAR,
            (
                "https://gravatar.com/avatar/"
                + "b58996c504c5638798eb6b511e6f49af?d=retro&s=100"
            ),
        ),
        (
            "user",
            "user@example.com",
            model.User.AVATAR_GRAVATAR,
            (
                "https://gravatar.com/avatar/"
                + "b58996c504c5638798eb6b511e6f49af?d=retro&s=100"
            ),
        ),
        (
            "user",
            None,
            model.User.AVATAR_MANUAL,
            "http://example.com/avatars/user.png",
        ),
    ],
)
def test_get_avatar_url(
    user_name, user_email, avatar_style, expected_url, config_injector
):
    config_injector(
        {
            "data_url": "http://example.com/",
            "thumbnails": {"avatar_width": 100},
        }
    )
    user = model.User()
    user.name = user_name
    user.email = user_email
    user.avatar_style = avatar_style
    assert users.get_avatar_url(user) == expected_url


@pytest.mark.parametrize(
    "same_user,can_edit_any_email,force_show,expected_email",
    [
        (False, False, False, False),
        (True, False, False, "test@example.com"),
        (False, True, False, "test@example.com"),
        (False, False, True, "test@example.com"),
    ],
)
def test_get_email(
    same_user, can_edit_any_email, force_show, expected_email, user_factory
):
    with patch("szurubooru.func.auth.has_privilege"):
        auth.has_privilege = lambda user, name: can_edit_any_email
        user = user_factory()
        user.email = "test@example.com"
        auth_user = user if same_user else user_factory()
        db.session.add_all([user, auth_user])
        db.session.flush()
        assert users.get_email(user, auth_user, force_show) == expected_email


@pytest.mark.parametrize(
    "same_user,score,expected_liked_post_count,expected_disliked_post_count",
    [
        (False, 1, False, False),
        (False, -1, False, False),
        (True, 1, 1, 0),
        (True, -1, 0, 1),
    ],
)
def test_get_liked_post_count(
    same_user,
    score,
    expected_liked_post_count,
    expected_disliked_post_count,
    user_factory,
    post_factory,
):
    user = user_factory()
    post = post_factory()
    auth_user = user if same_user else user_factory()
    score = model.PostScore(
        post=post, user=user, score=score, time=datetime.now()
    )
    db.session.add_all([post, user, score])
    db.session.flush()
    actual_liked_post_count = users.get_liked_post_count(user, auth_user)
    actual_disliked_post_count = users.get_disliked_post_count(user, auth_user)
    assert actual_liked_post_count == expected_liked_post_count
    assert actual_disliked_post_count == expected_disliked_post_count


def test_serialize_user_when_empty():
    assert users.serialize_user(None, None) is None


def test_serialize_user(user_factory):
    with patch("szurubooru.func.users.get_email"), patch(
        "szurubooru.func.users.get_avatar_url"
    ), patch("szurubooru.func.users.get_liked_post_count"), patch(
        "szurubooru.func.users.get_disliked_post_count"
    ):
        users.get_email.return_value = "test@example.com"
        users.get_avatar_url.return_value = "https://example.com/avatar.png"
        users.get_liked_post_count.return_value = 66
        users.get_disliked_post_count.return_value = 33
        auth_user = user_factory()
        user = user_factory(name="dummy user")
        user.creation_time = datetime(1997, 1, 1)
        user.last_edit_time = datetime(1998, 1, 1)
        user.avatar_style = model.User.AVATAR_MANUAL
        user.rank = model.User.RANK_ADMINISTRATOR
        db.session.add(user)
        db.session.flush()
        assert users.serialize_user(user, auth_user) == {
            "version": 1,
            "name": "dummy user",
            "email": "test@example.com",
            "rank": "administrator",
            "creationTime": datetime(1997, 1, 1, 0, 0),
            "lastLoginTime": None,
            "avatarStyle": "manual",
            "avatarUrl": "https://example.com/avatar.png",
            "likedPostCount": 66,
            "dislikedPostCount": 33,
            "commentCount": 0,
            "favoritePostCount": 0,
            "uploadedPostCount": 0,
        }


def test_serialize_micro_user(user_factory):
    with patch("szurubooru.func.users.get_avatar_url"):
        users.get_avatar_url.return_value = "https://example.com/avatar.png"
        auth_user = user_factory()
        user = user_factory(name="dummy user")
        db.session.add(user)
        db.session.flush()
        assert users.serialize_micro_user(user, auth_user) == {
            "name": "dummy user",
            "avatarUrl": "https://example.com/avatar.png",
        }


@pytest.mark.parametrize("count", [0, 1, 2])
def test_get_user_count(count, user_factory):
    for _ in range(count):
        db.session.add(user_factory())
    db.session.flush()
    assert users.get_user_count() == count


def test_try_get_user_by_name(user_factory):
    user = user_factory(name="name", email="email")
    db.session.add(user)
    db.session.flush()
    assert users.try_get_user_by_name("non-existing") is None
    assert users.try_get_user_by_name("email") is None
    assert users.try_get_user_by_name("name") is user
    assert users.try_get_user_by_name("NAME") is user


def test_get_user_by_name(user_factory):
    user = user_factory(name="name", email="email")
    db.session.add(user)
    db.session.flush()
    with pytest.raises(users.UserNotFoundError):
        assert users.get_user_by_name("non-existing")
    with pytest.raises(users.UserNotFoundError):
        assert users.get_user_by_name("email")
    assert users.get_user_by_name("name") is user
    assert users.get_user_by_name("NAME") is user


def test_try_get_user_by_name_or_email(user_factory):
    user = user_factory(name="name", email="email")
    db.session.add(user)
    db.session.flush()
    assert users.try_get_user_by_name_or_email("non-existing") is None
    assert users.try_get_user_by_name_or_email("email") is user
    assert users.try_get_user_by_name_or_email("EMAIL") is user
    assert users.try_get_user_by_name_or_email("name") is user
    assert users.try_get_user_by_name_or_email("NAME") is user


def test_get_user_by_name_or_email(user_factory):
    user = user_factory(name="name", email="email")
    db.session.add(user)
    db.session.flush()
    with pytest.raises(users.UserNotFoundError):
        assert users.get_user_by_name_or_email("non-existing")
    assert users.get_user_by_name_or_email("email") is user
    assert users.get_user_by_name_or_email("EMAIL") is user
    assert users.get_user_by_name_or_email("name") is user
    assert users.get_user_by_name_or_email("NAME") is user


def test_create_user_for_first_user(fake_datetime):
    with patch("szurubooru.func.users.update_user_name"), patch(
        "szurubooru.func.users.update_user_password"
    ), patch("szurubooru.func.users.update_user_email"), fake_datetime(
        "1997-01-01"
    ):
        user = users.create_user("name", "password", "email")
        assert user.creation_time == datetime(1997, 1, 1)
        assert user.last_login_time is None
        assert user.rank == model.User.RANK_ADMINISTRATOR
        users.update_user_name.assert_called_once_with(user, "name")
        users.update_user_password.assert_called_once_with(user, "password")
        users.update_user_email.assert_called_once_with(user, "email")


def test_create_user_for_subsequent_users(user_factory, config_injector):
    config_injector({"default_rank": "regular"})
    db.session.add(user_factory())
    db.session.flush()
    with patch("szurubooru.func.users.update_user_name"), patch(
        "szurubooru.func.users.update_user_email"
    ), patch("szurubooru.func.users.update_user_password"):
        user = users.create_user("name", "password", "email")
        assert user.rank == model.User.RANK_REGULAR


def test_update_user_name_with_empty_string(user_factory):
    user = user_factory()
    with pytest.raises(users.InvalidUserNameError):
        users.update_user_name(user, None)


def test_update_user_name_with_too_long_string(user_factory):
    user = user_factory()
    with pytest.raises(users.InvalidUserNameError):
        users.update_user_name(user, "a" * 300)


def test_update_user_name_with_invalid_name(user_factory, config_injector):
    config_injector({"user_name_regex": "^[a-z]+$"})
    user = user_factory()
    with pytest.raises(users.InvalidUserNameError):
        users.update_user_name(user, "0")


def test_update_user_name_with_duplicate_name(user_factory, config_injector):
    config_injector({"user_name_regex": "^[a-z]+$"})
    user = user_factory()
    existing_user = user_factory(name="dummy")
    db.session.add(existing_user)
    db.session.flush()
    with pytest.raises(users.UserAlreadyExistsError):
        users.update_user_name(user, "dummy")


def test_update_user_name_reusing_own_name(user_factory, config_injector):
    config_injector({"user_name_regex": "^[a-z]+$"})
    user = user_factory(name="dummy")
    db.session.add(user)
    db.session.flush()
    with patch("szurubooru.func.files.has"):
        files.has.return_value = False
        users.update_user_name(user, "dummy")
        db.session.flush()
        assert users.try_get_user_by_name("dummy") is user


def test_update_user_name_for_new_user(user_factory, config_injector):
    config_injector({"user_name_regex": "^[a-z]+$"})
    user = user_factory()
    with patch("szurubooru.func.files.has"):
        files.has.return_value = False
        users.update_user_name(user, "dummy")
        assert user.name == "dummy"


def test_update_user_name_moves_avatar(user_factory, config_injector):
    config_injector({"user_name_regex": "^[a-z]+$"})
    user = user_factory(name="old")
    with patch("szurubooru.func.files.has"), patch(
        "szurubooru.func.files.move"
    ):
        files.has.return_value = True
        users.update_user_name(user, "new")
        files.move.assert_called_once_with(
            "avatars/old.png", "avatars/new.png"
        )


def test_update_user_password_with_empty_string(user_factory):
    user = user_factory()
    with pytest.raises(users.InvalidPasswordError):
        users.update_user_password(user, None)


def test_update_user_password_with_invalid_string(
    user_factory, config_injector
):
    config_injector({"password_regex": "^[a-z]+$"})
    user = user_factory()
    with pytest.raises(users.InvalidPasswordError):
        users.update_user_password(user, "0")


def test_update_user_password(user_factory, config_injector):
    config_injector({"password_regex": "^[a-z]+$"})
    user = user_factory()
    with patch("szurubooru.func.auth.create_password"), patch(
        "szurubooru.func.auth.get_password_hash"
    ):
        auth.create_password.return_value = "salt"
        auth.get_password_hash.return_value = ("hash", 3)
        users.update_user_password(user, "a")
        assert user.password_salt == "salt"
        assert user.password_hash == "hash"
        assert user.password_revision == 3


def test_update_user_email_with_too_long_string(user_factory):
    user = user_factory()
    with pytest.raises(users.InvalidEmailError):
        users.update_user_email(user, "a" * 300)


def test_update_user_email_with_invalid_email(user_factory):
    user = user_factory()
    with patch("szurubooru.func.util.is_valid_email"):
        util.is_valid_email.return_value = False
        with pytest.raises(users.InvalidEmailError):
            users.update_user_email(user, "a")


def test_update_user_email_with_empty_string(user_factory):
    user = user_factory()
    with patch("szurubooru.func.util.is_valid_email"):
        util.is_valid_email.return_value = True
        users.update_user_email(user, "")
        assert user.email is None


def test_update_user_email(user_factory):
    user = user_factory()
    with patch("szurubooru.func.util.is_valid_email"):
        util.is_valid_email.return_value = True
        users.update_user_email(user, "a")
        assert user.email == "a"


def test_update_user_rank_with_empty_string(user_factory):
    user = user_factory()
    auth_user = user_factory()
    with pytest.raises(users.InvalidRankError):
        users.update_user_rank(user, "", auth_user)


def test_update_user_rank_with_invalid_string(user_factory):
    user = user_factory()
    auth_user = user_factory()
    with pytest.raises(users.InvalidRankError):
        users.update_user_rank(user, "invalid", auth_user)
    with pytest.raises(users.InvalidRankError):
        users.update_user_rank(user, "anonymous", auth_user)
    with pytest.raises(users.InvalidRankError):
        users.update_user_rank(user, "nobody", auth_user)


def test_update_user_rank_with_higher_rank_than_possible(user_factory):
    db.session.add(user_factory())
    db.session.flush()
    user = user_factory()
    auth_user = user_factory()
    auth_user.rank = model.User.RANK_ANONYMOUS
    with pytest.raises(errors.AuthError):
        users.update_user_rank(user, "regular", auth_user)
    with pytest.raises(errors.AuthError):
        users.update_user_rank(auth_user, "regular", auth_user)


def test_update_user_rank(user_factory):
    db.session.add(user_factory())
    db.session.flush()
    user = user_factory()
    auth_user = user_factory()
    auth_user.rank = model.User.RANK_ADMINISTRATOR
    users.update_user_rank(user, "regular", auth_user)
    users.update_user_rank(auth_user, "regular", auth_user)
    assert user.rank == model.User.RANK_REGULAR
    assert auth_user.rank == model.User.RANK_REGULAR


def test_update_user_avatar_with_invalid_style(user_factory):
    user = user_factory()
    with pytest.raises(users.InvalidAvatarError):
        users.update_user_avatar(user, "invalid", b"")


def test_update_user_avatar_to_gravatar(user_factory):
    user = user_factory()
    users.update_user_avatar(user, "gravatar")
    assert user.avatar_style == model.User.AVATAR_GRAVATAR


def test_update_user_avatar_to_empty_manual(user_factory):
    user = user_factory()
    with patch("szurubooru.func.files.has"), pytest.raises(
        users.InvalidAvatarError
    ):
        files.has.return_value = False
        users.update_user_avatar(user, "manual", b"")


def test_update_user_avatar_to_previous_manual(user_factory):
    user = user_factory()
    with patch("szurubooru.func.files.has"):
        files.has.return_value = True
        users.update_user_avatar(user, "manual", b"")


def test_update_user_avatar_to_new_manual(user_factory, config_injector):
    config_injector(
        {"thumbnails": {"avatar_width": 500, "avatar_height": 500}}
    )
    user = user_factory()
    with patch("szurubooru.func.files.save"):
        users.update_user_avatar(user, "manual", EMPTY_PIXEL)
        assert user.avatar_style == model.User.AVATAR_MANUAL
        assert files.save.called


def test_bump_user_login_time(user_factory, fake_datetime):
    user = user_factory()
    with fake_datetime("1997-01-01"):
        users.bump_user_login_time(user)
        assert user.last_login_time == datetime(1997, 1, 1)


def test_reset_user_password(user_factory):
    with patch("szurubooru.func.auth.create_password"), patch(
        "szurubooru.func.auth.get_password_hash"
    ):
        user = user_factory()
        auth.create_password.return_value = "salt"
        auth.get_password_hash.return_value = ("hash", 3)
        users.reset_user_password(user)
        assert user.password_salt == "salt"
        assert user.password_hash == "hash"
        assert user.password_revision == 3
