from unittest.mock import PropertyMock, patch

import pytest

from apps.schedules.models.on_call_schedule import OnCallScheduleQuerySet, OnCallScheduleWeb
from apps.slack.client import SlackClient
from apps.slack.errors import (
    SlackAPIError,
    SlackAPIInvalidUsersError,
    SlackAPITokenError,
    SlackAPIUsergroupNotFoundError,
    SlackAPIUsergroupPaidTeamOnlyError,
)
from apps.slack.models import SlackUserGroup
from apps.slack.tasks import (
    populate_slack_usergroups_for_team,
    start_update_slack_user_group_for_schedules,
    update_slack_user_group_for_schedules,
)
from apps.slack.tests.conftest import build_slack_response
from apps.user_management.models import Organization


@pytest.mark.django_db
def test_update_members(make_organization_with_slack_team_identity, make_slack_user_group):
    _, slack_team_identity = make_organization_with_slack_team_identity()
    user_group = make_slack_user_group(slack_team_identity)

    slack_ids = ["slack_id_1", "slack_id_2"]
    with patch("apps.slack.client.SlackClient.usergroups_users_update") as mock_update:
        user_group.update_members(slack_ids)

    mock_update.assert_called_once()
    assert user_group.members == slack_ids


@pytest.mark.django_db
@pytest.mark.parametrize(
    "exception",
    [
        SlackAPITokenError,
        SlackAPIUsergroupNotFoundError,
        SlackAPIInvalidUsersError,
        SlackAPIUsergroupPaidTeamOnlyError,
    ],
)
def test_slack_user_group_update_errors(
    make_organization_with_slack_team_identity,
    make_slack_user_group,
    exception,
):
    _, slack_team_identity = make_organization_with_slack_team_identity()
    user_group = make_slack_user_group(slack_team_identity=slack_team_identity)

    slack_ids = ["slack_id_1", "slack_id_2"]
    with patch("apps.slack.client.SlackClient.usergroups_users_update", side_effect=exception("Error")) as mock_update:
        user_group.update_members(slack_ids)

    mock_update.assert_called_once()
    assert user_group.members is None


@pytest.mark.django_db
def test_slack_user_group_update_errors_raise(
    make_organization_with_slack_team_identity,
    make_slack_user_group,
):
    _, slack_team_identity = make_organization_with_slack_team_identity()
    user_group = make_slack_user_group(slack_team_identity=slack_team_identity)

    slack_ids = ["slack_id_1", "slack_id_2"]
    with patch(
        "apps.slack.client.SlackClient.usergroups_users_update", side_effect=SlackAPIError("Error")
    ) as mock_update:
        with pytest.raises(SlackAPIError):
            user_group.update_members(slack_ids)

    mock_update.assert_called_once()


@pytest.mark.django_db
def test_oncall_slack_user_identities(
    make_organization_with_slack_team_identity,
    make_slack_user_group,
    make_user_with_slack_user_identity,
    make_user_for_organization,
):
    organization, slack_team_identity = make_organization_with_slack_team_identity()
    user_group = make_slack_user_group(slack_team_identity)

    user_1, slack_user_identity_1 = make_user_with_slack_user_identity(
        slack_team_identity, organization, slack_id="user_1"
    )
    user_2, slack_user_identity_2 = make_user_with_slack_user_identity(
        slack_team_identity, organization, slack_id="user_2"
    )
    user_3 = make_user_for_organization(organization)

    with patch.object(OnCallScheduleQuerySet, "get_oncall_users", return_value={"schedule1": [user_1, user_2, user_3]}):
        assert set(user_group.oncall_slack_user_identities) == {slack_user_identity_1, slack_user_identity_2}


@pytest.mark.django_db
def test_update_oncall_members(
    make_organization_with_slack_team_identity,
    make_slack_user_group,
    make_user_with_slack_user_identity,
):
    organization, slack_team_identity = make_organization_with_slack_team_identity()
    user_group = make_slack_user_group(slack_team_identity)

    _, slack_user_identity_1 = make_user_with_slack_user_identity(
        slack_team_identity, organization, slack_id="slack_id_1"
    )
    _, slack_user_identity_2 = make_user_with_slack_user_identity(
        slack_team_identity, organization, slack_id="slack_id_2"
    )

    with patch.object(
        SlackUserGroup, "oncall_slack_user_identities", new_callable=PropertyMock
    ) as oncall_slack_user_identities_mock:
        oncall_slack_user_identities_mock.return_value = [slack_user_identity_1, slack_user_identity_2]

        with patch.object(SlackUserGroup, "update_members") as update_members_mock:
            user_group.update_oncall_members()
            update_members_mock.assert_called()


@pytest.mark.django_db
def test_start_update_slack_user_group_for_schedules_organization_deleted(
    make_organization_with_slack_team_identity, make_slack_user_group, make_schedule
):
    organization, slack_team_identity = make_organization_with_slack_team_identity()
    user_group = make_slack_user_group(slack_team_identity)
    make_schedule(organization, schedule_class=OnCallScheduleWeb, user_group=user_group)

    # check user group is updated
    with patch.object(update_slack_user_group_for_schedules, "delay") as mock:
        start_update_slack_user_group_for_schedules()
        mock.assert_called_once_with(user_group_pk=user_group.pk)

    # soft delete the organization
    Organization.objects.filter(pk=organization.pk).delete()

    # check user group is not updated for deleted organization
    with patch.object(update_slack_user_group_for_schedules, "delay") as mock:
        start_update_slack_user_group_for_schedules()
        mock.assert_not_called()


@patch.object(
    SlackClient,
    "usergroups_users_list",
    return_value=build_slack_response({"ok": True, "users": ["test_user_1", "test_user_2"]}),
)
@patch.object(
    SlackClient,
    "usergroups_list",
    return_value=build_slack_response(
        {
            "ok": True,
            "usergroups": [{"id": "test_slack_id", "name": "test_name", "handle": "test_handle", "date_delete": 0}],
        }
    ),
)
@pytest.mark.django_db
def test_update_or_create_slack_usergroup_from_slack(
    mock_usergroups_list, mock_usergroups_users_list, make_organization_with_slack_team_identity
):
    _, slack_team_identity = make_organization_with_slack_team_identity()
    SlackUserGroup.update_or_create_slack_usergroup_from_slack("test_slack_id", slack_team_identity)

    usergroup = SlackUserGroup.objects.get()
    assert usergroup.name == "test_name"
    assert usergroup.handle == "test_handle"
    assert usergroup.members == ["test_user_1", "test_user_2"]
    assert usergroup.is_active


@patch.object(
    SlackClient,
    "usergroups_list",
    return_value=build_slack_response(
        {
            "ok": True,
            "usergroups": [{"id": "test_slack_id", "name": "test_name", "handle": "test_handle", "date_delete": 0}],
        }
    ),
)
@pytest.mark.django_db
def test_update_or_create_slack_usergroup_from_slack_group_not_found(
    mock_usergroups_list, make_organization_with_slack_team_identity
):
    _, slack_team_identity = make_organization_with_slack_team_identity()
    SlackUserGroup.update_or_create_slack_usergroup_from_slack("other_id", slack_team_identity)

    # no group is created, no error is raised
    assert SlackUserGroup.objects.count() == 0


@patch.object(
    SlackClient,
    "usergroups_users_list",
    return_value=build_slack_response({"ok": True, "users": ["test_user_1", "test_user_2"]}),
)
@patch.object(
    SlackClient,
    "usergroups_list",
    return_value=build_slack_response(
        {
            "ok": True,
            "usergroups": [{"id": "test_slack_id", "name": "test_name", "handle": "test_handle", "date_delete": 0}],
        }
    ),
)
@pytest.mark.django_db
def test_populate_slack_usergroups_for_team(
    mock_usergroups_list, mock_usergroups_users_list, make_organization_with_slack_team_identity
):
    _, slack_team_identity = make_organization_with_slack_team_identity()
    populate_slack_usergroups_for_team(slack_team_identity.pk)

    usergroup = SlackUserGroup.objects.get()
    assert usergroup.name == "test_name"
    assert usergroup.handle == "test_handle"
    assert usergroup.members == ["test_user_1", "test_user_2"]
    assert usergroup.is_active


@pytest.mark.django_db
def test_get_users_from_members_for_organization(
    make_organization_with_slack_team_identity,
    make_slack_user_group,
    make_user_with_slack_user_identity,
):
    organization, slack_team_identity = make_organization_with_slack_team_identity()

    user_1, _ = make_user_with_slack_user_identity(slack_team_identity, organization, slack_id="slack_id_1")
    user_2, _ = make_user_with_slack_user_identity(slack_team_identity, organization, slack_id="slack_id_2")
    user_group = make_slack_user_group(slack_team_identity)
    user_group.members = ["slack_id_1", "slack_id_2"]
    user_group.save(update_fields=["members"])

    users = user_group.get_users_from_members_for_organization(organization)
    assert set(users) == {user_1, user_2}
