"""Marketplace UX polish & micro-interaction tests (Phase 9).

These assert the rendered markup that powers the CSS/JS polish — badges, empty
states, skeletons, scroll-snap hooks, lazy images and fallbacks — plus that the
discovery data itself is unchanged (no business-logic regression).
"""

import os
import unittest
from datetime import timedelta
from decimal import Decimal

os.environ["TEST_DATABASE_URL"] = "sqlite:///:memory:"

from flask import render_template_string
from werkzeug.security import generate_password_hash

from app import create_app
from app.extensions import db
from app.models import (
    Category,
    Listing,
    ListingCondition,
    ListingImage,
    ListingStatus,
    ListingVisibility,
    ReviewStatus,
    User,
)
from app.models.base import utcnow
from app.models.enums import UserRole


class UiPolishTests(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()

    def _seed(self, n=3, featured_first=True, with_images=False):
        seller = User(is_verified=True, email="s@c.edu", password_hash=generate_password_hash("Passw0rd!"), role=UserRole.STUDENT)
        db.session.add(seller)
        db.session.commit()
        cat = Category(name="Books", description="d", is_active=True, display_order=1)
        db.session.add(cat)
        db.session.commit()
        for i in range(n):
            l = Listing(
                sku=f"S{i}", seller_id=seller.id, category_id=cat.id, title=f"Item {i}", slug=f"s{i}",
                description="d", price=Decimal("50"), condition=ListingCondition.GOOD,
                status=ListingStatus.AVAILABLE, visibility=ListingVisibility.PUBLIC, review_status=ReviewStatus.APPROVED,
            )
            if featured_first and i == 0:
                l.is_featured = True
                l.featured_at = utcnow()
                l.featured_until = utcnow() + timedelta(days=7)
            db.session.add(l)
            db.session.commit()
            if with_images:
                db.session.add(ListingImage(listing_id=l.id, image_path=f"img-{i}.jpg", display_order=1))
                db.session.commit()
        return seller

    def _home(self):
        return self.app.test_client().get("/marketplace").get_data(as_text=True)

    # --- badge rendering (reusable macro, every kind) ---
    def test_badge_macro_renders_every_kind(self):
        tpl = "{% from 'components/badges.html' import mk_badge %}{{ mk_badge(kind, overlay=ov) }}"
        cases = {
            "trending": ("mk-badge-trending", "Trending"),
            "featured": ("mk-badge-featured", "Featured"),
            "new": ("mk-badge-new", "New"),
            "popular": ("mk-badge-popular", "Popular"),
            "premium": ("mk-badge-premium", "Premium Seller"),
            "reported": ("mk-badge-reported", "Reported"),
            "sold": ("mk-badge-sold", "Sold"),
            "reserved": ("mk-badge-reserved", "Reserved"),
        }
        for kind, (cls, label) in cases.items():
            html = render_template_string(tpl, kind=kind, ov=False)
            self.assertIn("mk-badge", html)
            self.assertIn(cls, html)
            self.assertIn(label, html)
        # overlay modifier
        self.assertIn("mk-badge-overlay", render_template_string(tpl, kind="trending", ov=True))
        # unknown kind renders nothing (no stray markup)
        self.assertNotIn("mk-badge", render_template_string(tpl, kind="nope", ov=False))

    def test_section_badges_on_homepage(self):
        self._seed()
        html = self._home()
        self.assertIn("mk-badge-trending", html)   # trending row
        self.assertIn("mk-badge-featured", html)   # featured row
        self.assertIn("mk-badge-new", html)        # new arrivals row
        # badges live in the single top overlay (left cluster), not floating
        self.assertIn("listing-overlay-top", html)
        self.assertIn("listing-overlay-left", html)

    # --- scroll-snap / scroller hooks ---
    def test_scroll_snap_classes(self):
        self._seed()
        html = self._home()
        self.assertIn("discovery-scroll", html)
        self.assertIn("data-discovery-scroll", html)   # JS wheel/drag hook
        self.assertIn('tabindex="0"', html)             # keyboard focusable scroller

    # --- skeleton loading ---
    def test_skeleton_rendering(self):
        self._seed()
        html = self._home()
        self.assertIn("discovery-skeletons", html)
        self.assertIn("mk-skeleton-card", html)
        self.assertIn("mk-skeleton-img", html)
        self.assertIn("mk-skeleton-title", html)
        self.assertIn("mk-skeleton-price", html)
        # real content wrapper coexists (revealed once is-loading clears)
        self.assertIn("discovery-real", html)

    # --- empty states ---
    def test_empty_states(self):
        self._seed()  # anonymous client -> no personalization / view history
        html = self._home()
        self.assertIn("discovery-empty", html)
        # Recommended shows a personalize prompt for anonymous / no-history visitors.
        self.assertIn("personalize this section for you.", html)
        # a contextual CTA is offered, not blank whitespace
        self.assertIn("discovery-empty-cta", html)
        # Recently viewed is hidden entirely for anonymous visitors (no placeholder).
        self.assertNotIn("Recently viewed", html)

    def test_trending_empty_state_when_no_listings(self):
        # No listings at all -> trending shows its friendly empty state + CTA.
        html = self._home()
        self.assertIn("Nothing is trending yet", html)
        self.assertIn("discovery-empty", html)

    # --- image behaviour ---
    def test_images_lazy_load(self):
        self._seed(with_images=True)
        html = self._home()
        self.assertIn('loading="lazy"', html)
        self.assertIn('decoding="async"', html)

    def test_missing_image_fallback(self):
        self._seed()  # listings created without images
        html = self._home()
        self.assertIn("listing-thumb-placeholder", html)  # graceful placeholder, no broken img

    # --- view all ---
    def test_view_all_links(self):
        self._seed()
        html = self._home()
        self.assertIn("discovery-viewall", html)
        self.assertIn("View all", html)

    # --- no regression to discovery functionality ---
    def test_discovery_data_still_renders(self):
        self._seed(n=3)
        html = self._home()
        real = html.split('class="discovery-real"', 1)[-1]
        self.assertIn("Item 0", real)   # a seeded listing appears in a discovery row
        # filtered catalog view still bypasses discovery entirely
        filtered = self.app.test_client().get("/marketplace?sort=price_low").get_data(as_text=True)
        self.assertNotIn("discovery-real", filtered)

    # --- accessibility hooks ---
    def test_accessibility_hooks_present(self):
        self._seed()
        html = self._home()
        self.assertIn("aria-label", html)
        self.assertIn('aria-hidden="true"', html)  # decorative icons/skeletons


if __name__ == "__main__":
    unittest.main()
