import datetime
import json
import textwrap
from unittest.mock import Mock, patch

import pytest
from django.utils import timezone

from apps.alerts.tasks.notify_ical_schedule_shift import (
    MIN_DAYS_TO_LOOKUP_FOR_THE_END_OF_EVENT,
    notify_ical_schedule_shift,
)
from apps.schedules.models import CustomOnCallShift, OnCallScheduleCalendar, OnCallScheduleICal, OnCallScheduleWeb

ICAL_DATA = """
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
DTSTART;VALUE=DATE:20211005
DTEND;VALUE=DATE:20211012
RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=7;BYDAY=WE
DTSTAMP:20210930T125523Z
UID:id1@google.com
CREATED:20210928T202349Z
DESCRIPTION:
LAST-MODIFIED:20210929T204751Z
LOCATION:
SEQUENCE:1
STATUS:CONFIRMED
SUMMARY:user1
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20210928
DTEND;VALUE=DATE:20211005
RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=7;BYDAY=WE
DTSTAMP:20210930T125523Z
UID:id2@google.com
CREATED:20210928T202331Z
DESCRIPTION:
LAST-MODIFIED:20210929T204744Z
LOCATION:
SEQUENCE:2
STATUS:CONFIRMED
SUMMARY:user2
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR
"""


@pytest.mark.django_db
def test_current_overrides_ical_schedule_is_none(
    make_organization_and_user_with_slack_identities,
    make_schedule,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()

    ical_schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleICal,
        name="test_ical_schedule",
        channel="channel",
        ical_url_primary="url",
        prev_ical_file_primary=ICAL_DATA,
        cached_ical_file_primary=ICAL_DATA,
        prev_ical_file_overrides=ICAL_DATA,
        cached_ical_file_overrides=None,
    )

    # this should not raise
    notify_ical_schedule_shift(ical_schedule.pk)


@pytest.mark.django_db
def test_next_shift_notification_long_shifts(
    make_organization_and_user_with_slack_identities,
    make_schedule,
    make_user,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    make_user(organization=organization, username="user1")
    make_user(organization=organization, username="user2")

    ical_schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleICal,
        name="test_ical_schedule",
        channel="channel",
        ical_url_primary="url",
        prev_ical_file_primary=ICAL_DATA,
        cached_ical_file_primary=ICAL_DATA,
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    with patch("apps.alerts.tasks.notify_ical_schedule_shift.datetime", Mock(wraps=datetime)) as mock_datetime:
        mock_datetime.datetime.now.return_value = datetime.datetime(2021, 9, 29, 12, 0, tzinfo=datetime.timezone.utc)
        with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
            notify_ical_schedule_shift(ical_schedule.pk)

    slack_blocks = mock_slack_api_call.call_args_list[0][1]["blocks"]
    notification = slack_blocks[1]["text"]["text"]
    assert "*New on-call shift*\nuser2" in notification
    notification = slack_blocks[2]["text"]["text"]
    assert "*Next on-call shift*\nuser1" in notification


@pytest.mark.django_db
def test_overrides_changes_no_current_no_triggering_notification(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")

    ical_before = textwrap.dedent(
        """
        BEGIN:VCALENDAR
        PRODID:-//Google Inc//Google Calendar 70.9054//EN
        VERSION:2.0
        CALSCALE:GREGORIAN
        METHOD:PUBLISH
        BEGIN:VEVENT
        DTSTART:20230101T020000
        DTEND:20230101T170000
        DTSTAMP:20230101T000000
        UID:id1@google.com
        CREATED:20230101T000000
        DESCRIPTION:
        LAST-MODIFIED:20230101T000000
        LOCATION:
        SEQUENCE:1
        STATUS:CONFIRMED
        SUMMARY:user1
        TRANSP:TRANSPARENT
        END:VEVENT
        END:VCALENDAR"""
    )

    # event outside current time is changed
    ical_after = textwrap.dedent(
        """
        BEGIN:VCALENDAR
        PRODID:-//Google Inc//Google Calendar 70.9054//EN
        VERSION:2.0
        CALSCALE:GREGORIAN
        METHOD:PUBLISH
        BEGIN:VEVENT
        DTSTART:20230101T020000
        DTEND:20230101T210000
        DTSTAMP:20230101T000000
        UID:id1@google.com
        CREATED:20230101T000000
        DESCRIPTION:
        LAST-MODIFIED:20230101T000000
        LOCATION:
        SEQUENCE:2
        STATUS:CONFIRMED
        SUMMARY:user1
        TRANSP:TRANSPARENT
        END:VEVENT
        END:VCALENDAR"""
    )

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleCalendar,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=ical_before,
    )

    now = timezone.now().replace(microsecond=0)
    start_date = now - timezone.timedelta(days=7, minutes=1)

    data = {
        "start": start_date,
        "rotation_start": start_date,
        "duration": timezone.timedelta(seconds=3600 * 24),
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
    }
    on_call_shift = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
    )
    on_call_shift.add_rolling_users([[user1]])
    on_call_shift.schedules.add(schedule)

    # setup current shifts before checking/triggering for notifications
    current_shifts = schedule.final_events(now, now, False, False)
    schedule.current_shifts = json.dumps(current_shifts, default=str)
    schedule.empty_oncall = False
    schedule.cached_ical_file_overrides = ical_after
    schedule.prev_ical_file_overrides = ical_before
    schedule.save()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    assert not mock_slack_api_call.called


@pytest.mark.django_db
def test_no_changes_no_triggering_notification(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleCalendar,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    now = timezone.now().replace(microsecond=0)
    start_date = now - timezone.timedelta(days=7, minutes=1)
    data = {
        "start": start_date,
        "rotation_start": start_date,
        "duration": timezone.timedelta(seconds=3600 * 24),
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
    }
    on_call_shift = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
    )
    on_call_shift.add_rolling_users([[user1]])
    on_call_shift.schedules.add(schedule)

    # setup current shifts before checking/triggering for notifications
    current_shifts = schedule.final_events(now, now, False, False)
    schedule.current_shifts = json.dumps(current_shifts, default=str)
    schedule.empty_oncall = False
    schedule.save()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    assert not mock_slack_api_call.called


@pytest.mark.django_db
def test_current_shift_changes_trigger_notification(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleCalendar,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    now = timezone.now().replace(microsecond=0)
    start_date = now - datetime.timedelta(days=7)
    data = {
        "start": start_date,
        "rotation_start": start_date,
        "duration": datetime.timedelta(seconds=3600 * 24),
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
    }
    on_call_shift = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
    )
    on_call_shift.add_rolling_users([[user1]])
    on_call_shift.schedules.add(schedule)
    schedule.refresh_ical_file()

    # setup empty current shifts before checking/triggering for notifications
    schedule.current_shifts = json.dumps({}, default=str)
    schedule.empty_oncall = False
    schedule.save()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    assert mock_slack_api_call.called


@pytest.mark.django_db
@pytest.mark.parametrize("swap_taken", [False, True])
def test_current_shift_changes_swap_split(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
    make_shift_swap_request,
    swap_taken,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")
    user2 = make_user(organization=organization, username="user2")

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleWeb,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
    duration = timezone.timedelta(hours=23, minutes=59, seconds=59)
    data = {
        "start": today,
        "rotation_start": today,
        "duration": duration,
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
        "schedule": schedule,
    }
    on_call_shift = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
    )
    on_call_shift.add_rolling_users([[user1]])

    # setup in progress swap request
    swap_request = make_shift_swap_request(
        schedule,
        user1,
        swap_start=today,
        swap_end=today + timezone.timedelta(days=2),
    )
    if swap_taken:
        swap_request.benefactor = user2
        swap_request.save()

    schedule.refresh_ical_file()

    # setup empty current shifts before checking/triggering for notifications
    schedule.current_shifts = json.dumps({}, default=str)
    schedule.empty_oncall = False
    schedule.save()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    text_block = mock_slack_api_call.call_args_list[0][1]["blocks"][1]["text"]["text"]
    assert "user2" in text_block if swap_taken else "user1" in text_block


@pytest.mark.django_db
def test_current_shift_changes_end_affected_by_swap(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
    make_shift_swap_request,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")
    user2 = make_user(organization=organization, username="user2")

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleWeb,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
    duration = timezone.timedelta(days=3)
    data = {
        "start": today,
        "rotation_start": today,
        "duration": duration,
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_WEEKLY,
        "schedule": schedule,
    }
    on_call_shift = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
    )
    on_call_shift.add_rolling_users([[user1]])

    # setup in progress swap request
    swap_start = today + timezone.timedelta(days=1)
    swap_end = today + timezone.timedelta(days=2)
    make_shift_swap_request(
        schedule,
        user1,
        swap_start=swap_start,
        swap_end=swap_end,
        benefactor=user2,
    )

    schedule.refresh_ical_file()

    # setup empty current shifts before checking/triggering for notifications
    schedule.current_shifts = json.dumps({}, default=str)
    schedule.empty_oncall = False
    schedule.save()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    current_text_block = mock_slack_api_call.call_args_list[0][1]["blocks"][1]["text"]["text"]
    assert "user1" in current_text_block
    assert today.strftime("%Y-%m-%d") in current_text_block
    assert swap_start.strftime("%Y-%m-%d") in current_text_block
    next_text_block = mock_slack_api_call.call_args_list[0][1]["blocks"][2]["text"]["text"]
    assert "user2" in next_text_block
    assert swap_start.strftime("%Y-%m-%d") in next_text_block
    assert swap_end.strftime("%Y-%m-%d") in next_text_block


@pytest.mark.django_db
def test_next_shift_changes_no_triggering_notification(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")
    user2 = make_user(organization=organization, username="user2")

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleCalendar,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    now = timezone.now().replace(microsecond=0)
    start_date_1 = now - datetime.timedelta(days=7, minutes=1)
    data_1 = {
        "start": start_date_1,
        "rotation_start": start_date_1,
        "duration": datetime.timedelta(seconds=3600 * 24),
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
    }
    on_call_shift_1 = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data_1
    )
    on_call_shift_1.add_rolling_users([[user1]])
    on_call_shift_1.schedules.add(schedule)

    start_date_2 = now + datetime.timedelta(minutes=10)
    data_2 = {
        "start": start_date_2,
        "rotation_start": start_date_2,
        "duration": datetime.timedelta(seconds=3600 * 24),
        "priority_level": 2,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
    }
    on_call_shift_2 = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data_2
    )
    on_call_shift_2.add_rolling_users([[user1]])
    on_call_shift_2.schedules.add(schedule)

    schedule.refresh_ical_file()

    # setup current shifts before checking/triggering for notifications
    next_shifts = schedule.final_events(
        now, now + datetime.timedelta(days=MIN_DAYS_TO_LOOKUP_FOR_THE_END_OF_EVENT), False, False
    )
    current_shifts = [e for e in next_shifts if now > e["start"]]
    schedule.current_shifts = json.dumps(current_shifts, default=str)
    schedule.empty_oncall = False
    schedule.save()

    on_call_shift_2.add_rolling_users([[user2]])
    schedule.refresh_ical_file()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    assert not mock_slack_api_call.called


@pytest.mark.django_db
def test_lower_priority_changes_no_triggering_notification(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")
    user2 = make_user(organization=organization, username="user2")

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleCalendar,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    now = timezone.now().replace(microsecond=0)
    start_date = now - datetime.timedelta(days=7, minutes=1)
    data_1 = {
        "start": start_date,
        "rotation_start": start_date,
        "duration": datetime.timedelta(seconds=3600 * 24),
        "priority_level": 2,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
    }
    on_call_shift_1 = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data_1
    )
    on_call_shift_1.add_rolling_users([[user1]])
    on_call_shift_1.schedules.add(schedule)

    data_2 = {
        "start": start_date,
        "rotation_start": start_date,
        "duration": datetime.timedelta(seconds=3600 * 24),
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
    }
    on_call_shift_2 = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data_2
    )
    on_call_shift_2.add_rolling_users([[user1]])
    on_call_shift_2.schedules.add(schedule)

    schedule.refresh_ical_file()

    # setup empty current shifts before checking/triggering for notifications
    current_shifts = schedule.final_events(now, now, False, False)
    schedule.current_shifts = json.dumps(current_shifts, default=str)
    schedule.empty_oncall = False
    schedule.save()

    on_call_shift_2.add_rolling_users([[user2]])
    schedule.refresh_ical_file()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    assert not mock_slack_api_call.called


@pytest.mark.django_db
def test_vtimezone_changes_no_triggering_notification(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    make_user(organization=organization, username="user1")

    ical_before = textwrap.dedent(
        """
        BEGIN:VCALENDAR
        PRODID:-//Google Inc//Google Calendar 70.9054//EN
        VERSION:2.0
        CALSCALE:GREGORIAN
        X-WR-TIMEZONE:Europe/London
        METHOD:PUBLISH
        BEGIN:VTIMEZONE
        TZID:Europe/Rome
        BEGIN:STANDARD
        TZOFFSETFROM:0200
        TZOFFSETTO:0100
        TZNAME:CET
        DTSTART:19701025T030000
        END:STANDARD
        END:VTIMEZONE
        BEGIN:VTIMEZONE
        TZID:America/Argentina/Buenos_Aires
        X-LIC-LOCATION:America/Argentina/Buenos_Aires
        BEGIN:STANDARD
        TZOFFSETFROM:-0300
        TZOFFSETTO:-0300
        TZNAME:-03
        DTSTART:19700101T000000
        END:STANDARD
        END:VTIMEZONE
        BEGIN:VEVENT
        DTSTART;VALUE=DATE:20230101
        DTEND;VALUE=DATE:20230102
        RRULE:FREQ=DAILY
        DTSTAMP:20230101T000000
        UID:id1@google.com
        CREATED:20230101T000000
        DESCRIPTION:
        LAST-MODIFIED:20230101T000000
        LOCATION:
        SEQUENCE:1
        STATUS:CONFIRMED
        SUMMARY:user1
        TRANSP:TRANSPARENT
        END:VEVENT
        END:VCALENDAR"""
    )

    # same data, timezones in different order (eg. google usually randomly reorders them)
    ical_after = textwrap.dedent(
        """
        BEGIN:VCALENDAR
        PRODID:-//Google Inc//Google Calendar 70.9054//EN
        VERSION:2.0
        CALSCALE:GREGORIAN
        METHOD:PUBLISH
        X-WR-TIMEZONE:Europe/London
        BEGIN:VTIMEZONE
        TZID:America/Argentina/Buenos_Aires
        X-LIC-LOCATION:America/Argentina/Buenos_Aires
        BEGIN:STANDARD
        TZOFFSETFROM:-0300
        TZOFFSETTO:-0300
        TZNAME:-03
        DTSTART:19700101T000000
        END:STANDARD
        END:VTIMEZONE
        BEGIN:VTIMEZONE
        TZID:Europe/Rome
        BEGIN:STANDARD
        TZOFFSETFROM:0200
        TZOFFSETTO:0100
        TZNAME:CET
        DTSTART:19701025T030000
        END:STANDARD
        END:VTIMEZONE
        BEGIN:VEVENT
        DTSTART;VALUE=DATE:20230101
        DTEND;VALUE=DATE:20230102
        RRULE:FREQ=DAILY
        DTSTAMP:20230101T000000
        UID:id1@google.com
        CREATED:20230101T000000
        DESCRIPTION:
        LAST-MODIFIED:20230101T000000
        LOCATION:
        SEQUENCE:1
        STATUS:CONFIRMED
        SUMMARY:user1
        TRANSP:TRANSPARENT
        END:VEVENT
        END:VCALENDAR"""
    )

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleICal,
        name="test_ical_schedule",
        channel="channel",
        ical_url_primary="url",
        prev_ical_file_primary=None,
        cached_ical_file_primary=ical_before,
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    # setup current shifts before checking/triggering for notifications
    now = datetime.datetime.now(datetime.timezone.utc)
    current_shifts = schedule.final_events(now, now, False, False)
    schedule.current_shifts = json.dumps(current_shifts, default=str)
    schedule.empty_oncall = False
    # update schedule cached ical to ical_after
    schedule.prev_ical_file_primary = ical_before
    schedule.cached_ical_file_primary = ical_after
    schedule.save()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    assert not mock_slack_api_call.called


@pytest.mark.django_db
def test_no_changes_no_triggering_notification_from_old_to_new_task_version(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleCalendar,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    now = timezone.now().replace(microsecond=0)
    start_date = now - timezone.timedelta(days=7)
    data = {
        "start": start_date,
        "rotation_start": start_date,
        "duration": timezone.timedelta(seconds=3600 * 24),
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
    }
    on_call_shift = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
    )
    on_call_shift.add_rolling_users([[user1]])
    on_call_shift.schedules.add(schedule)

    # setup current shifts with old version of shifts structure before checking/triggering for notifications
    current_shifts = {
        "test_shift_uid": {
            "users": [user1.pk],
            "start": start_date,
            "end": start_date + data["duration"],
            "all_day": False,
            "priority": data["priority_level"],
            "priority_increased_by": 0,
        }
    }
    schedule.current_shifts = json.dumps(current_shifts, default=str)
    schedule.empty_oncall = False
    schedule.save()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    assert not mock_slack_api_call.called


@pytest.mark.django_db
def test_current_shift_changes_trigger_notification_from_old_to_new_task_version(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")
    user2 = make_user(organization=organization, username="user2")

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleCalendar,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    now = timezone.now().replace(microsecond=0)
    start_date = now - datetime.timedelta(days=7)
    data = {
        "start": start_date,
        "rotation_start": start_date,
        "duration": datetime.timedelta(seconds=3600 * 24),
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_DAILY,
    }
    on_call_shift = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
    )
    on_call_shift.add_rolling_users([[user1]])
    on_call_shift.schedules.add(schedule)
    schedule.refresh_ical_file()

    # setup current shifts with old version of shifts structure before checking/triggering for notifications
    current_shifts = {
        "test_shift_uid": {
            "users": [user1.pk],
            "start": start_date,
            "end": start_date + data["duration"],
            "all_day": False,
            "priority": data["priority_level"],
            "priority_increased_by": 0,
        }
    }
    schedule.current_shifts = json.dumps(current_shifts, default=str)
    schedule.empty_oncall = False
    schedule.save()

    on_call_shift.add_rolling_users([[user2]])
    schedule.refresh_ical_file()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    assert mock_slack_api_call.called


@pytest.mark.django_db
def test_next_shift_notification_long_and_short_shifts(
    make_organization_and_user_with_slack_identities,
    make_user,
    make_schedule,
    make_on_call_shift,
):
    organization, _, _, _ = make_organization_and_user_with_slack_identities()
    user1 = make_user(organization=organization, username="user1")
    user2 = make_user(organization=organization, username="user2")
    user3 = make_user(organization=organization, username="user3")

    schedule = make_schedule(
        organization,
        schedule_class=OnCallScheduleWeb,
        name="test_schedule",
        channel="channel",
        prev_ical_file_overrides=None,
        cached_ical_file_overrides=None,
    )

    now = timezone.now().replace(microsecond=0)
    start_date_1 = now - datetime.timedelta(days=1)
    data_1 = {
        "start": start_date_1,
        "rotation_start": start_date_1,
        "duration": datetime.timedelta(seconds=3600 * 24 * 7),  # one week duration
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_WEEKLY,
        "schedule": schedule,
    }
    on_call_shift_1 = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data_1
    )
    on_call_shift_1.add_rolling_users([[user1], [user2]])

    start_date_2 = now - datetime.timedelta(hours=1)
    data_2 = {
        "start": start_date_2,
        "rotation_start": start_date_2,
        "duration": datetime.timedelta(seconds=3600 * 24),
        "priority_level": 1,
        "frequency": CustomOnCallShift.FREQUENCY_WEEKLY,
        "schedule": schedule,
    }
    on_call_shift_2 = make_on_call_shift(
        organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data_2
    )
    on_call_shift_2.add_rolling_users([[user3]])

    schedule.refresh_ical_file()

    # setup empty current shifts before checking/triggering for notifications
    schedule.current_shifts = json.dumps({}, default=str)
    schedule.empty_oncall = False
    schedule.save()

    with patch("apps.slack.client.SlackClient.chat_postMessage") as mock_slack_api_call:
        notify_ical_schedule_shift(schedule.pk)

    assert mock_slack_api_call.called
    new_shift_notification = mock_slack_api_call.call_args[1]["blocks"][1]["text"]["text"]
    next_shift_notification = mock_slack_api_call.call_args[1]["blocks"][2]["text"]["text"]

    assert "*New on-call shift*\n[L1] user1" in new_shift_notification
    assert "[L1] user3" in new_shift_notification
    assert "*Next on-call shift*\n[L1] user2" in next_shift_notification
