import json
from datetime import timedelta
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.test import APIClient

from apps.api.permissions import LegacyAccessControlRole
from apps.base.models import UserNotificationPolicy
from apps.base.tests.messaging_backend import TestOnlyBackend

DEFAULT_NOTIFICATION_CHANNEL = UserNotificationPolicy.NotificationChannel.SLACK


@pytest.fixture()
def user_notification_policy_internal_api_setup(
    make_organization_and_user_with_plugin_token, make_user_for_organization, make_user_notification_policy
):
    organization, admin, token = make_organization_and_user_with_plugin_token()
    user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)

    wait_notification_step = make_user_notification_policy(
        admin, UserNotificationPolicy.Step.WAIT, wait_delay=timezone.timedelta(minutes=15), important=False
    )
    notify_notification_step = make_user_notification_policy(
        admin,
        UserNotificationPolicy.Step.NOTIFY,
        notify_by=UserNotificationPolicy.NotificationChannel.SLACK,
        important=False,
    )

    important_notification_step = make_user_notification_policy(
        admin,
        UserNotificationPolicy.Step.NOTIFY,
        notify_by=UserNotificationPolicy.NotificationChannel.SLACK,
        important=True,
    )

    second_user_step = make_user_notification_policy(
        user,
        UserNotificationPolicy.Step.NOTIFY,
        notify_by=UserNotificationPolicy.NotificationChannel.SLACK,
        important=False,
    )
    steps = wait_notification_step, notify_notification_step, important_notification_step, second_user_step
    users = admin, user
    return token, steps, users


@pytest.mark.django_db
def test_create_notification_policy(user_notification_policy_internal_api_setup, make_user_auth_headers):
    token, _, users = user_notification_policy_internal_api_setup
    admin, _ = users
    client = APIClient()
    url = reverse("api-internal:notification_policy-list")

    data = {
        "step": UserNotificationPolicy.Step.NOTIFY,
        "notify_by": UserNotificationPolicy.NotificationChannel.SLACK,
        "wait_delay": None,
        "important": False,
        "user": admin.public_primary_key,
    }
    response = client.post(url, data, format="json", **make_user_auth_headers(admin, token))
    assert response.status_code == status.HTTP_201_CREATED


@pytest.mark.django_db
@pytest.mark.parametrize("wait_delay", (timedelta(seconds=59), timedelta(hours=24, seconds=1)))
def test_create_notification_policy_wait_delay_invalid(
    user_notification_policy_internal_api_setup, make_user_auth_headers, wait_delay
):
    token, _, users = user_notification_policy_internal_api_setup
    admin, _ = users
    client = APIClient()
    url = reverse("api-internal:notification_policy-list")

    data = {
        "step": UserNotificationPolicy.Step.WAIT,
        "wait_delay": str(wait_delay.total_seconds()),
        "important": False,
        "user": admin.public_primary_key,
    }
    response = client.post(url, data, format="json", **make_user_auth_headers(admin, token))
    assert response.status_code == status.HTTP_400_BAD_REQUEST


@pytest.mark.django_db
def test_admin_can_create_notification_policy_for_user(
    user_notification_policy_internal_api_setup, make_user_auth_headers
):
    token, _, users = user_notification_policy_internal_api_setup
    admin, user = users
    client = APIClient()
    url = reverse("api-internal:notification_policy-list")

    data = {
        "step": UserNotificationPolicy.Step.NOTIFY,
        "notify_by": UserNotificationPolicy.NotificationChannel.SLACK,
        "wait_delay": None,
        "important": False,
        "user": user.public_primary_key,
    }
    response = client.post(url, data, format="json", **make_user_auth_headers(admin, token))
    assert response.status_code == status.HTTP_201_CREATED


@pytest.mark.django_db
def test_user_cant_create_notification_policy_for_user(
    user_notification_policy_internal_api_setup,
    make_user_auth_headers,
):
    token, _, users = user_notification_policy_internal_api_setup
    admin, user = users

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

    data = {
        "step": UserNotificationPolicy.Step.NOTIFY,
        "notify_by": UserNotificationPolicy.NotificationChannel.SLACK,
        "wait_delay": None,
        "important": False,
        "user": admin.public_primary_key,
    }
    response = client.post(url, data, format="json", **make_user_auth_headers(user, token))
    assert response.status_code == status.HTTP_403_FORBIDDEN


@pytest.mark.django_db
def test_create_notification_policy_order_is_ignored(
    user_notification_policy_internal_api_setup,
    make_user_auth_headers,
):
    token, steps, users = user_notification_policy_internal_api_setup
    wait_notification_step, _, _, _ = steps
    admin, _ = users
    client = APIClient()
    url = reverse("api-internal:notification_policy-list")

    data = {
        "position": 2023,
        "step": UserNotificationPolicy.Step.NOTIFY,
        "notify_by": UserNotificationPolicy.NotificationChannel.SLACK,
        "wait_delay": None,
        "important": False,
        "user": admin.public_primary_key,
    }
    response = client.post(url, data, format="json", **make_user_auth_headers(admin, token))
    assert response.status_code == status.HTTP_201_CREATED
    assert response.data["order"] == 2


@pytest.mark.django_db
def test_move_to_position_position_error(user_notification_policy_internal_api_setup, make_user_auth_headers):
    token, steps, users = user_notification_policy_internal_api_setup
    admin, _ = users
    step = steps[0]
    client = APIClient()
    url = reverse("api-internal:notification_policy-move-to-position", kwargs={"pk": step.public_primary_key})

    # position value only can be 0 or 1 for this test setup, because there are only 2 steps
    response = client.put(f"{url}?position=2", content_type="application/json", **make_user_auth_headers(admin, token))
    assert response.status_code == status.HTTP_400_BAD_REQUEST


@pytest.mark.django_db
def test_update_step(user_notification_policy_internal_api_setup, make_user_auth_headers):
    token, steps, users = user_notification_policy_internal_api_setup
    admin, _ = users

    _, notify_notification_step, _, _ = steps
    client = APIClient()
    url = reverse("api-internal:notification_policy-detail", kwargs={"pk": notify_notification_step.public_primary_key})

    response = client.patch(
        url,
        data=json.dumps({"notify_by": UserNotificationPolicy.NotificationChannel.PHONE_CALL}),
        content_type="application/json",
        **make_user_auth_headers(admin, token),
    )
    assert response.status_code == status.HTTP_200_OK
    assert response.data["notify_by"] == UserNotificationPolicy.NotificationChannel.PHONE_CALL


@pytest.mark.django_db
def test_admin_can_update_user_step(user_notification_policy_internal_api_setup, make_user_auth_headers):
    token, steps, users = user_notification_policy_internal_api_setup
    admin, _ = users
    _, _, _, second_user_step = steps
    client = APIClient()
    url = reverse("api-internal:notification_policy-detail", kwargs={"pk": second_user_step.public_primary_key})

    response = client.patch(
        url,
        data=json.dumps({"notify_by": UserNotificationPolicy.NotificationChannel.PHONE_CALL}),
        content_type="application/json",
        **make_user_auth_headers(admin, token),
    )
    assert response.status_code == status.HTTP_200_OK
    assert response.data["notify_by"] == UserNotificationPolicy.NotificationChannel.PHONE_CALL


@pytest.mark.django_db
def test_user_cant_update_admin_step(
    user_notification_policy_internal_api_setup,
    make_user_auth_headers,
):
    token, steps, users = user_notification_policy_internal_api_setup
    _, user = users

    admin_step, _, _, _ = steps
    client = APIClient()
    url = reverse("api-internal:notification_policy-detail", kwargs={"pk": admin_step.public_primary_key})

    response = client.patch(
        url,
        data=json.dumps({"notify_by": UserNotificationPolicy.NotificationChannel.PHONE_CALL}),
        content_type="application/json",
        **make_user_auth_headers(user, token),
    )
    assert response.status_code == status.HTTP_403_FORBIDDEN


@pytest.mark.django_db
def test_admin_can_move_user_step(user_notification_policy_internal_api_setup, make_user_auth_headers):
    token, steps, users = user_notification_policy_internal_api_setup
    admin, _ = users
    _, _, _, second_user_step = steps
    client = APIClient()
    url = reverse(
        "api-internal:notification_policy-move-to-position", kwargs={"pk": second_user_step.public_primary_key}
    )

    response = client.put(f"{url}?position=0", content_type="application/json", **make_user_auth_headers(admin, token))
    assert response.status_code == status.HTTP_200_OK


@pytest.mark.django_db
def test_user_cant_move_admin_step(user_notification_policy_internal_api_setup, make_user_auth_headers):
    token, steps, users = user_notification_policy_internal_api_setup
    _, user = users

    admin_step, _, _, _ = steps
    client = APIClient()
    url = reverse("api-internal:notification_policy-move-to-position", kwargs={"pk": admin_step.public_primary_key})

    response = client.put(f"{url}?position=1", content_type="application/json", **make_user_auth_headers(user, token))
    assert response.status_code == status.HTTP_403_FORBIDDEN


@pytest.mark.django_db
def test_unable_to_change_importance(user_notification_policy_internal_api_setup, make_user_auth_headers):
    token, steps, users = user_notification_policy_internal_api_setup
    admin, _ = users
    _, notify_notification_step, _, _ = steps
    client = APIClient()
    url = reverse("api-internal:notification_policy-detail", kwargs={"pk": notify_notification_step.public_primary_key})

    data = {
        "important": True,
    }
    response = client.patch(
        url, data=json.dumps(data), content_type="application/json", **make_user_auth_headers(admin, token)
    )
    notify_notification_step.refresh_from_db()

    assert response.status_code == status.HTTP_200_OK
    assert notify_notification_step.important != data["important"]


@pytest.mark.django_db
@pytest.mark.parametrize("wait_delay, expected_wait_delay", [(None, "300.0"), ("900.0", "900.0")])
def test_switch_step_type_from_notify_to_wait(
    make_organization_and_user_with_plugin_token,
    make_user_auth_headers,
    make_user_notification_policy,
    wait_delay,
    expected_wait_delay,
):
    organization, admin, token = make_organization_and_user_with_plugin_token()

    notify_notification_step = make_user_notification_policy(
        admin,
        UserNotificationPolicy.Step.NOTIFY,
        notify_by=UserNotificationPolicy.NotificationChannel.SLACK,
        important=False,
    )
    client = APIClient()
    url = reverse("api-internal:notification_policy-detail", kwargs={"pk": notify_notification_step.public_primary_key})

    data = {
        "id": notify_notification_step.public_primary_key,
        "important": False,
        "notify_by": None,
        "order": 0,
        "user": admin.public_primary_key,
        "step": UserNotificationPolicy.Step.WAIT,
        "wait_delay": wait_delay,
    }

    expected_response = {
        "id": notify_notification_step.public_primary_key,
        "important": False,
        "notify_by": None,
        "order": 0,
        "user": admin.public_primary_key,
        "step": UserNotificationPolicy.Step.WAIT,
        "wait_delay": expected_wait_delay,
    }
    response = client.put(
        url, data=json.dumps(data), content_type="application/json", **make_user_auth_headers(admin, token)
    )
    notify_notification_step.refresh_from_db()

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


@pytest.mark.django_db
@pytest.mark.parametrize(
    "notification_channel, expected_notification_channel",
    [
        (None, DEFAULT_NOTIFICATION_CHANNEL),
        (
            UserNotificationPolicy.NotificationChannel.PHONE_CALL,
            UserNotificationPolicy.NotificationChannel.PHONE_CALL,
        ),
    ],
)
def test_switch_step_type_wait_to_notify(
    make_organization_and_user_with_plugin_token,
    make_user_auth_headers,
    make_user_notification_policy,
    notification_channel,
    expected_notification_channel,
):
    organization, admin, token = make_organization_and_user_with_plugin_token()

    wait_notification_step = make_user_notification_policy(
        admin,
        UserNotificationPolicy.Step.WAIT,
        important=False,
    )

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

    data = {
        "id": wait_notification_step.public_primary_key,
        "important": False,
        "notify_by": notification_channel,
        "order": 0,
        "user": admin.public_primary_key,
        "step": UserNotificationPolicy.Step.NOTIFY,
        "wait_delay": None,
    }

    expected_response = {
        "id": wait_notification_step.public_primary_key,
        "important": False,
        "notify_by": expected_notification_channel,
        "order": 0,
        "user": admin.public_primary_key,
        "step": UserNotificationPolicy.Step.NOTIFY,
        "wait_delay": None,
    }
    response = client.put(
        url, data=json.dumps(data), content_type="application/json", **make_user_auth_headers(admin, token)
    )

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


@pytest.mark.django_db
def test_switch_notification_channel(
    make_organization_and_user_with_plugin_token,
    make_user_auth_headers,
    make_user_notification_policy,
):
    organization, admin, token = make_organization_and_user_with_plugin_token()

    notify_notification_step = make_user_notification_policy(
        admin,
        UserNotificationPolicy.Step.NOTIFY,
        notify_by=UserNotificationPolicy.NotificationChannel.SLACK,
        important=False,
    )
    client = APIClient()
    url = reverse("api-internal:notification_policy-detail", kwargs={"pk": notify_notification_step.public_primary_key})

    data = {
        "id": notify_notification_step.public_primary_key,
        "important": False,
        "notify_by": UserNotificationPolicy.NotificationChannel.PHONE_CALL,
        "order": 0,
        "user": admin.public_primary_key,
        "step": UserNotificationPolicy.Step.NOTIFY,
        "wait_delay": None,
    }

    expected_response = {
        "id": notify_notification_step.public_primary_key,
        "important": False,
        "notify_by": UserNotificationPolicy.NotificationChannel.PHONE_CALL,
        "order": 0,
        "user": admin.public_primary_key,
        "step": UserNotificationPolicy.Step.NOTIFY,
        "wait_delay": None,
    }
    response = client.put(
        url, data=json.dumps(data), content_type="application/json", **make_user_auth_headers(admin, token)
    )

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


@pytest.mark.django_db
@pytest.mark.parametrize(
    "from_wait_delay, to_wait_delay", [(None, "300.0"), (timezone.timedelta(seconds=900), "900.0")]
)
def test_switch_wait_delay(
    make_organization_and_user_with_plugin_token,
    make_user_auth_headers,
    make_user_notification_policy,
    from_wait_delay,
    to_wait_delay,
):
    organization, admin, token = make_organization_and_user_with_plugin_token()
    wait_notification_step = make_user_notification_policy(
        admin, UserNotificationPolicy.Step.WAIT, wait_delay=from_wait_delay, important=False
    )
    client = APIClient()
    url = reverse("api-internal:notification_policy-detail", kwargs={"pk": wait_notification_step.public_primary_key})

    data = {
        "id": wait_notification_step.public_primary_key,
        "important": False,
        "notify_by": DEFAULT_NOTIFICATION_CHANNEL,
        "order": 0,
        "user": admin.public_primary_key,
        "step": UserNotificationPolicy.Step.NOTIFY,
        "wait_delay": to_wait_delay,
    }

    expected_response = {
        "id": wait_notification_step.public_primary_key,
        "important": False,
        "notify_by": DEFAULT_NOTIFICATION_CHANNEL,
        "order": 0,
        "user": admin.public_primary_key,
        "step": UserNotificationPolicy.Step.NOTIFY,
        "wait_delay": to_wait_delay,
    }
    response = client.put(
        url, data=json.dumps(data), content_type="application/json", **make_user_auth_headers(admin, token)
    )

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


@pytest.mark.django_db
def test_notification_policy_backends_enabled(
    user_notification_policy_internal_api_setup, settings, make_user_auth_headers
):
    token, _, users = user_notification_policy_internal_api_setup
    admin, _ = users

    client = APIClient()
    url = reverse("api-internal:notification_policy-notify-by-options")

    response = client.get(url, **make_user_auth_headers(admin, token))
    assert response.status_code == status.HTTP_200_OK
    options = [opt["display_name"] for opt in response.json()]
    assert "Test Only Backend" in options


@pytest.mark.django_db
def test_notification_policy_backends_disabled_for_organization(
    user_notification_policy_internal_api_setup, settings, make_user_auth_headers
):
    token, _, users = user_notification_policy_internal_api_setup
    admin, _ = users

    client = APIClient()
    url = reverse("api-internal:notification_policy-notify-by-options")

    with patch.object(TestOnlyBackend, "is_enabled_for_organization", return_value=False):
        response = client.get(url, **make_user_auth_headers(admin, token))

    assert response.status_code == status.HTTP_200_OK

    options = [opt["display_name"] for opt in response.json()]
    assert "Test Only Backend" not in options
