from unittest.mock import patch

import pytest
from django.utils import timezone

from apps.slack.client import SlackClient
from apps.slack.tasks import populate_slack_channels_for_team


@pytest.mark.django_db
def test_populate_slack_channels_for_team(make_organization_with_slack_team_identity, make_slack_channel):
    organization, slack_team_identity = make_organization_with_slack_team_identity()

    yesterday = (timezone.now() - timezone.timedelta(days=1)).date()
    _ = tuple(
        make_slack_channel(
            slack_team_identity=slack_team_identity, slack_id=slack_id, name=name, last_populated=yesterday
        )
        for slack_id, name in (
            ("C111111111", "test1"),
            ("C222222222", "test2"),
            ("C444444444", "test4"),
        )
    )

    response, cursor, rate_limited = (
        {
            "channels": (
                {"id": "C111111111", "name": "test1", "is_archived": False, "is_shared": False},
                {"id": "C222222222", "name": "test_changed_name", "is_archived": False, "is_shared": True},
                {"id": "C333333333", "name": "test3", "is_archived": False, "is_shared": True},
            )
        },
        None,
        False,
    )

    with patch.object(SlackClient, "paginated_api_call_with_ratelimit", return_value=(response, cursor, rate_limited)):
        populate_slack_channels_for_team(slack_team_identity.pk)

    channels = slack_team_identity.cached_channels.all()

    expected_channel_ids = set(channel["id"] for channel in response["channels"])
    actual_channel_ids = set(channels.values_list("slack_id", flat=True))
    assert expected_channel_ids == actual_channel_ids

    assert not channels.filter(slack_id="C444444444").exists()

    second_channel = channels.get(slack_id="C222222222")
    assert second_channel.name == "test_changed_name"

    assert not channels.filter(last_populated__lte=yesterday).exists()


@patch("apps.slack.tasks.start_populate_slack_channels_for_team")
@pytest.mark.django_db
def test_populate_slack_channels_for_team_ratelimit(
    mocked_start_populate_slack_channels_for_team,
    make_organization_with_slack_team_identity,
    make_slack_channel,
):
    organization, slack_team_identity = make_organization_with_slack_team_identity()

    yesterday = (timezone.now() - timezone.timedelta(days=1)).date()
    today = timezone.now().date()

    _ = tuple(
        make_slack_channel(
            slack_team_identity=slack_team_identity, slack_id=slack_id, name=name, last_populated=yesterday
        )
        for slack_id, name in (
            ("C111111111", "test1"),
            ("C222222222", "test2"),
            ("C444444444", "test4"),
        )
    )
    # first response with rate limit error
    response_1, cursor_1, rate_limited_1 = (
        {"channels": ({"id": "C111111111", "name": "test1", "is_archived": False, "is_shared": False},)},
        "TESTCURSOR1",
        True,
    )

    # second response with rate limit error
    response_2, cursor_2, rate_limited_2 = (
        {
            "channels": (
                {"id": "C111111111", "name": "test1", "is_archived": False, "is_shared": False},
                {"id": "C222222222", "name": "test_changed_name", "is_archived": False, "is_shared": True},
            )
        },
        "TESTCURSOR2",
        True,
    )

    # third response without rate limit error
    response_3, cursor_3, rate_limited_3 = (
        {
            "channels": (
                {"id": "C222222222", "name": "test_changed_name", "is_archived": False, "is_shared": True},
                {"id": "C333333333", "name": "test3", "is_archived": False, "is_shared": True},
            )
        },
        "",
        False,
    )
    # these channels should exist after finishing populate_slack_channels_for_team
    expected_channel_ids = {"C111111111", "C222222222", "C333333333"}

    with patch.object(
        SlackClient,
        "paginated_api_call_with_ratelimit",
        return_value=(response_1, cursor_1, rate_limited_1),
    ):
        populate_slack_channels_for_team(slack_team_identity.pk)

    # expected only one channel to update and no channel to delete
    # start_populate_slack_channels_for_team should be called
    channels = slack_team_identity.cached_channels.all()
    channel_1 = channels.get(slack_id="C111111111")
    assert channel_1.last_populated == today

    channel_2 = channels.get(slack_id="C222222222")
    assert channel_2.last_populated == yesterday

    assert channels.filter(slack_id="C444444444").exists()

    assert mocked_start_populate_slack_channels_for_team.called
    assert mocked_start_populate_slack_channels_for_team.call_count == 1

    with patch.object(
        SlackClient,
        "paginated_api_call_with_ratelimit",
        return_value=(response_2, cursor_2, rate_limited_2),
    ):
        populate_slack_channels_for_team(slack_team_identity.pk, cursor_1)

    # expected another one channel to update and no channel to delete
    # start_populate_slack_channels_for_team should be called
    channels = slack_team_identity.cached_channels.all()

    channel_2 = channels.get(slack_id="C222222222")
    assert channel_2.last_populated == today
    assert channel_2.name == "test_changed_name"

    assert not channels.filter(slack_id="C333333333").exists()
    assert channels.filter(slack_id="C444444444").exists()

    assert mocked_start_populate_slack_channels_for_team.called
    assert mocked_start_populate_slack_channels_for_team.call_count == 2

    with patch.object(
        SlackClient,
        "paginated_api_call_with_ratelimit",
        return_value=(response_3, cursor_3, rate_limited_3),
    ):
        populate_slack_channels_for_team(slack_team_identity.pk, cursor_1)

    # expected one new channel and one deleted channel. List of channel ids in response and in db should be the same
    # start_populate_slack_channels_for_team should NOT be called
    channels = slack_team_identity.cached_channels.all()

    actual_channel_ids = set(channels.values_list("slack_id", flat=True))
    assert actual_channel_ids == expected_channel_ids

    assert not channels.filter(slack_id="C444444444").exists()

    channel_2 = channels.get(slack_id="C222222222")
    assert channel_2.name == "test_changed_name"

    assert not channels.filter(last_populated__lte=yesterday).exists()
    assert mocked_start_populate_slack_channels_for_team.call_count == 2
