"""Featured Listings empty-state + seller-notification tests.

Covers the UX refinement around the (unchanged) Featured discovery rule:
  * the homepage shows an informative empty state when a promoted listing exists
    but is currently unavailable, and hides the section only when nothing is
    featured at all;
  * a Premium seller is notified exactly once when their *displayed* featured
    listing transitions out of the visible pool (reserved / removed).
"""

import os
import unittest
from datetime import timedelta
from decimal import Decimal

os.environ["TEST_DATABASE_URL"] = "sqlite:///:memory:"

from werkzeug.security import generate_password_hash

from app import create_app
from app.extensions import db
from app.marketplace.discovery import (
    get_featured_discovery,
    get_homepage_sections,
    has_featured_in_system,
)
from app.marketplace.services import (
    notify_featured_listing_hidden,
    reserve_listing_for_offer,
    soft_delete_listing,
)
from app.models import (
    Category,
    Conversation,
    Listing,
    ListingCondition,
    ListingStatus,
    ListingVisibility,
    Notification,
    NotificationType,
    Offer,
    OfferStatus,
    ReviewStatus,
    User,
)
from app.models.base import utcnow

_EMPTY_TEXT = "no featured listings available"


class FeaturedEmptyStateTests(unittest.TestCase):
    def setUp(self):
        self.app = create_app("testing")
        self.ctx = self.app.app_context()
        self.ctx.push()
        db.create_all()
        self.cat = Category(name="Electronics", description="d", is_active=True, display_order=1)
        db.session.add(self.cat)
        db.session.commit()

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.ctx.pop()

    # --- factories -------------------------------------------------------------
    def _user(self, email):
        u = User(email=email, password_hash=generate_password_hash("Passw0rd!"), is_verified=True)
        db.session.add(u)
        db.session.commit()
        return u

    def _listing(self, seller, *, status=ListingStatus.AVAILABLE, featured=False,
                 visibility=ListingVisibility.PUBLIC):
        idx = (db.session.scalar(db.select(db.func.count(Listing.id))) or 0) + 1
        listing = Listing(
            sku=f"SKU-{seller.id}-{idx}", seller_id=seller.id, category_id=self.cat.id,
            title=f"Item {idx}", slug=f"slug-{seller.id}-{idx}", description="d",
            price=Decimal("100"), condition=ListingCondition.GOOD, status=status,
            visibility=visibility, review_status=ReviewStatus.APPROVED,
            is_featured=featured,
            featured_at=(utcnow() if featured else None),
            featured_until=(utcnow() + timedelta(days=7) if featured else None),
        )
        db.session.add(listing)
        db.session.commit()
        return listing

    def _accepted_offer(self, listing, buyer):
        convo = Conversation(listing_id=listing.id, buyer_id=buyer.id, seller_id=listing.seller_id)
        db.session.add(convo)
        db.session.commit()
        offer = Offer(listing_id=listing.id, conversation_id=convo.id, buyer_id=buyer.id,
                      seller_id=listing.seller_id, amount=Decimal("90"), status=OfferStatus.ACCEPTED)
        db.session.add(offer)
        db.session.commit()
        return offer

    def _featured_notifs(self, user):
        # Featured-hidden notices all carry a "Featured listing …" title, which
        # distinguishes them from the generic "Listing reserved/sold" notices.
        return db.session.scalars(
            db.select(Notification).where(
                Notification.user_id == user.id,
                Notification.title.like("Featured listing%"),
            )
        ).all()

    # --- service flag ----------------------------------------------------------
    def test_has_featured_in_system(self):
        seller = self._user("s@c.edu")
        self.assertFalse(has_featured_in_system())
        listing = self._listing(seller, featured=True)
        self.assertTrue(has_featured_in_system())
        self.assertEqual(len(get_featured_discovery()), 1)
        # Reserving keeps is_featured=True but removes it from the visible pool.
        reserve_listing_for_offer(self._accepted_offer(listing, self._user("b@c.edu")))
        self.assertTrue(has_featured_in_system())          # still featured in system
        self.assertEqual(len(get_featured_discovery()), 0)  # but not displayed

    def test_homepage_flag_when_featured_but_unavailable(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller, featured=True)
        reserve_listing_for_offer(self._accepted_offer(listing, self._user("b@c.edu")))
        sections = get_homepage_sections(None)
        self.assertEqual(sections["featured"], [])
        self.assertTrue(sections["featured_exists"])

    def test_homepage_flag_false_when_nothing_featured(self):
        self._listing(self._user("s@c.edu"))   # available, not featured
        sections = get_homepage_sections(None)
        self.assertEqual(sections["featured"], [])
        self.assertFalse(sections["featured_exists"])

    # --- notifications (transition, not on read) ------------------------------
    def test_notify_seller_on_reserve_of_featured(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller, featured=True)
        reserve_listing_for_offer(self._accepted_offer(listing, self._user("b@c.edu")))
        notifs = self._featured_notifs(seller)
        self.assertEqual(len(notifs), 1)
        self.assertIn("reserved", notifs[0].message)
        self.assertEqual(notifs[0].notification_type, NotificationType.LISTING)

    def test_reason_specific_copy(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller, featured=True)   # title == "Item 1"
        cases = {
            "reserved": ("Featured listing reserved", "has been reserved"),
            "sold": ("Featured listing sold", "has been sold"),
            "removed": ("Featured listing removed", "it was removed"),
            "expired": ("Featured listing expired", "has expired"),
        }
        for reason in cases:
            notify_featured_listing_hidden(listing, reason)
        notifs = {n.title: n for n in self._featured_notifs(seller)}
        for reason, (title, phrase) in cases.items():
            self.assertIn(title, notifs)
            msg = notifs[title].message
            self.assertIn(phrase, msg)                              # reason-specific
            self.assertIn("Item 1", msg)                            # listing name interpolated
            self.assertIn("Feature another available listing", msg)  # actionable CTA

    def test_notification_not_generated_on_page_load(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller, featured=True)
        reserve_listing_for_offer(self._accepted_offer(listing, self._user("b@c.edu")))
        before = len(self._featured_notifs(seller))
        for _ in range(3):
            get_homepage_sections(None)          # rendering must not re-notify
        self.assertEqual(len(self._featured_notifs(seller)), before)

    def test_no_notification_when_reserved_listing_not_featured(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller, featured=False)
        reserve_listing_for_offer(self._accepted_offer(listing, self._user("b@c.edu")))
        self.assertEqual(len(self._featured_notifs(seller)), 0)

    def test_reserve_is_idempotent_single_notification(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller, featured=True)
        offer = self._accepted_offer(listing, self._user("b@c.edu"))
        reserve_listing_for_offer(offer)
        reserve_listing_for_offer(offer)   # already reserved → early return, no re-notify
        self.assertEqual(len(self._featured_notifs(seller)), 1)

    def test_notify_seller_on_soft_delete_of_visible_featured(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller, featured=True)
        soft_delete_listing(listing, seller)
        notifs = self._featured_notifs(seller)
        self.assertEqual(len(notifs), 1)
        self.assertIn("removed", notifs[0].message)

    def test_no_double_notify_when_deleting_already_reserved(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller, featured=True)
        reserve_listing_for_offer(self._accepted_offer(listing, self._user("b@c.edu")))
        soft_delete_listing(listing, seller)   # already non-visible → no second notice
        self.assertEqual(len(self._featured_notifs(seller)), 1)

    # --- route / template ------------------------------------------------------
    def test_route_shows_empty_state_when_featured_unavailable(self):
        seller = self._user("s@c.edu")
        listing = self._listing(seller, featured=True)
        reserve_listing_for_offer(self._accepted_offer(listing, self._user("b@c.edu")))
        html = self.app.test_client().get("/marketplace").get_data(as_text=True)
        self.assertIn(_EMPTY_TEXT, html)

    def test_route_hides_section_when_nothing_featured(self):
        self._listing(self._user("s@c.edu"))   # available, not featured
        html = self.app.test_client().get("/marketplace").get_data(as_text=True)
        self.assertNotIn(_EMPTY_TEXT, html)


if __name__ == "__main__":
    unittest.main()
