"""Tests: notification FCM/WebSocket dispatch runs off the request path."""

import threading
from unittest.mock import patch

import pytest
from django.contrib.auth import get_user_model

from apps.notifications.handler import NotificationHandler
from apps.notifications.models import Notification

User = get_user_model()


@pytest.fixture
def user(db):
    return User.objects.create_user(email="n@test.local", password="x")


@pytest.mark.django_db
class TestSendDispatch:
    @pytest.fixture(autouse=True)
    def eager(self, settings):
        settings.BACKGROUND_TASKS_EAGER = True

    # Dispatch is deferred to transaction.on_commit, so tests must capture
    # and execute the callbacks (the django_db transaction never commits).
    def test_send_creates_notification_and_pushes(self, user, django_capture_on_commit_callbacks):
        with patch("apps.notifications.fcm.send_to_user", return_value=1) as mock_push:
            with django_capture_on_commit_callbacks(execute=True):
                n = NotificationHandler.send(user=user, title="T", message="M")
        assert Notification.objects.filter(id=n.id).exists()
        mock_push.assert_called_once()
        assert mock_push.call_args.kwargs["title"] == "T"

    def test_send_survives_fcm_failure(self, user, django_capture_on_commit_callbacks):
        with patch("apps.notifications.fcm.send_to_user", side_effect=RuntimeError("boom")):
            with django_capture_on_commit_callbacks(execute=True):
                n = NotificationHandler.send(user=user, title="T", message="M")
        assert Notification.objects.filter(id=n.id).exists()

    def test_broadcast_pushes_each_user(self, user, django_capture_on_commit_callbacks):
        other = User.objects.create_user(email="n2@test.local", password="x")
        with patch("apps.notifications.fcm.send_to_users", return_value=2) as mock_push:
            with django_capture_on_commit_callbacks(execute=True):
                created = NotificationHandler.broadcast(users=[user, other], title="T", message="M")
        assert len(created) == 2
        mock_push.assert_called_once()
        payloads = mock_push.call_args.args[0]
        assert {p["user"].id for p in payloads} == {user.id, other.id}


# transaction=True: the worker thread opens its own DB connection, which can't
# see rows still inside the test's wrapping transaction.
@pytest.mark.django_db(transaction=True)
class TestThreadedDispatch:
    def test_fcm_push_happens_on_background_thread(self, user):
        done = threading.Event()
        seen = {}

        def fake_push(u, **kwargs):
            seen["thread"] = threading.current_thread().name
            done.set()
            return 1

        with patch("apps.notifications.fcm.send_to_user", side_effect=fake_push):
            NotificationHandler.send(user=user, title="T", message="M")
            assert done.wait(timeout=5), "FCM push never dispatched"
        assert seen["thread"].startswith("khub-bg")
