"""Report & moderation tests (Phase 7)."""

import os
import unittest
from datetime import timedelta
from decimal import Decimal
from unittest.mock import patch

os.environ["TEST_DATABASE_URL"] = "sqlite:///:memory:"

from sqlalchemy import event
from werkzeug.security import generate_password_hash

from app import create_app
from app.admin.analytics import get_admin_dashboard
from app.admin.listing_management import get_listing_detail, get_listings
from app.extensions import db
from app.models import (
    AdminAuditLog,
    Category,
    Conversation,
    Listing,
    ListingCondition,
    ListingStatus,
    ListingVisibility,
    Message,
    MessageType,
    Notification,
    NotificationType,
    Offer,
    OfferStatus,
    Report,
    ReportEntityType,
    ReportReason,
    ReportStatus,
    ReviewStatus,
    User,
)
from app.models.base import utcnow
from app.models.enums import AccountStatus, UserRole
from app.moderation.services import (
    CannotReportOwnListingError,
    DuplicateReportError,
    assign_report,
    create_report,
    dismiss_report,
    get_listing_conversations,
    get_moderation_analytics,
    get_open_report_listing_ids,
    get_report_detail,
    get_report_summary,
    get_reports,
    get_user_report_counts,
    notify_report_parties,
    remove_reported_listing,
    resolve_report,
    warn_reported_seller,
)


class _QC:
    def __init__(self):
        self.n = 0

    def __enter__(self):
        self._e = db.engine
        event.listen(self._e, "after_cursor_execute", self._cb)
        return self

    def __exit__(self, *a):
        event.remove(self._e, "after_cursor_execute", self._cb)

    def _cb(self, *a):
        self.n += 1


class ModerationTests(unittest.TestCase):
    def setUp(self):
        self.app = create_app("testing")
        self.ctx = self.app.app_context()
        self.ctx.push()
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.ctx.pop()

    # --- factories ---
    def _user(self, email, role=UserRole.STUDENT):
        u = User(is_verified=True, email=email, password_hash=generate_password_hash("Passw0rd!"), role=role)
        db.session.add(u)
        db.session.commit()
        return u

    def _category(self):
        cat = db.session.scalar(db.select(Category).filter_by(name="Books"))
        if cat is None:
            cat = Category(name="Books", description="d", is_active=True, display_order=1)
            db.session.add(cat)
            db.session.commit()
        return cat

    def _listing(self, seller, idx=1):
        listing = Listing(
            sku=f"SKU-{seller.id}-{idx}", seller_id=seller.id, category_id=self._category().id,
            title=f"Item {idx}", slug=f"item-{seller.id}-{idx}", description="d", price=Decimal("50.00"),
            condition=ListingCondition.GOOD, status=ListingStatus.AVAILABLE, visibility=ListingVisibility.PUBLIC,
            review_status=ReviewStatus.APPROVED,
        )
        db.session.add(listing)
        db.session.commit()
        return listing

    def _report(self, reporter, listing, reason=ReportReason.SCAM, status=ReportStatus.PENDING, moderator=None):
        report = Report(
            reporter_id=reporter.id, entity_type=ReportEntityType.LISTING, entity_public_id=listing.public_id,
            listing_id=listing.id, reported_user_id=listing.seller_id, reason=reason, status=status,
            moderator_id=(moderator.id if moderator else None),
            resolved_at=(utcnow() if status in (ReportStatus.RESOLVED, ReportStatus.DISMISSED) else None),
            created_at=utcnow(),
        )
        db.session.add(report)
        db.session.commit()
        return report

    def _admin_client(self):
        self._user("admin@c.edu", UserRole.SUPER_ADMIN)
        client = self.app.test_client()
        client.post("/login", data={"email": "admin@c.edu", "password": "Passw0rd!"})
        return client

    def _login(self, email):
        client = self.app.test_client()
        client.post("/login", data={"email": email, "password": "Passw0rd!"})
        return client

    # --- student reporting ---
    def test_create_report(self):
        seller = self._user("s@c.edu")
        buyer = self._user("b@c.edu")
        listing = self._listing(seller)
        report = create_report(buyer, listing, "scam", "looks fake")
        self.assertEqual(report.status, ReportStatus.PENDING)
        self.assertEqual(report.reason, ReportReason.SCAM)
        self.assertEqual(report.entity_type, ReportEntityType.LISTING)
        self.assertEqual(report.reported_user_id, seller.id)

    def test_cannot_report_own_listing(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller)
        with self.assertRaises(CannotReportOwnListingError):
            create_report(seller, listing, "scam")

    def test_cannot_submit_duplicate_pending_report(self):
        seller = self._user("s@c.edu")
        buyer = self._user("b@c.edu")
        listing = self._listing(seller)
        create_report(buyer, listing, "scam")
        with self.assertRaises(DuplicateReportError):
            create_report(buyer, listing, "spam")
        self.assertEqual(db.session.scalar(db.select(db.func.count(Report.id))), 1)

    def test_report_route_creates_report(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller)
        self._user("buyer@c.edu")
        client = self.app.test_client()
        client.post("/login", data={"email": "buyer@c.edu", "password": "Passw0rd!"})
        client.post(f"/app/listings/{listing.public_id}/report", data={"reason": "scam", "description": "bad"})
        report = db.session.scalar(db.select(Report))
        self.assertIsNotNone(report)
        self.assertEqual(report.reason, ReportReason.SCAM)

    # --- admin access ---
    def test_admin_access(self):
        client = self._admin_client()
        self.assertEqual(client.get("/admin/reports").status_code, 200)

    def test_non_admin_denied(self):
        self._user("s@c.edu")
        client = self.app.test_client()
        client.post("/login", data={"email": "s@c.edu", "password": "Passw0rd!"})
        self.assertEqual(client.get("/admin/reports").status_code, 403)

    # --- filters ---
    def test_filters(self):
        seller = self._user("s@c.edu")
        buyer = self._user("b@c.edu")
        mod = self._user("m@c.edu", UserRole.SUPER_ADMIN)
        l1, l2 = self._listing(seller, 1), self._listing(seller, 2)
        self._report(buyer, l1, reason=ReportReason.SCAM, status=ReportStatus.PENDING)
        self._report(buyer, l2, reason=ReportReason.SPAM, status=ReportStatus.RESOLVED, moderator=mod)
        _, rows = get_reports(status="pending")
        self.assertEqual([r["reason"] for r in rows], ["Scam"])
        _, rows = get_reports(reason="spam")
        self.assertEqual([r["status"] for r in rows], ["resolved"])
        _, rows = get_reports(assigned="unassigned")
        self.assertEqual(len(rows), 1)
        _, rows = get_reports(assigned="assigned")
        self.assertEqual(len(rows), 1)
        _, rows = get_reports(seller=seller.public_id)
        self.assertEqual(len(rows), 2)

    # --- lifecycle ---
    def test_assignment(self):
        seller, buyer, mod = self._user("s@c.edu"), self._user("b@c.edu"), self._user("m@c.edu", UserRole.SUPER_ADMIN)
        report = self._report(buyer, self._listing(seller))
        assign_report(report, mod)
        self.assertEqual(report.moderator_id, mod.id)
        self.assertEqual(report.status, ReportStatus.UNDER_REVIEW)

    def test_resolution(self):
        seller, buyer, mod = self._user("s@c.edu"), self._user("b@c.edu"), self._user("m@c.edu", UserRole.SUPER_ADMIN)
        report = self._report(buyer, self._listing(seller))
        resolve_report(report, mod, notes="handled")
        self.assertEqual(report.status, ReportStatus.RESOLVED)
        self.assertIsNotNone(report.resolved_at)
        self.assertEqual(report.moderator_notes, "handled")

    def test_dismissal(self):
        seller, buyer, mod = self._user("s@c.edu"), self._user("b@c.edu"), self._user("m@c.edu", UserRole.SUPER_ADMIN)
        report = self._report(buyer, self._listing(seller))
        dismiss_report(report, mod)
        self.assertEqual(report.status, ReportStatus.DISMISSED)
        self.assertIsNotNone(report.resolved_at)

    # --- audit logging: every moderator action writes exactly one audit row ---
    def test_every_moderator_action_is_audited(self):
        client = self._admin_client()
        admin = db.session.scalar(db.select(User).where(User.email == "admin@c.edu"))
        seller = self._user("s@c.edu")
        buyer = self._user("b@c.edu")

        cases = [
            ("assign", "report_assigned"),
            ("under_review", "report_under_review"),
            ("dismiss", "report_dismissed"),
            ("resolve", "report_resolved"),
            ("remove_listing", "report_listing_removed"),
            ("suspend_seller", "report_seller_suspended"),
        ]
        for action, expected in cases:
            listing = self._listing(seller, idx=action)
            report = self._report(buyer, listing)
            client.post(f"/admin/reports/{report.public_id}/action", data={"action": action})
            logs = db.session.scalars(
                db.select(AdminAuditLog).where(AdminAuditLog.entity_public_id == report.public_id)
            ).all()
            self.assertEqual(len(logs), 1, f"{action} should write exactly one audit row")
            self.assertEqual(logs[0].action, expected)

    def test_remove_listing_action_soft_deletes(self):
        client = self._admin_client()
        seller = self._user("s@c.edu")
        buyer = self._user("b@c.edu")
        listing = self._listing(seller)
        report = self._report(buyer, listing)
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "remove_listing"})
        db.session.expire_all()
        self.assertIsNotNone(db.session.get(Listing, listing.id).deleted_at)
        self.assertEqual(db.session.get(Report, report.id).status, ReportStatus.RESOLVED)

    def test_suspend_seller_action(self):
        client = self._admin_client()
        seller = self._user("s@c.edu")
        buyer = self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "suspend_seller"})
        db.session.expire_all()
        self.assertEqual(db.session.get(User, seller.id).account_status, AccountStatus.SUSPENDED)

    # --- summary ---
    def test_summary(self):
        seller, buyer, mod = self._user("s@c.edu"), self._user("b@c.edu"), self._user("m@c.edu", UserRole.SUPER_ADMIN)
        self._report(buyer, self._listing(seller, 1), status=ReportStatus.PENDING)
        self._report(buyer, self._listing(seller, 2), status=ReportStatus.UNDER_REVIEW, moderator=mod)
        self._report(buyer, self._listing(seller, 3), status=ReportStatus.RESOLVED, moderator=mod)
        s = get_report_summary()
        self.assertEqual(s["pending"], 1)
        self.assertEqual(s["under_review"], 1)
        self.assertEqual(s["resolved"], 1)
        self.assertEqual(s["today"], 3)
        self.assertEqual(s["this_month"], 3)

    # --- listing badge ---
    def test_listing_reported_badge(self):
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        reported = self._listing(seller, 1)
        clean = self._listing(seller, 2)
        self._report(buyer, reported)
        ids = get_open_report_listing_ids([reported.id, clean.id])
        self.assertEqual(ids, {reported.id})
        # surfaced in listing-management rows + detail
        _, rows = get_listings()
        flags = {r["title"]: r["is_reported"] for r in rows}
        self.assertTrue(flags["Item 1"])
        self.assertFalse(flags["Item 2"])
        self.assertTrue(get_listing_detail(reported)["is_reported"])

    def test_user_report_counts(self):
        seller, buyer, mod = self._user("s@c.edu"), self._user("b@c.edu"), self._user("m@c.edu", UserRole.SUPER_ADMIN)
        self._report(buyer, self._listing(seller, 1), status=ReportStatus.PENDING)
        self._report(buyer, self._listing(seller, 2), status=ReportStatus.RESOLVED, moderator=mod)
        counts = get_user_report_counts(seller)
        self.assertEqual(counts["open"], 1)
        self.assertEqual(counts["resolved"], 1)

    # --- dashboard integration ---
    def test_dashboard_integration(self):
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        self._report(buyer, self._listing(seller))
        dash = get_admin_dashboard()
        self.assertEqual(dash["overview"]["pending_reports"], 1)
        self.assertEqual(len(dash["recent_reports"]), 1)
        self.assertTrue(dash["moderation"]["reports_available"])

    # --- analytics ---
    def test_analytics(self):
        seller, buyer, mod = self._user("s@c.edu"), self._user("b@c.edu"), self._user("m@c.edu", UserRole.SUPER_ADMIN)
        self._report(buyer, self._listing(seller, 1), reason=ReportReason.SCAM, status=ReportStatus.PENDING)
        r = self._report(buyer, self._listing(seller, 2), reason=ReportReason.SCAM, status=ReportStatus.RESOLVED, moderator=mod)
        r.created_at = utcnow() - timedelta(hours=4)
        r.resolved_at = utcnow()
        db.session.commit()
        a = get_moderation_analytics()
        self.assertEqual(a["reports_this_month"], 2)
        self.assertEqual(a["most_common_reason"], "Scam")
        self.assertIsNotNone(a["avg_resolution_hours"])
        self.assertEqual(len(a["trend"]["values"]), 30)
        self.assertTrue(any(name == "Books" for name, _ in a["top_categories"]))

    # --- performance ---
    def test_no_n_plus_1(self):
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        for i in range(4):
            self._report(buyer, self._listing(seller, i))
        with _QC() as c1:
            get_reports(page=1)
        small = c1.n
        for i in range(4, 40):
            self._report(buyer, self._listing(seller, i))
        with _QC() as c2:
            get_reports(page=1)
        self.assertEqual(small, c2.n)

    # --- conversation history ---
    def _conversation(self, listing, buyer, seller):
        convo = Conversation(listing_id=listing.id, buyer_id=buyer.id, seller_id=seller.id)
        db.session.add(convo)
        db.session.commit()
        return convo

    def _message(self, convo, sender, text, mtype=MessageType.TEXT):
        msg = Message(conversation_id=convo.id, sender_id=sender.id, message=text, message_type=mtype)
        db.session.add(msg)
        db.session.commit()
        return msg

    def _offer(self, convo, maker, other, amount, status=OfferStatus.PENDING):
        offer = Offer(listing_id=convo.listing_id, conversation_id=convo.id,
                      buyer_id=maker.id, seller_id=other.id, amount=Decimal(str(amount)), status=status)
        db.session.add(offer)
        db.session.commit()
        return offer

    def test_conversation_history_timeline(self):
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        listing = self._listing(seller)
        convo = self._conversation(listing, buyer, seller)
        self._message(convo, buyer, "Is the tablet still available?")
        self._message(convo, seller, "Yes.")
        self._offer(convo, buyer, seller, 90000)                       # buyer offer
        self._offer(convo, seller, buyer, 100000, OfferStatus.PENDING)  # seller counter
        report = self._report(buyer, listing)

        data = get_listing_conversations(report)
        self.assertEqual(data["conversation_count"], 1)
        self.assertEqual(data["total_messages"], 2)
        self.assertEqual(data["total_offers"], 2)
        events = data["groups"][0]["events"]
        # chronological, typed, and buyer-vs-seller offers distinguished
        self.assertEqual([e["kind"] for e in events], ["message", "message", "offer", "offer"])
        self.assertEqual(events[2]["offer_kind"], "offer")
        self.assertEqual(events[3]["offer_kind"], "counter")
        self.assertEqual(events[3]["role"], "seller")

    def test_conversation_history_empty(self):
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        data = get_listing_conversations(report)
        self.assertEqual(data["conversation_count"], 0)
        self.assertEqual(data["groups"], [])
        # exposed through the detail payload the page renders
        self.assertIn("conversations", get_report_detail(report))

    def test_conversation_history_no_n_plus_1(self):
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        listing = self._listing(seller)
        convo = self._conversation(listing, buyer, seller)
        for i in range(3):
            self._message(convo, buyer, f"m{i}")
        report = self._report(buyer, listing)
        with _QC() as c1:
            get_listing_conversations(report)
        base = c1.n
        for i in range(20):
            self._message(convo, seller, f"s{i}")
        with _QC() as c2:
            get_listing_conversations(report)
        self.assertEqual(base, c2.n)  # constant query count regardless of volume

    # --- warn seller ---
    def test_warn_seller_service_creates_notification(self):
        seller, buyer, mod = self._user("s@c.edu"), self._user("b@c.edu"), self._user("m@c.edu", UserRole.SUPER_ADMIN)
        report = self._report(buyer, self._listing(seller))
        notif = warn_reported_seller(report, mod, reason="Scam", message="Please fix this.")
        self.assertIsNotNone(notif)
        self.assertEqual(notif.user_id, seller.id)
        self.assertEqual(notif.title, "Warning from CampusCart")
        self.assertEqual(notif.notification_type, NotificationType.SYSTEM)
        self.assertIn("Please fix this.", notif.message)
        # a warning does not close the report
        self.assertEqual(report.status, ReportStatus.PENDING)

    def test_warn_seller_route_audits(self):
        client = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "warn_seller"})
        logs = db.session.scalars(
            db.select(AdminAuditLog).where(AdminAuditLog.entity_public_id == report.public_id)
        ).all()
        self.assertEqual(len(logs), 1)
        self.assertEqual(logs[0].action, "seller_warned")
        notif = db.session.scalar(db.select(Notification).where(Notification.user_id == seller.id))
        self.assertIsNotNone(notif)
        self.assertEqual(notif.title, "Warning from CampusCart")

    def test_seller_history_aggregates(self):
        seller, buyer, mod = self._user("s@c.edu"), self._user("b@c.edu"), self._user("m@c.edu", UserRole.SUPER_ADMIN)
        active = self._listing(seller, 1)
        removed = self._listing(seller, 2)
        removed.deleted_at = utcnow()
        removed.status = ListingStatus.REMOVED
        db.session.commit()
        r1 = self._report(buyer, active, status=ReportStatus.PENDING)
        self._report(buyer, removed, status=ReportStatus.RESOLVED, moderator=mod)
        db.session.add(AdminAuditLog(admin_id=mod.id, action="seller_warned", entity_type="report",
                                     entity_public_id=r1.public_id, entity_name="Report", created_at=utcnow()))
        db.session.commit()

        sh = get_report_detail(r1)["seller_history"]
        self.assertEqual(sh["total_reports"], 2)
        self.assertEqual(sh["open_reports"], 1)
        self.assertEqual(sh["resolved_reports"], 1)
        self.assertEqual(sh["warnings"], 1)
        self.assertEqual(sh["active_listings"], 1)
        self.assertEqual(sh["removed_listings"], 1)

    # --- moderation outcome notifications ---
    def _notifs(self, user):
        return db.session.scalars(db.select(Notification).where(Notification.user_id == user.id)).all()

    def test_reporter_notified_when_resolved(self):
        client = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "resolve"})
        db.session.expire_all()
        reporter_notifs = self._notifs(buyer)
        self.assertEqual(len(reporter_notifs), 1)
        self.assertEqual(reporter_notifs[0].title, "Report Reviewed")
        self.assertIn("appropriate moderation action has been taken", reporter_notifs[0].message)
        # a bare resolve takes no concrete action against the seller → not notified
        self.assertEqual(len(self._notifs(seller)), 0)

    def test_reporter_notified_when_dismissed(self):
        client = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "dismiss"})
        db.session.expire_all()
        reporter_notifs = self._notifs(buyer)
        self.assertEqual(len(reporter_notifs), 1)
        self.assertEqual(reporter_notifs[0].title, "Report Reviewed")
        self.assertIn("did not find sufficient evidence", reporter_notifs[0].message)
        # dismissed = no action → reported user never notified
        self.assertEqual(len(self._notifs(seller)), 0)

    def test_reported_user_notified_on_listing_removed(self):
        client = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "remove_listing"})
        db.session.expire_all()
        seller_notifs = self._notifs(seller)
        self.assertEqual(len(seller_notifs), 1)
        self.assertEqual(seller_notifs[0].title, "Listing Removed")
        self.assertEqual(seller_notifs[0].notification_type, NotificationType.SYSTEM)
        # reporter also told the report was actioned
        self.assertEqual(self._notifs(buyer)[0].title, "Report Reviewed")

    def test_reported_user_notified_on_suspension(self):
        client = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "suspend_seller"})
        db.session.expire_all()
        seller_notifs = self._notifs(seller)
        self.assertEqual(len(seller_notifs), 1)
        self.assertEqual(seller_notifs[0].title, "Account Suspended")

    def test_reported_user_not_notified_when_ignored(self):
        client = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "dismiss"})
        db.session.expire_all()
        self.assertEqual(len(self._notifs(seller)), 0)

    def test_notification_failure_does_not_rollback_moderation(self):
        client = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        with patch("app.notifications.services.create_notification", side_effect=RuntimeError("boom")):
            resp = client.post(f"/admin/reports/{report.public_id}/action", data={"action": "resolve"})
        self.assertEqual(resp.status_code, 302)  # request still succeeded
        db.session.expire_all()
        # moderation persisted despite the notification blowing up
        self.assertEqual(db.session.get(Report, report.id).status, ReportStatus.RESOLVED)
        self.assertEqual(len(self._notifs(buyer)), 0)

    def test_no_duplicate_notifications(self):
        client = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "resolve"})
        client.post(f"/admin/reports/{report.public_id}/action", data={"action": "resolve"})
        db.session.expire_all()
        self.assertEqual(len(self._notifs(buyer)), 1)  # second review does not re-notify

    # --- actionable notifications (deep links) ---
    # Notifications are created via the service layer (not a logged-in admin test
    # client) so a second test client can authenticate cleanly in the same test.
    def test_reporter_notification_opens_my_reports(self):
        mod = self._user("m@c.edu", UserRole.SUPER_ADMIN)
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        resolve_report(report, mod)
        notify_report_parties(report, "resolve")
        notif = self._notifs(buyer)[0]
        resp = self._login("b@c.edu").get(f"/app/notifications/{notif.public_id}")
        self.assertEqual(resp.status_code, 302)
        self.assertIn("/app/my-reports", resp.headers["Location"])

    def test_listing_removed_notification_opens_my_listings(self):
        mod = self._user("m@c.edu", UserRole.SUPER_ADMIN)
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        remove_reported_listing(report, mod)
        notify_report_parties(report, "remove_listing")
        notif = self._notifs(seller)[0]
        self.assertEqual(notif.title, "Listing Removed")
        resp = self._login("s@c.edu").get(f"/app/notifications/{notif.public_id}")
        self.assertEqual(resp.status_code, 302)
        self.assertIn("/app/my-listings", resp.headers["Location"])

    def test_moderation_notification_link_destinations(self):
        from app.moderation.services import moderation_notification_link
        admin = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        # suspension → reported user gets an "Account Suspended" notice → Learn More
        admin.post(f"/admin/reports/{report.public_id}/action", data={"action": "suspend_seller"})
        notif = self._notifs(seller)[0]
        self.assertEqual(notif.title, "Account Suspended")
        link = moderation_notification_link(notif)
        self.assertEqual(link["endpoint"], "main.terms")
        self.assertEqual(link["label"], "Learn More")

    def test_my_reports_page_shows_own_reports_without_notes(self):
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        listing = self._listing(seller)
        report = self._report(buyer, listing)
        report.moderator_notes = "internal-only rationale"
        db.session.commit()
        html = self._login("b@c.edu").get("/app/my-reports").get_data(as_text=True)
        self.assertIn("My Reports", html)
        self.assertIn("Item 1", html)                      # their reported listing
        self.assertNotIn("internal-only rationale", html)  # privacy: notes never exposed

    def test_notification_delivery_status_after_action(self):
        admin = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        admin.post(f"/admin/reports/{report.public_id}/action", data={"action": "remove_listing"})
        db.session.expire_all()
        nd = get_report_detail(db.session.get(Report, report.id))["notification_delivery"]
        self.assertTrue(nd["show"])
        self.assertTrue(nd["reporter_notified"])
        self.assertTrue(nd["reported_notified"])

    def test_notification_delivery_status_on_dismiss(self):
        admin = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        report = self._report(buyer, self._listing(seller))
        admin.post(f"/admin/reports/{report.public_id}/action", data={"action": "dismiss"})
        db.session.expire_all()
        nd = get_report_detail(db.session.get(Report, report.id))["notification_delivery"]
        self.assertTrue(nd["reporter_notified"])
        self.assertFalse(nd["reported_notified"])

    def test_report_detail_page_renders_history_and_assignment(self):
        client = self._admin_client()
        seller, buyer = self._user("s@c.edu"), self._user("b@c.edu")
        listing = self._listing(seller)
        convo = self._conversation(listing, buyer, seller)
        self._message(convo, buyer, "Hello there")
        report = self._report(buyer, listing)
        html = client.get(f"/admin/reports/{report.public_id}").get_data(as_text=True)
        self.assertIn("Conversation History", html)
        self.assertIn("Hello there", html)
        self.assertNotIn("future release", html)  # placeholder is gone
        self.assertIn("Assign to Me", html)       # unassigned → assign affordance
        self.assertIn("Warn Seller", html)


if __name__ == "__main__":
    unittest.main()
