"""ITs for admin-Post (kind=NOTE_UNLOCK) write protection.

Three guards stack:
  1. post.html input is `readonly` w. "No response needed…" placeholder
     (FT covers this — `functional_tests/test_admin_post_readonly.py`).
  2. view_post POST handler hard-rejects writes (HTTP 403). This file's
     PostRejectsAdminWritesTest.
  3. post_save signal nukes any Line saved on a NOTE_UNLOCK Post that
     lacks `admin_solicited=True` — defense-in-depth for paths that
     bypass the view (raw API, ORM, etc.). UnsolicitedLineListenerTest.

Bug A — May 2026.
"""
from django.test import TestCase
from django.urls import reverse

from apps.billboard.models import Brief, Line, Post
from apps.drama.models import Note
from apps.lyric.models import User, get_or_create_adman


class PostRejectsAdminWritesTest(TestCase):
    """POST /billboard/post/<note_unlock>/ → HTTP 403, no Line appended."""

    def setUp(self):
        self.user = User.objects.create(email="admin-rej@test.io")
        self.client.force_login(self.user)
        Note.grant_if_new(self.user, "stargazer")
        self.admin_post = Post.objects.get(
            owner=self.user, kind=Post.KIND_NOTE_UNLOCK,
        )
        self.line_count_before = Line.objects.filter(post=self.admin_post).count()

    def test_post_to_admin_post_returns_403(self):
        resp = self.client.post(
            reverse("billboard:view_post", args=[self.admin_post.id]),
            data={"text": "errant response"},
        )
        self.assertEqual(resp.status_code, 403)

    def test_post_to_admin_post_does_not_append_line(self):
        self.client.post(
            reverse("billboard:view_post", args=[self.admin_post.id]),
            data={"text": "errant response"},
        )
        self.assertEqual(
            Line.objects.filter(post=self.admin_post).count(),
            self.line_count_before,
        )

    def test_post_to_user_post_still_succeeds(self):
        """Regression: kind=USER_POST still accepts compose."""
        user_post = Post.objects.create(
            owner=self.user, kind=Post.KIND_USER_POST, title="composing",
        )
        Line.objects.create(post=user_post, text="seed", author=self.user)
        resp = self.client.post(
            reverse("billboard:view_post", args=[user_post.id]),
            data={"text": "valid append"},
        )
        # 302 redirect on success
        self.assertEqual(resp.status_code, 302)
        self.assertTrue(
            Line.objects.filter(post=user_post, text="valid append").exists(),
        )


class UnsolicitedLineListenerTest(TestCase):
    """post_save signal deletes any Line saved on a NOTE_UNLOCK Post without
    `admin_solicited=True`. Note.grant_if_new sets it; everything else
    defaults to False, so a stray ORM-level write gets nuked."""

    def setUp(self):
        self.user = User.objects.create(email="listener@test.io")
        Note.grant_if_new(self.user, "stargazer")
        self.admin_post = Post.objects.get(
            owner=self.user, kind=Post.KIND_NOTE_UNLOCK,
        )

    def test_unsolicited_line_on_note_unlock_post_is_deleted(self):
        unsolicited = Line.objects.create(
            post=self.admin_post,
            text="errant ORM write",
            author=self.user,
            # admin_solicited defaults to False
        )
        # Signal fires post_save; the Line should be gone.
        self.assertFalse(
            Line.objects.filter(pk=unsolicited.pk).exists(),
            "Unsolicited Line on NOTE_UNLOCK Post must be deleted",
        )

    def test_admin_solicited_line_on_note_unlock_post_persists(self):
        """The Note grant Lines are admin_solicited=True — must NOT be nuked."""
        adman = get_or_create_adman()
        line = Line.objects.create(
            post=self.admin_post,
            text="valid system prose",
            author=adman,
            admin_solicited=True,
        )
        self.assertTrue(Line.objects.filter(pk=line.pk).exists())

    def test_unsolicited_line_on_user_post_persists(self):
        """User-typed Lines on user_post posts default to admin_solicited=False
        and must NOT be nuked — the listener only guards NOTE_UNLOCK."""
        up = Post.objects.create(
            owner=self.user, kind=Post.KIND_USER_POST, title="x",
        )
        line = Line.objects.create(
            post=up, text="user-typed line", author=self.user,
        )
        self.assertTrue(Line.objects.filter(pk=line.pk).exists())


class NoteGrantSetsAdminSolicitedTest(TestCase):
    """Note.grant_if_new must persist Lines with admin_solicited=True so
    they survive the listener pass."""

    def test_grant_creates_line_with_admin_solicited_true(self):
        u = User.objects.create(email="grant@test.io")
        Note.grant_if_new(u, "stargazer")
        post = Post.objects.get(owner=u, kind=Post.KIND_NOTE_UNLOCK)
        # Exactly one Line on a fresh grant
        line = post.lines.get()
        self.assertTrue(line.admin_solicited)
