import datetime
import json
from unittest.mock import patch

import pytest
from django.urls import reverse
from django.utils import timezone
from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient

from apps.api.permissions import LegacyAccessControlRole
from apps.schedules.models import CustomOnCallShift, OnCallScheduleWeb, ShiftSwapRequest
from common.api_helpers.utils import serialize_datetime_as_utc_timestamp
from common.insight_log import EntityEvent

description = "my shift swap request"
tomorrow = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + datetime.timedelta(days=1)
two_days_from_now = tomorrow + datetime.timedelta(days=1)

mock_success_response = Response(status=status.HTTP_200_OK)


@pytest.fixture
def ssr_setup(
    make_schedule, make_organization_and_user_with_plugin_token, make_user_for_organization, make_shift_swap_request
):
    def _ssr_setup(beneficiary_role=None, benefactor_role=None, **kwargs):
        organization, beneficiary, token = make_organization_and_user_with_plugin_token(role=beneficiary_role)
        benefactor = make_user_for_organization(organization, role=benefactor_role)

        schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb)
        ssr = make_shift_swap_request(schedule, beneficiary, swap_start=tomorrow, swap_end=two_days_from_now, **kwargs)

        return ssr, beneficiary, token, benefactor

    return _ssr_setup


def _construct_serialized_object(
    ssr: ShiftSwapRequest, status="open", description=None, benefactor=None, list_response=False, expand_users=False
):
    data = {
        "id": ssr.public_primary_key,
        "created_at": serialize_datetime_as_utc_timestamp(ssr.created_at),
        "updated_at": serialize_datetime_as_utc_timestamp(ssr.updated_at),
        "schedule": ssr.schedule.public_primary_key,
        "swap_start": serialize_datetime_as_utc_timestamp(ssr.swap_start),
        "swap_end": serialize_datetime_as_utc_timestamp(ssr.swap_end),
        "beneficiary": ssr.beneficiary.public_primary_key,
        "status": status,
        "benefactor": benefactor,
        "description": description,
    }

    if expand_users:

        def _serialized_user(u):
            if u:
                return {
                    "display_name": u.username,
                    "email": u.email,
                    "pk": u.public_primary_key,
                    "avatar_full": u.avatar_full_url(u.organization),
                }

        data["beneficiary"] = _serialized_user(ssr.beneficiary)
        data["benefactor"] = _serialized_user(ssr.benefactor)

    if not list_response:
        data["shifts"] = ssr.shifts()

    return data


def _build_expected_update_response(ssr, modified_data, updated_at_ts, **kwargs):
    """
    updated_at timestamp will obviously be bumped when we do a PUT/PATCH
    """
    return _construct_serialized_object(ssr, **kwargs) | modified_data | {"updated_at": updated_at_ts}


@pytest.mark.django_db
@pytest.mark.parametrize("expand_users", ["true", "false", None])
def test_list(ssr_setup, make_user_auth_headers, expand_users):
    ssr, beneficiary, token, _ = ssr_setup(description=description)
    client = APIClient()
    url = reverse("api-internal:shift_swap-list")
    if expand_users is not None:
        url += "?expand_users={}".format(expand_users)

    expected_payload = {
        "next": None,
        "previous": None,
        "page_size": 50,
        "count": 1,
        "current_page_number": 1,
        "total_pages": 1,
        "results": [
            _construct_serialized_object(
                ssr, description=description, list_response=True, expand_users=expand_users == "true"
            ),
        ],
    }

    response = client.get(url, format="json", **make_user_auth_headers(beneficiary, token))
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == expected_payload


@patch("apps.api.views.shift_swap.ShiftSwapViewSet.list", return_value=mock_success_response)
@pytest.mark.django_db
@pytest.mark.parametrize(
    "role,expected_status",
    [
        (LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
        (LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
        (LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
        (LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
    ],
)
def test_list_permissions(
    mock_endpoint_handler,
    ssr_setup,
    make_user_auth_headers,
    role,
    expected_status,
):
    _, beneficiary, token, _ = ssr_setup(beneficiary_role=role)
    client = APIClient()
    url = reverse("api-internal:shift_swap-list")

    response = client.get(url, format="json", **make_user_auth_headers(beneficiary, token))
    assert response.status_code == expected_status


@pytest.mark.django_db
@pytest.mark.parametrize("expand_users", ["true", "false", None])
def test_retrieve(ssr_setup, make_user_auth_headers, expand_users):
    ssr, beneficiary, token, _ = ssr_setup(description=description)
    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
    if expand_users is not None:
        url += "?expand_users={}".format(expand_users)

    response = client.get(url, format="json", **make_user_auth_headers(beneficiary, token))
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == _construct_serialized_object(
        ssr, description=description, expand_users=expand_users == "true"
    )


@patch("apps.api.views.shift_swap.ShiftSwapViewSet.retrieve", return_value=mock_success_response)
@pytest.mark.django_db
@pytest.mark.parametrize(
    "role,expected_status",
    [
        (LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
        (LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
        (LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
        (LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
    ],
)
def test_retrieve_permissions(
    mock_endpoint_handler,
    ssr_setup,
    make_user_auth_headers,
    role,
    expected_status,
):
    ssr, _, token, benefactor = ssr_setup(benefactor_role=role)
    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})

    response = client.get(url, format="json", **make_user_auth_headers(benefactor, token))
    assert response.status_code == expected_status


@patch("apps.schedules.models.shift_swap_request.write_resource_insight_log")
@patch("apps.schedules.tasks.shift_swaps.create_shift_swap_request_message")
@pytest.mark.django_db
def test_create(
    mock_create_shift_swap_request_message,
    mock_write_resource_insight_log,
    make_organization_and_user_with_plugin_token,
    make_schedule,
    make_user_auth_headers,
):
    organization, user, token = make_organization_and_user_with_plugin_token()
    schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb)

    client = APIClient()
    url = reverse("api-internal:shift_swap-list")

    data = {
        "schedule": schedule.public_primary_key,
        "description": "hellooooo world",
        "swap_start": tomorrow,
        "swap_end": two_days_from_now,
    }
    response = client.post(url, data, format="json", **make_user_auth_headers(user, token))
    ssr = ShiftSwapRequest.objects.get(public_primary_key=response.json()["id"])
    expected_response = _construct_serialized_object(ssr) | {
        **data,
        "swap_start": serialize_datetime_as_utc_timestamp(tomorrow),
        "swap_end": serialize_datetime_as_utc_timestamp(two_days_from_now),
    }

    assert response.status_code == status.HTTP_201_CREATED
    assert response.json() == expected_response

    mock_write_resource_insight_log.assert_called_once_with(instance=ssr, author=user, event=EntityEvent.CREATED)
    mock_create_shift_swap_request_message.apply_async.assert_called_once_with((ssr.pk,))


@pytest.mark.django_db
@pytest.mark.parametrize(
    "swap_start,expected_persisted_value",
    [
        # UTC format
        ("2285-07-20T12:00:00Z", "2285-07-20T12:00:00.000000Z"),
        # UTC format w/ microseconds
        ("2285-07-20T12:00:00.245652Z", "2285-07-20T12:00:00.245652Z"),
        # UTC offset w/ colons + no microseconds
        ("2285-07-20T12:00:00+07:00", "2285-07-20T05:00:00.000000Z"),
        # UTC offset w/ colons + microseconds
        ("2285-07-20T12:00:00.245652+07:00", "2285-07-20T05:00:00.245652Z"),
        # UTC offset w/ no colons + no microseconds
        ("2285-07-20T12:00:00+0700", "2285-07-20T05:00:00.000000Z"),
        # UTC offset w/ no colons + microseconds
        ("2285-07-20T12:00:00.245652+0700", "2285-07-20T05:00:00.245652Z"),
        ("2285-07-20 12:00:00", None),
        ("22850720T120000Z", None),
    ],
)
def test_create_swap_start_and_swap_end_must_include_time_zone(
    make_organization_and_user_with_plugin_token,
    make_schedule,
    make_user_auth_headers,
    swap_start,
    expected_persisted_value,
):
    organization, user, token = make_organization_and_user_with_plugin_token()
    schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb)

    client = APIClient()
    url = reverse("api-internal:shift_swap-list")

    start_year = "2285"
    end_year = "2286"
    swap_end = swap_start.replace(start_year, end_year)

    data = {
        "schedule": schedule.public_primary_key,
        "description": "hellooooo world",
        "swap_start": swap_start,
        "swap_end": swap_end,
    }
    response = client.post(url, data, format="json", **make_user_auth_headers(user, token))

    if expected_persisted_value:
        ssr = ShiftSwapRequest.objects.get(public_primary_key=response.json()["id"])

        assert response.status_code == status.HTTP_201_CREATED
        assert response.json() == _construct_serialized_object(ssr) | {
            **data,
            "swap_start": expected_persisted_value,
            "swap_end": expected_persisted_value.replace(start_year, end_year),
        }
    else:
        assert response.status_code == status.HTTP_400_BAD_REQUEST


@patch("apps.api.views.shift_swap.ShiftSwapViewSet.create", return_value=mock_success_response)
@pytest.mark.django_db
@pytest.mark.parametrize(
    "role,expected_status",
    [
        (LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
        (LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
        (LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
        (LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
    ],
)
def test_create_permissions(
    mock_endpoint_handler,
    ssr_setup,
    make_user_auth_headers,
    role,
    expected_status,
):
    _, _, token, benefactor = ssr_setup(benefactor_role=role)
    client = APIClient()
    url = reverse("api-internal:shift_swap-list")

    response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
    assert response.status_code == expected_status


@patch("apps.api.views.shift_swap.write_resource_insight_log")
@patch("apps.api.views.shift_swap.update_shift_swap_request_message")
@pytest.mark.django_db
def test_update(
    mock_update_shift_swap_request_message, mock_write_resource_insight_log, ssr_setup, make_user_auth_headers
):
    ssr, beneficiary, token, _ = ssr_setup(description=description)
    insights_log_prev_state = ssr.insight_logs_serialized

    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
    auth_headers = make_user_auth_headers(beneficiary, token)

    data = {
        "description": "hellooooo world",
        "schedule": ssr.schedule.public_primary_key,
        "swap_start": serialize_datetime_as_utc_timestamp(ssr.swap_start),
        "swap_end": serialize_datetime_as_utc_timestamp(ssr.swap_end),
    }

    response = client.put(url, data=json.dumps(data), content_type="application/json", **auth_headers)
    response_json = response.json()
    expected_response = _build_expected_update_response(ssr, data, response_json["updated_at"], description=description)
    assert response.status_code == status.HTTP_200_OK
    assert response_json == expected_response

    response = client.get(url, format="json", **auth_headers)
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == expected_response

    ssr.refresh_from_db()
    mock_write_resource_insight_log.assert_called_once_with(
        instance=ssr,
        author=beneficiary,
        event=EntityEvent.UPDATED,
        prev_state=insights_log_prev_state,
        new_state=ssr.insight_logs_serialized,
    )

    mock_update_shift_swap_request_message.apply_async.assert_called_once_with((ssr.pk,))


@pytest.mark.django_db
@pytest.mark.parametrize(
    "swap_start,expected_persisted_value",
    [
        # UTC format
        ("2285-07-20T12:00:00Z", "2285-07-20T12:00:00.000000Z"),
        # UTC format w/ microseconds
        ("2285-07-20T12:00:00.245652Z", "2285-07-20T12:00:00.245652Z"),
        # UTC offset w/ colons + no microseconds
        ("2285-07-20T12:00:00+07:00", "2285-07-20T05:00:00.000000Z"),
        # UTC offset w/ colons + microseconds
        ("2285-07-20T12:00:00.245652+07:00", "2285-07-20T05:00:00.245652Z"),
        # UTC offset w/ no colons + no microseconds
        ("2285-07-20T12:00:00+0700", "2285-07-20T05:00:00.000000Z"),
        # UTC offset w/ no colons + microseconds
        ("2285-07-20T12:00:00.245652+0700", "2285-07-20T05:00:00.245652Z"),
        ("2285-07-20 12:00:00", None),
        ("22850720T120000Z", None),
    ],
)
def test_update_swap_start_and_swap_end_must_include_time_zone(
    ssr_setup,
    make_user_auth_headers,
    swap_start,
    expected_persisted_value,
):
    ssr, beneficiary, token, _ = ssr_setup()

    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})

    start_year = "2285"
    end_year = "2286"
    swap_end = swap_start.replace(start_year, end_year)

    data = {
        "schedule": ssr.schedule.public_primary_key,
        "swap_start": swap_start,
        "swap_end": swap_end,
    }
    response = client.put(url, data, format="json", **make_user_auth_headers(beneficiary, token))

    if expected_persisted_value:
        ssr = ShiftSwapRequest.objects.get(public_primary_key=response.json()["id"])

        assert response.status_code == status.HTTP_200_OK
        assert response.json() == _construct_serialized_object(ssr) | {
            **data,
            "swap_start": expected_persisted_value,
            "swap_end": expected_persisted_value.replace(start_year, end_year),
        }
    else:
        assert response.status_code == status.HTTP_400_BAD_REQUEST


@pytest.mark.django_db
@pytest.mark.parametrize(
    "role,expected_status",
    [
        (LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
        (LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
        (LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
        (LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
    ],
)
def test_update_own_ssr_permissions(ssr_setup, make_user_auth_headers, role, expected_status):
    ssr, beneficiary, token, _ = ssr_setup(beneficiary_role=role)
    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})

    data = {
        "description": "hellooooo world",
        "schedule": ssr.schedule.public_primary_key,
        "swap_start": serialize_datetime_as_utc_timestamp(ssr.swap_start),
        "swap_end": serialize_datetime_as_utc_timestamp(ssr.swap_end),
    }

    response = client.put(
        url, data=json.dumps(data), content_type="application/json", **make_user_auth_headers(beneficiary, token)
    )
    assert response.status_code == expected_status


@pytest.mark.django_db
def test_update_others_ssr_permissions(ssr_setup, make_user_auth_headers):
    ssr, _, token, benefactor = ssr_setup()
    assert benefactor.role == LegacyAccessControlRole.ADMIN

    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})

    response = client.put(
        url, data=json.dumps({}), content_type="application/json", **make_user_auth_headers(benefactor, token)
    )
    assert response.status_code == status.HTTP_403_FORBIDDEN


@patch("apps.api.views.shift_swap.write_resource_insight_log")
@patch("apps.api.views.shift_swap.update_shift_swap_request_message")
@pytest.mark.django_db
def test_partial_update(
    mock_update_shift_swap_request_message, mock_write_resource_insight_log, ssr_setup, make_user_auth_headers
):
    ssr, beneficiary, token, _ = ssr_setup(description=description)
    insights_log_prev_state = ssr.insight_logs_serialized

    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
    auth_headers = make_user_auth_headers(beneficiary, token)

    data = {"description": "this is a shift swap request"}

    response = client.patch(url, data=json.dumps(data), content_type="application/json", **auth_headers)
    response_json = response.json()
    expected_response = _build_expected_update_response(ssr, data, response_json["updated_at"], description=description)
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == expected_response

    response = client.get(url, format="json", **auth_headers)
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == expected_response

    ssr.refresh_from_db()
    mock_write_resource_insight_log.assert_called_once_with(
        instance=ssr,
        author=beneficiary,
        event=EntityEvent.UPDATED,
        prev_state=insights_log_prev_state,
        new_state=ssr.insight_logs_serialized,
    )

    mock_update_shift_swap_request_message.apply_async.assert_called_once_with((ssr.pk,))


@pytest.mark.django_db
def test_partial_update_time_related_fields(ssr_setup, make_user_auth_headers):
    ssr, beneficiary, token, _ = ssr_setup()
    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
    auth_headers = make_user_auth_headers(beneficiary, token)

    # but if we do PATCH a time related field, we must specify all the time fields
    swap_start = {"swap_start": serialize_datetime_as_utc_timestamp(tomorrow + datetime.timedelta(days=5))}
    swap_end = {"swap_end": serialize_datetime_as_utc_timestamp(tomorrow + datetime.timedelta(days=10))}
    valid = swap_start | swap_end

    for case in [swap_start, swap_end]:
        response = client.patch(url, data=json.dumps(case), content_type="application/json", **auth_headers)
        assert response.status_code == status.HTTP_400_BAD_REQUEST

    # valid way to patch time related fields
    response = client.patch(url, data=json.dumps(valid), content_type="application/json", **auth_headers)
    response_json = response.json()
    expected_response = _build_expected_update_response(ssr, valid, response_json["updated_at"])
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == expected_response

    response = client.get(url, format="json", **auth_headers)
    assert response.status_code == status.HTTP_200_OK
    assert response.json() == expected_response


@pytest.mark.django_db
def test_related_shifts(ssr_setup, make_on_call_shift, make_user_auth_headers):
    ssr, beneficiary, token, _ = ssr_setup()

    schedule = ssr.schedule
    organization = schedule.organization
    user = beneficiary

    today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
    start = today + timezone.timedelta(days=1)
    duration = timezone.timedelta(hours=8)
    data = {
        "start": start,
        "rotation_start": start,
        "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([[user]])

    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
    auth_headers = make_user_auth_headers(beneficiary, token)
    response = client.get(url, **auth_headers)

    assert response.status_code == status.HTTP_200_OK
    response_json = response.json()
    expected = [
        # start, end, user, swap request ID
        (
            start.strftime("%Y-%m-%dT%H:%M:%SZ"),
            (start + duration).strftime("%Y-%m-%dT%H:%M:%SZ"),
            user.public_primary_key,
            ssr.public_primary_key,
        ),
    ]
    returned_events = [
        (e["start"], e["end"], e["users"][0]["pk"], e["users"][0]["swap_request"]["pk"])
        for e in response_json["shifts"]
    ]
    assert returned_events == expected


@pytest.mark.django_db
@pytest.mark.parametrize(
    "role,expected_status",
    [
        (LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
        (LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
        (LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
        (LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
    ],
)
def test_partial_update_own_ssr_permissions(ssr_setup, make_user_auth_headers, role, expected_status):
    ssr, beneficiary, token, _ = ssr_setup(beneficiary_role=role)
    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})

    response = client.patch(
        url,
        data=json.dumps({"description": "foo"}),
        content_type="application/json",
        **make_user_auth_headers(beneficiary, token),
    )
    assert response.status_code == expected_status


@pytest.mark.django_db
def test_partial_update_others_ssr_permissions(ssr_setup, make_user_auth_headers):
    ssr, _, token, benefactor = ssr_setup()
    assert benefactor.role == LegacyAccessControlRole.ADMIN

    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})

    response = client.patch(
        url, data=json.dumps({}), content_type="application/json", **make_user_auth_headers(benefactor, token)
    )
    assert response.status_code == status.HTTP_403_FORBIDDEN


@pytest.mark.django_db
def test_benefactor_and_beneficiary_are_read_only_fields(ssr_setup, make_user_auth_headers):
    ssr, beneficiary, token, benefactor = ssr_setup(description=description)

    client = APIClient()
    list_url = reverse("api-internal:shift_swap-list")
    detail_url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
    auth_headers = make_user_auth_headers(beneficiary, token)

    base_data = {
        "description": "hellooooo world",
        "schedule": ssr.schedule.public_primary_key,
        "swap_start": serialize_datetime_as_utc_timestamp(ssr.swap_start),
        "swap_end": serialize_datetime_as_utc_timestamp(ssr.swap_end),
    }

    update_beneficiary = {"beneficiary": benefactor.public_primary_key}
    update_benefactor = {"benefactor": beneficiary.public_primary_key}

    def _assert_beneficiary_hasnt_changed(resp):
        assert resp.json()["beneficiary"] == beneficiary.public_primary_key

    def _assert_benefactor_is_still_none(resp):
        assert resp.json()["benefactor"] is None

    response = client.post(
        list_url, data=json.dumps(base_data | update_beneficiary), content_type="application/json", **auth_headers
    )
    _assert_beneficiary_hasnt_changed(response)

    response = client.post(
        list_url, data=json.dumps(base_data | update_benefactor), content_type="application/json", **auth_headers
    )
    _assert_benefactor_is_still_none(response)

    response = client.put(
        detail_url, data=json.dumps(base_data | update_beneficiary), content_type="application/json", **auth_headers
    )
    _assert_beneficiary_hasnt_changed(response)

    response = client.put(
        detail_url, data=json.dumps(base_data | update_benefactor), content_type="application/json", **auth_headers
    )
    _assert_benefactor_is_still_none(response)

    response = client.patch(
        detail_url, data=json.dumps(base_data | update_beneficiary), content_type="application/json", **auth_headers
    )
    _assert_beneficiary_hasnt_changed(response)

    response = client.patch(
        detail_url, data=json.dumps(base_data | update_benefactor), content_type="application/json", **auth_headers
    )
    _assert_benefactor_is_still_none(response)


@patch("apps.api.views.shift_swap.write_resource_insight_log")
@patch("apps.api.views.shift_swap.update_shift_swap_request_message")
@pytest.mark.django_db
def test_delete(
    mock_update_shift_swap_request_message, mock_write_resource_insight_log, ssr_setup, make_user_auth_headers
):
    ssr, beneficiary, token, _ = ssr_setup()
    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
    auth_headers = make_user_auth_headers(beneficiary, token)

    response = client.delete(url, **auth_headers)
    assert response.status_code == status.HTTP_204_NO_CONTENT

    response = client.get(url, format="json", **auth_headers)
    assert response.status_code == status.HTTP_404_NOT_FOUND

    mock_write_resource_insight_log.assert_called_once_with(
        instance=ssr,
        author=beneficiary,
        event=EntityEvent.DELETED,
    )

    mock_update_shift_swap_request_message.apply_async.assert_called_once_with((ssr.pk,))


@pytest.mark.django_db
@pytest.mark.parametrize(
    "role,expected_status",
    [
        (LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
        (LegacyAccessControlRole.EDITOR, status.HTTP_204_NO_CONTENT),
        (LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
        (LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
    ],
)
def test_delete_own_ssr_permissions(ssr_setup, make_user_auth_headers, role, expected_status):
    ssr, beneficiary, token, _ = ssr_setup(beneficiary_role=role)
    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})

    response = client.delete(url, format="json", **make_user_auth_headers(beneficiary, token))
    assert response.status_code == expected_status


@pytest.mark.django_db
def test_delete_others_ssr_permissions(ssr_setup, make_user_auth_headers):
    ssr, _, token, benefactor = ssr_setup()
    assert benefactor.role == LegacyAccessControlRole.ADMIN

    client = APIClient()
    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})

    response = client.delete(url, format="json", **make_user_auth_headers(benefactor, token))
    assert response.status_code == status.HTTP_403_FORBIDDEN


@pytest.mark.django_db
def test_take(ssr_setup, make_user_auth_headers):
    ssr, _, token, benefactor = ssr_setup()
    client = APIClient()
    url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})
    auth_headers = make_user_auth_headers(benefactor, token)

    response = client.post(url, format="json", **auth_headers)
    response_json = response.json()
    updated_at = response_json["updated_at"]
    expected_response = _build_expected_update_response(
        ssr, {}, updated_at, status="taken", benefactor=benefactor.public_primary_key
    )

    assert response.status_code == status.HTTP_200_OK
    assert response_json == expected_response
    assert updated_at != serialize_datetime_as_utc_timestamp(
        ssr.updated_at
    )  # validate that updated_at is auto-updated on take

    url = reverse("api-internal:shift_swap-detail", kwargs={"pk": ssr.public_primary_key})
    response = client.get(url, format="json", **auth_headers)
    response_json = response.json()

    assert response.status_code == status.HTTP_200_OK
    assert response_json == expected_response


@pytest.mark.django_db
def test_benficiary_tries_to_take_their_own_ssr(ssr_setup, make_user_auth_headers):
    ssr, beneficiary, token, _ = ssr_setup()
    client = APIClient()
    url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})

    response = client.post(url, format="json", **make_user_auth_headers(beneficiary, token))
    assert response.status_code == status.HTTP_400_BAD_REQUEST


@pytest.mark.django_db
def test_take_already_taken_ssr(ssr_setup, make_user_auth_headers):
    ssr, _, token, benefactor = ssr_setup()
    client = APIClient()
    url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})

    response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
    assert response.status_code == status.HTTP_200_OK

    # try to take the SSR again
    response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
    assert response.status_code == status.HTTP_400_BAD_REQUEST


@pytest.mark.django_db
def test_take_past_due_ssr(ssr_setup, make_user_auth_headers):
    ssr, _, token, benefactor = ssr_setup()
    client = APIClient()
    url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})

    ssr.swap_start = tomorrow - datetime.timedelta(days=5)
    ssr.save()

    response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
    assert response.status_code == status.HTTP_400_BAD_REQUEST


@pytest.mark.django_db
def test_take_deleted_ssr(ssr_setup, make_user_auth_headers):
    ssr, _, token, benefactor = ssr_setup()
    client = APIClient()
    url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})

    ssr.delete()

    response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
    assert response.status_code == status.HTTP_404_NOT_FOUND


@patch("apps.api.views.shift_swap.ShiftSwapViewSet.take", return_value=mock_success_response)
@pytest.mark.django_db
@pytest.mark.parametrize(
    "role,expected_status",
    [
        (LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
        (LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
        (LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
        (LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
    ],
)
def test_take_permissions(
    mock_endpoint_handler,
    ssr_setup,
    make_user_auth_headers,
    role,
    expected_status,
):
    ssr, _, token, benefactor = ssr_setup(benefactor_role=role)
    client = APIClient()
    url = reverse("api-internal:shift_swap-take", kwargs={"pk": ssr.public_primary_key})

    response = client.post(url, format="json", **make_user_auth_headers(benefactor, token))
    assert response.status_code == expected_status
