# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# 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 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 <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

import mock
import pytest

from rhodecode.model.db import User
from rhodecode.tests import TEST_USER_REGULAR_LOGIN
from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.model.user_group import UserGroupModel
from rhodecode.model.meta import Session


fixture = Fixture()


def teardown_module(self):
    _delete_all_user_groups()


class TestGetUserGroups(object):
    def test_returns_filtered_list(self, backend, user_util):
        created_groups = []
        for i in range(4):
            created_groups.append(user_util.create_user_group(users_group_active=True))

        group_filter = created_groups[-1].users_group_name[-2:]
        with mock.patch("rhodecode.lib.helpers.gravatar_url"):
            with self._patch_user_group_list():
                groups = UserGroupModel().get_user_groups(group_filter)

        fake_groups = [u for u in groups if u["value"].startswith("test_returns")]
        assert len(fake_groups) == 1
        assert fake_groups[0]["value"] == created_groups[-1].users_group_name
        assert fake_groups[0]["value_display"].startswith("Group: test_returns")

    def test_returns_limited_list(self, backend, user_util):
        created_groups = []
        for i in range(3):
            created_groups.append(user_util.create_user_group(users_group_active=True))
        with mock.patch("rhodecode.lib.helpers.gravatar_url"):
            with self._patch_user_group_list():
                groups = UserGroupModel().get_user_groups("test_returns")

        fake_groups = [u for u in groups if u["value"].startswith("test_returns")]
        assert len(fake_groups) == 3

    def test_returns_active_user_groups(self, backend, user_util):
        for i in range(4):
            is_active = i % 2 == 0
            user_util.create_user_group(users_group_active=is_active)
        with mock.patch("rhodecode.lib.helpers.gravatar_url"):
            with self._patch_user_group_list():
                groups = UserGroupModel().get_user_groups()
        expected = ("id", "icon_link", "value_display", "value", "value_type")
        for group in groups:
            assert group["value_type"] == "user_group"
            for key in expected:
                assert key in group

        fake_groups = [u for u in groups if u["value"].startswith("test_returns")]
        assert len(fake_groups) == 2
        for user in fake_groups:
            assert user["value_display"].startswith("Group: test_returns")

    def _patch_user_group_list(self):
        def side_effect(group_list, perm_set):
            return group_list

        return mock.patch("rhodecode.model.user_group.UserGroupList", side_effect=side_effect)


@pytest.mark.parametrize(
    "pre_existing, regular_should_be, external_should_be, groups, expected",
    [
        ([], [], [], [], []),
        # no changes of regular
        ([], ["regular"], [], [], ["regular"]),
        # not added to regular group
        (["some_other"], [], [], ["some_other"], []),
        ([], ["regular"], ["container"], ["container"], ["regular", "container"]),
        ([], ["regular"], [], ["container", "container2"], ["regular", "container", "container2"]),
        # remove not used
        ([], ["regular"], ["other"], [], ["regular"]),
        (
            ["some_other"],
            ["regular"],
            ["other", "container"],
            ["container", "container2"],
            ["regular", "container", "container2"],
        ),
    ],
)
def test_enforce_groups(pre_existing, regular_should_be, external_should_be, groups, expected, backend_hg):
    # TODO: anderson: adding backend_hg fixture so it sets up the database
    # for when running this file alone
    _delete_all_user_groups()

    user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
    for gr in pre_existing:
        gr = fixture.create_user_group(gr)
    Session().commit()

    # make sure use is just in those groups
    for gr in regular_should_be:
        gr = fixture.create_user_group(gr)
        Session().commit()
        UserGroupModel().add_user_to_group(gr, user)
        Session().commit()

    # now special external groups created by auth plugins
    for gr in external_should_be:
        gr = fixture.create_user_group(gr, user_group_data={"extern_type": "container"})
        Session().commit()
        UserGroupModel().add_user_to_group(gr, user)
        Session().commit()

    UserGroupModel().enforce_groups(user, groups, "container")
    Session().commit()

    user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
    in_groups = user.group_member

    expected.sort()
    assert expected == sorted(x.users_group.users_group_name for x in in_groups)


def _delete_all_user_groups():
    for gr in UserGroupModel.get_all():
        fixture.destroy_user_group(gr)
    Session().commit()


def test_add_and_remove_user_from_group(user_regular, user_util):
    user_group = user_util.create_user_group()
    assert user_group.members == []
    UserGroupModel().add_user_to_group(user_group, user_regular)
    Session().commit()
    assert user_group.members[0].user == user_regular
    UserGroupModel().remove_user_from_group(user_group, user_regular)
    Session().commit()
    assert user_group.members == []


@pytest.mark.parametrize(
    "data, expected",
    [
        ([], []),
        ([{"member_user_id": 1, "type": "new"}], [1]),
        ([{"member_user_id": 1, "type": "new"}, {"member_user_id": 1, "type": "existing"}], [1]),
        (
            [
                {"member_user_id": 1, "type": "new"},
                {"member_user_id": 2, "type": "new"},
                {"member_user_id": 3, "type": "remove"},
            ],
            [1, 2],
        ),
    ],
)
def test_clean_members_data(data, expected):
    cleaned = UserGroupModel()._clean_members_data(data)
    assert cleaned == expected


def _create_test_members():
    members = []
    for member_number in range(3):
        member = mock.Mock()
        member.user_id = member_number + 1
        member.user.user_id = member_number + 1
        members.append(member)
    return members


def test_get_added_and_removed_users():
    members = _create_test_members()
    mock_user_group = mock.Mock()
    mock_user_group.members = [members[0], members[1]]
    new_users_list = [members[1].user.user_id, members[2].user.user_id]
    model = UserGroupModel()

    added, removed = model._get_added_and_removed_user_ids(mock_user_group, new_users_list)

    assert added == [members[2].user.user_id]
    assert removed == [members[0].user.user_id]


def test_set_users_as_members_and_find_user_in_group(user_util, user_regular, user_admin):
    user_group = user_util.create_user_group()
    assert len(user_group.members) == 0
    user_list = [user_regular.user_id, user_admin.user_id]
    UserGroupModel()._set_users_as_members(user_group, user_list)
    assert len(user_group.members) == 2
    assert UserGroupModel()._find_user_in_group(user_regular, user_group)
