"""
Tests for Themis notification system — model, utility functions, views,
context processor, API endpoints, admin, and management command.
"""
from datetime import timedelta
from io import StringIO
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.core.management import call_command
from django.test import Client, RequestFactory, TestCase, override_settings
from django.urls import reverse
from django.utils import timezone

from themis.context_processors import notifications as notifications_ctx
from themis.models import UserNotification, UserProfile
from themis.notifications import (
    cleanup_notifications,
    get_notifications_for_user,
    get_unread_count,
    notify_user,
)

User = get_user_model()


# ---------------------------------------------------------------------------
# UserNotification model tests
# ---------------------------------------------------------------------------


class UserNotificationModelTest(TestCase):
    """Tests for the UserNotification model."""

    def setUp(self):
        self.user = User.objects.create_user(username="notifuser", password="pass123")

    def test_create_notification(self):
        """Can create a basic notification."""
        n = UserNotification.objects.create(
            user=self.user,
            title="Test",
            message="Body text",
            level="info",
        )
        self.assertEqual(n.title, "Test")
        self.assertEqual(n.user, self.user)
        self.assertFalse(n.is_read)
        self.assertFalse(n.is_dismissed)
        self.assertIsNotNone(n.id)

    def test_str_representation(self):
        """__str__ includes level, title, and username."""
        n = UserNotification.objects.create(
            user=self.user, title="Server down", level="danger"
        )
        self.assertIn("danger", str(n))
        self.assertIn("Server down", str(n))
        self.assertIn("notifuser", str(n))

    def test_get_absolute_url(self):
        """get_absolute_url returns the notification list URL."""
        n = UserNotification.objects.create(
            user=self.user, title="Test", level="info"
        )
        self.assertEqual(n.get_absolute_url(), reverse("themis:notification-list"))

    def test_level_weight(self):
        """level_weight returns correct numeric values."""
        n = UserNotification(level="info")
        self.assertEqual(n.level_weight, 0)
        n.level = "warning"
        self.assertEqual(n.level_weight, 1)
        n.level = "danger"
        self.assertEqual(n.level_weight, 2)

    def test_is_expired_false_when_no_expiry(self):
        """is_expired returns False when expires_at is None."""
        n = UserNotification.objects.create(
            user=self.user, title="Test", level="info"
        )
        self.assertFalse(n.is_expired)

    def test_is_expired_true_when_past(self):
        """is_expired returns True when expires_at is in the past."""
        n = UserNotification.objects.create(
            user=self.user,
            title="Test",
            level="info",
            expires_at=timezone.now() - timedelta(hours=1),
        )
        self.assertTrue(n.is_expired)

    def test_is_expired_false_when_future(self):
        """is_expired returns False when expires_at is in the future."""
        n = UserNotification.objects.create(
            user=self.user,
            title="Test",
            level="info",
            expires_at=timezone.now() + timedelta(hours=1),
        )
        self.assertFalse(n.is_expired)

    def test_level_css_class(self):
        """level_css_class returns correct DaisyUI classes."""
        n = UserNotification(level="info")
        self.assertEqual(n.level_css_class, "alert-info")
        n.level = "success"
        self.assertEqual(n.level_css_class, "alert-success")
        n.level = "warning"
        self.assertEqual(n.level_css_class, "alert-warning")
        n.level = "danger"
        self.assertEqual(n.level_css_class, "alert-error")

    def test_level_badge_class(self):
        """level_badge_class returns correct DaisyUI classes."""
        n = UserNotification(level="danger")
        self.assertEqual(n.level_badge_class, "badge-error")
        n.level = "success"
        self.assertEqual(n.level_badge_class, "badge-success")

    def test_ordering_newest_first(self):
        """Notifications are ordered newest first."""
        n1 = UserNotification.objects.create(
            user=self.user, title="First", level="info"
        )
        n2 = UserNotification.objects.create(
            user=self.user, title="Second", level="info"
        )
        notifications = list(UserNotification.objects.filter(user=self.user))
        self.assertEqual(notifications[0], n2)
        self.assertEqual(notifications[1], n1)

    def test_source_tracking_fields(self):
        """Source tracking fields store correctly."""
        n = UserNotification.objects.create(
            user=self.user,
            title="Task due",
            level="warning",
            source_app="tasks",
            source_model="Task",
            source_id="42",
        )
        self.assertEqual(n.source_app, "tasks")
        self.assertEqual(n.source_model, "Task")
        self.assertEqual(n.source_id, "42")

    def test_uuid_primary_key(self):
        """Notifications use UUID primary keys."""
        n = UserNotification.objects.create(
            user=self.user, title="Test", level="info"
        )
        import uuid
        self.assertIsInstance(n.pk, uuid.UUID)


# ---------------------------------------------------------------------------
# UserProfile notification preference tests
# ---------------------------------------------------------------------------


class UserProfileNotificationPrefsTest(TestCase):
    """Tests for notification preference fields on UserProfile."""

    def setUp(self):
        self.user = User.objects.create_user(username="prefuser", password="pass123")
        self.profile = self.user.profile

    def test_default_notifications_enabled(self):
        """notifications_enabled defaults to True."""
        self.assertTrue(self.profile.notifications_enabled)

    def test_default_min_level(self):
        """notifications_min_level defaults to info."""
        self.assertEqual(self.profile.notifications_min_level, "info")

    def test_default_browser_notifications_disabled(self):
        """browser_notifications_enabled defaults to False."""
        self.assertFalse(self.profile.browser_notifications_enabled)

    def test_default_retention_days(self):
        """notification_retention_days defaults to 30."""
        self.assertEqual(self.profile.notification_retention_days, 30)

    def test_update_notification_prefs(self):
        """Can update notification preferences."""
        self.profile.notifications_enabled = False
        self.profile.notifications_min_level = "warning"
        self.profile.browser_notifications_enabled = True
        self.profile.notification_retention_days = 7
        self.profile.save()
        self.profile.refresh_from_db()
        self.assertFalse(self.profile.notifications_enabled)
        self.assertEqual(self.profile.notifications_min_level, "warning")
        self.assertTrue(self.profile.browser_notifications_enabled)
        self.assertEqual(self.profile.notification_retention_days, 7)


# ---------------------------------------------------------------------------
# notify_user() utility tests
# ---------------------------------------------------------------------------


class NotifyUserTest(TestCase):
    """Tests for the notify_user() utility function."""

    def setUp(self):
        self.user = User.objects.create_user(username="nuser", password="pass123")

    def test_creates_notification(self):
        """notify_user creates a notification in the database."""
        n = notify_user(self.user, "Hello", message="World", level="info")
        self.assertIsNotNone(n)
        self.assertEqual(n.title, "Hello")
        self.assertEqual(n.message, "World")
        self.assertEqual(n.user, self.user)

    def test_returns_none_when_disabled(self):
        """Returns None when user has notifications disabled."""
        self.user.profile.notifications_enabled = False
        self.user.profile.save()
        n = notify_user(self.user, "Ignored")
        self.assertIsNone(n)
        self.assertEqual(UserNotification.objects.count(), 0)

    def test_level_filtering(self):
        """Filters out notifications below minimum level."""
        self.user.profile.notifications_min_level = "warning"
        self.user.profile.save()
        n_info = notify_user(self.user, "Info msg", level="info")
        n_warn = notify_user(self.user, "Warning msg", level="warning")
        n_danger = notify_user(self.user, "Danger msg", level="danger")
        self.assertIsNone(n_info)
        self.assertIsNotNone(n_warn)
        self.assertIsNotNone(n_danger)
        self.assertEqual(UserNotification.objects.count(), 2)

    def test_success_level_filtered_by_warning_min(self):
        """Success level (weight 0) is filtered out by warning minimum."""
        self.user.profile.notifications_min_level = "warning"
        self.user.profile.save()
        n = notify_user(self.user, "Success msg", level="success")
        self.assertIsNone(n)

    def test_deduplication(self):
        """Deduplication prevents duplicate notifications."""
        kwargs = dict(
            source_app="tasks",
            source_model="Task",
            source_id="42",
            deduplicate=True,
        )
        n1 = notify_user(self.user, "Task due", **kwargs)
        n2 = notify_user(self.user, "Task due again", **kwargs)
        self.assertIsNotNone(n1)
        self.assertIsNone(n2)
        self.assertEqual(UserNotification.objects.count(), 1)

    def test_deduplication_allows_after_read(self):
        """Dedup allows new notification after the original is read."""
        kwargs = dict(
            source_app="tasks",
            source_model="Task",
            source_id="42",
            deduplicate=True,
        )
        n1 = notify_user(self.user, "Task due", **kwargs)
        n1.is_read = True
        n1.save()
        n2 = notify_user(self.user, "Task due again", **kwargs)
        self.assertIsNotNone(n2)
        self.assertEqual(UserNotification.objects.count(), 2)

    def test_title_truncation(self):
        """Long titles are truncated to 200 chars."""
        long_title = "A" * 300
        n = notify_user(self.user, long_title)
        self.assertEqual(len(n.title), 200)

    def test_source_id_converted_to_string(self):
        """source_id is stored as string even when passed as int."""
        n = notify_user(self.user, "Test", source_id=42)
        self.assertEqual(n.source_id, "42")

    def test_with_url(self):
        """URL is stored on the notification."""
        n = notify_user(self.user, "Check this", url="/tasks/42/")
        self.assertEqual(n.url, "/tasks/42/")

    def test_with_expires_at(self):
        """expires_at is stored on the notification."""
        future = timezone.now() + timedelta(days=7)
        n = notify_user(self.user, "Expiring", expires_at=future)
        self.assertEqual(n.expires_at, future)

    def test_all_levels_accepted(self):
        """All four notification levels are accepted."""
        for level in ("info", "success", "warning", "danger"):
            n = notify_user(self.user, f"Level {level}", level=level)
            self.assertIsNotNone(n)
            self.assertEqual(n.level, level)


# ---------------------------------------------------------------------------
# get_unread_count / get_notifications_for_user tests
# ---------------------------------------------------------------------------


class NotificationQueryTest(TestCase):
    """Tests for get_unread_count and get_notifications_for_user."""

    def setUp(self):
        self.user = User.objects.create_user(username="quser", password="pass123")
        self.n1 = notify_user(self.user, "First", level="info")
        self.n2 = notify_user(self.user, "Second", level="warning")
        self.n3 = notify_user(self.user, "Third", level="danger")

    def test_unread_count(self):
        """get_unread_count returns correct count."""
        self.assertEqual(get_unread_count(self.user), 3)

    def test_unread_count_excludes_read(self):
        """get_unread_count excludes read notifications."""
        self.n1.is_read = True
        self.n1.save()
        self.assertEqual(get_unread_count(self.user), 2)

    def test_unread_count_excludes_dismissed(self):
        """get_unread_count excludes dismissed notifications."""
        self.n1.is_dismissed = True
        self.n1.save()
        self.assertEqual(get_unread_count(self.user), 2)

    def test_unread_count_excludes_expired(self):
        """get_unread_count excludes expired notifications."""
        self.n1.expires_at = timezone.now() - timedelta(hours=1)
        self.n1.save()
        self.assertEqual(get_unread_count(self.user), 2)

    def test_unread_count_respects_min_level(self):
        """get_unread_count respects user's minimum level setting."""
        self.user.profile.notifications_min_level = "warning"
        self.user.profile.save()
        # Only n2 (warning) and n3 (danger) should count
        self.assertEqual(get_unread_count(self.user), 2)

    def test_unread_count_anonymous(self):
        """get_unread_count returns 0 for anonymous users."""
        anon = AnonymousUser()
        self.assertEqual(get_unread_count(anon), 0)

    def test_get_notifications_default(self):
        """get_notifications_for_user returns unread non-dismissed."""
        notifications = get_notifications_for_user(self.user)
        self.assertEqual(len(notifications), 3)

    def test_get_notifications_include_read(self):
        """include_read=True includes read notifications."""
        self.n1.is_read = True
        self.n1.save()
        # Without include_read: 2
        self.assertEqual(len(get_notifications_for_user(self.user)), 2)
        # With include_read: 3
        self.assertEqual(
            len(get_notifications_for_user(self.user, include_read=True)), 3
        )

    def test_get_notifications_excludes_dismissed(self):
        """Dismissed notifications are always excluded."""
        self.n1.is_dismissed = True
        self.n1.save()
        self.assertEqual(
            len(get_notifications_for_user(self.user, include_read=True)), 2
        )

    def test_get_notifications_limit(self):
        """Limit parameter restricts results."""
        for i in range(10):
            notify_user(self.user, f"Extra {i}", level="info")
        self.assertEqual(len(get_notifications_for_user(self.user, limit=5)), 5)


# ---------------------------------------------------------------------------
# cleanup_notifications tests
# ---------------------------------------------------------------------------


class CleanupNotificationsTest(TestCase):
    """Tests for the cleanup_notifications utility."""

    def setUp(self):
        self.user = User.objects.create_user(username="cleanuser", password="pass123")

    def test_deletes_old_read_notifications(self):
        """Deletes read notifications older than max age."""
        n = notify_user(self.user, "Old read")
        n.is_read = True
        n.save()
        # Manually backdate
        UserNotification.objects.filter(pk=n.pk).update(
            created_at=timezone.now() - timedelta(days=100)
        )
        deleted = cleanup_notifications(max_age_days=90)
        self.assertEqual(deleted, 1)
        self.assertFalse(UserNotification.objects.filter(pk=n.pk).exists())

    def test_keeps_recent_read_notifications(self):
        """Keeps read notifications newer than max age."""
        n = notify_user(self.user, "Recent read")
        n.is_read = True
        n.save()
        deleted = cleanup_notifications(max_age_days=90)
        self.assertEqual(deleted, 0)
        self.assertTrue(UserNotification.objects.filter(pk=n.pk).exists())

    def test_keeps_unread_notifications(self):
        """Keeps unread notifications regardless of age."""
        n = notify_user(self.user, "Unread old")
        UserNotification.objects.filter(pk=n.pk).update(
            created_at=timezone.now() - timedelta(days=200)
        )
        deleted = cleanup_notifications(max_age_days=90)
        self.assertEqual(deleted, 0)

    def test_deletes_expired_notifications(self):
        """Deletes notifications past their expires_at."""
        n = notify_user(
            self.user,
            "Expired",
            expires_at=timezone.now() - timedelta(hours=1),
        )
        deleted = cleanup_notifications()
        self.assertGreaterEqual(deleted, 1)
        self.assertFalse(UserNotification.objects.filter(pk=n.pk).exists())

    def test_per_user_cleanup_respects_retention(self):
        """Per-user cleanup uses user's retention_days preference."""
        self.user.profile.notification_retention_days = 10
        self.user.profile.save()
        n = notify_user(self.user, "Old for this user")
        n.is_read = True
        n.save()
        UserNotification.objects.filter(pk=n.pk).update(
            created_at=timezone.now() - timedelta(days=15)
        )
        deleted = cleanup_notifications(user=self.user)
        self.assertGreaterEqual(deleted, 1)

    def test_management_command(self):
        """cleanup_notifications management command runs."""
        n = notify_user(self.user, "Expired cmd")
        n.is_read = True
        n.save()
        UserNotification.objects.filter(pk=n.pk).update(
            created_at=timezone.now() - timedelta(days=100)
        )
        out = StringIO()
        call_command("cleanup_notifications", stdout=out)
        self.assertIn("Cleaned up", out.getvalue())

    def test_management_command_with_max_age(self):
        """Management command accepts --max-age-days."""
        out = StringIO()
        call_command("cleanup_notifications", max_age_days=30, stdout=out)
        self.assertIn("Cleaned up", out.getvalue())


# ---------------------------------------------------------------------------
# Notification view tests
# ---------------------------------------------------------------------------


class NotificationViewTest(TestCase):
    """Tests for notification FBV views."""

    def setUp(self):
        self.user = User.objects.create_user(username="viewnotif", password="pass123")
        self.client.login(username="viewnotif", password="pass123")
        self.n1 = notify_user(self.user, "View test 1", level="info")
        self.n2 = notify_user(self.user, "View test 2", level="warning")

    def test_notification_list_get(self):
        """GET notification list returns 200."""
        url = reverse("themis:notification-list")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "themis/notifications/list.html")

    def test_notification_list_context(self):
        """Notification list provides correct context."""
        url = reverse("themis:notification-list")
        response = self.client.get(url)
        self.assertEqual(response.context["unread_count"], 2)
        self.assertFalse(response.context["include_read"])

    def test_notification_list_include_read(self):
        """include_read=1 shows read notifications too."""
        self.n1.is_read = True
        self.n1.read_at = timezone.now()
        self.n1.save()
        url = reverse("themis:notification-list") + "?include_read=1"
        response = self.client.get(url)
        self.assertTrue(response.context["include_read"])

    def test_mark_read_redirects(self):
        """Mark read redirects to list when no URL."""
        url = reverse("themis:notification-mark-read", kwargs={"pk": self.n1.pk})
        response = self.client.post(url)
        self.assertRedirects(response, reverse("themis:notification-list"))
        self.n1.refresh_from_db()
        self.assertTrue(self.n1.is_read)
        self.assertIsNotNone(self.n1.read_at)

    def test_mark_read_redirects_to_url(self):
        """Mark read redirects to notification URL when set."""
        self.n1.url = "/tasks/42/"
        self.n1.save()
        url = reverse("themis:notification-mark-read", kwargs={"pk": self.n1.pk})
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(response.url, "/tasks/42/")

    def test_mark_read_get_not_allowed(self):
        """GET is not allowed on mark_read."""
        url = reverse("themis:notification-mark-read", kwargs={"pk": self.n1.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 405)

    def test_mark_all_read(self):
        """Mark all read updates all unread notifications."""
        url = reverse("themis:notification-mark-all-read")
        response = self.client.post(url)
        self.assertRedirects(response, reverse("themis:notification-list"))
        self.assertEqual(
            UserNotification.objects.filter(user=self.user, is_read=False).count(),
            0,
        )

    def test_dismiss(self):
        """Dismiss marks notification as dismissed."""
        url = reverse("themis:notification-dismiss", kwargs={"pk": self.n1.pk})
        response = self.client.post(url)
        self.assertRedirects(response, reverse("themis:notification-list"))
        self.n1.refresh_from_db()
        self.assertTrue(self.n1.is_dismissed)
        self.assertIsNotNone(self.n1.dismissed_at)

    def test_notification_count_json(self):
        """Notification count returns JSON with count."""
        url = reverse("themis:notification-count")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertEqual(data["count"], 2)

    def test_cannot_read_other_users_notification(self):
        """Cannot mark another user's notification as read."""
        other = User.objects.create_user(username="othernotif", password="pass123")
        n = notify_user(other, "Other's notification")
        url = reverse("themis:notification-mark-read", kwargs={"pk": n.pk})
        response = self.client.post(url)
        self.assertEqual(response.status_code, 404)

    def test_login_required(self):
        """Notification views require authentication."""
        self.client.logout()
        urls = [
            reverse("themis:notification-list"),
            reverse("themis:notification-count"),
        ]
        for url in urls:
            response = self.client.get(url)
            self.assertEqual(response.status_code, 302, f"No redirect for {url}")


# ---------------------------------------------------------------------------
# Notification context processor tests
# ---------------------------------------------------------------------------


class NotificationContextProcessorTest(TestCase):
    """Tests for the notifications context processor."""

    def setUp(self):
        self.factory = RequestFactory()

    def test_authenticated_with_notifications(self):
        """Returns correct count for user with notifications."""
        user = User.objects.create_user(username="ctxnotif", password="pass123")
        notify_user(user, "Test 1")
        notify_user(user, "Test 2")

        request = self.factory.get("/")
        request.user = user
        context = notifications_ctx(request)
        self.assertEqual(context["themis_unread_notification_count"], 2)
        self.assertTrue(context["themis_notifications_enabled"])
        self.assertFalse(context["themis_browser_notifications_enabled"])

    def test_authenticated_notifications_disabled(self):
        """Returns 0 count when notifications are disabled."""
        user = User.objects.create_user(username="ctxdis", password="pass123")
        user.profile.notifications_enabled = False
        user.profile.save()
        notify_user(user, "Should not count")  # returns None

        request = self.factory.get("/")
        request.user = user
        context = notifications_ctx(request)
        self.assertEqual(context["themis_unread_notification_count"], 0)
        self.assertFalse(context["themis_notifications_enabled"])

    def test_anonymous_user(self):
        """Returns defaults for anonymous users."""
        request = self.factory.get("/")
        request.user = AnonymousUser()
        context = notifications_ctx(request)
        self.assertEqual(context["themis_unread_notification_count"], 0)
        self.assertFalse(context["themis_notifications_enabled"])
        self.assertFalse(context["themis_browser_notifications_enabled"])

    def test_returns_expected_keys(self):
        """Context has all expected keys."""
        request = self.factory.get("/")
        request.user = AnonymousUser()
        context = notifications_ctx(request)
        expected = {
            "themis_unread_notification_count",
            "themis_notifications_enabled",
            "themis_browser_notifications_enabled",
        }
        self.assertEqual(set(context.keys()), expected)


# ---------------------------------------------------------------------------
# Notification API tests
# ---------------------------------------------------------------------------


class NotificationAPITest(TestCase):
    """Tests for the notification REST API endpoints."""

    def setUp(self):
        self.user = User.objects.create_user(username="apinotif", password="pass123")
        self.client.login(username="apinotif", password="pass123")
        self.n1 = notify_user(self.user, "API test 1", level="info")
        self.n2 = notify_user(self.user, "API test 2", level="warning")

    def test_list_notifications(self):
        """GET /notifications/ returns user's notifications."""
        url = reverse("themis-api:notification-list")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertEqual(len(data), 2)

    def test_filter_by_level(self):
        """Can filter by level query param."""
        url = reverse("themis-api:notification-list") + "?level=warning"
        response = self.client.get(url)
        data = response.json()
        self.assertEqual(len(data), 1)
        self.assertEqual(data[0]["level"], "warning")

    def test_filter_by_is_read(self):
        """Can filter by is_read query param."""
        self.n1.is_read = True
        self.n1.read_at = timezone.now()
        self.n1.save()
        url = reverse("themis-api:notification-list") + "?is_read=true"
        response = self.client.get(url)
        self.assertEqual(len(response.json()), 1)

    def test_mark_read_action(self):
        """PATCH /notifications/{id}/mark_read/ marks as read."""
        url = reverse(
            "themis-api:notification-mark-read", kwargs={"pk": self.n1.pk}
        )
        response = self.client.patch(url, content_type="application/json")
        self.assertEqual(response.status_code, 200)
        self.n1.refresh_from_db()
        self.assertTrue(self.n1.is_read)

    def test_mark_all_read_action(self):
        """PATCH /notifications/mark-all-read/ marks all as read."""
        url = reverse("themis-api:notification-mark-all-read")
        response = self.client.patch(url, content_type="application/json")
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertEqual(data["marked_read"], 2)

    def test_dismiss_action(self):
        """PATCH /notifications/{id}/dismiss/ dismisses notification."""
        url = reverse(
            "themis-api:notification-dismiss", kwargs={"pk": self.n1.pk}
        )
        response = self.client.patch(url, content_type="application/json")
        self.assertEqual(response.status_code, 200)
        self.n1.refresh_from_db()
        self.assertTrue(self.n1.is_dismissed)

    def test_count_action(self):
        """GET /notifications/count/ returns unread count."""
        url = reverse("themis-api:notification-count")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json()["count"], 2)

    def test_delete_notification(self):
        """DELETE /notifications/{id}/ deletes the notification."""
        url = reverse(
            "themis-api:notification-detail", kwargs={"pk": self.n1.pk}
        )
        response = self.client.delete(url)
        self.assertEqual(response.status_code, 204)
        self.assertFalse(UserNotification.objects.filter(pk=self.n1.pk).exists())

    def test_cannot_create_via_api(self):
        """POST is not allowed (notifications created via notify_user)."""
        url = reverse("themis-api:notification-list")
        response = self.client.post(
            url,
            data={"title": "Hacked", "level": "info"},
            content_type="application/json",
        )
        self.assertEqual(response.status_code, 405)

    def test_only_own_notifications(self):
        """Users can only see their own notifications."""
        other = User.objects.create_user(username="otherapi", password="pass123")
        notify_user(other, "Other's notification")
        url = reverse("themis-api:notification-list")
        response = self.client.get(url)
        data = response.json()
        self.assertEqual(len(data), 2)  # Only own notifications

    def test_serializer_includes_css_classes(self):
        """Serializer includes level_css_class and level_badge_class."""
        url = reverse(
            "themis-api:notification-detail", kwargs={"pk": self.n2.pk}
        )
        response = self.client.get(url)
        data = response.json()
        self.assertEqual(data["level_css_class"], "alert-warning")
        self.assertEqual(data["level_badge_class"], "badge-warning")


# ---------------------------------------------------------------------------
# Notification admin tests
# ---------------------------------------------------------------------------


class NotificationAdminTest(TestCase):
    """Tests for UserNotificationAdmin configuration."""

    def test_notification_registered(self):
        """UserNotification is registered with the admin site."""
        from django.contrib import admin
        self.assertIn(UserNotification, admin.site._registry)

    def test_list_display_fields(self):
        """Admin list_display includes expected fields."""
        from themis.admin import UserNotificationAdmin
        self.assertIn("title", UserNotificationAdmin.list_display)
        self.assertIn("level", UserNotificationAdmin.list_display)
        self.assertIn("is_read", UserNotificationAdmin.list_display)

    def test_admin_actions(self):
        """Admin has mark_as_read and mark_as_dismissed actions."""
        from themis.admin import UserNotificationAdmin
        self.assertIn("mark_as_read", UserNotificationAdmin.actions)
        self.assertIn("mark_as_dismissed", UserNotificationAdmin.actions)

    def test_search_fields(self):
        """Admin search_fields includes title and user."""
        from themis.admin import UserNotificationAdmin
        self.assertIn("title", UserNotificationAdmin.search_fields)
        self.assertIn("user__username", UserNotificationAdmin.search_fields)
