"""Public landing page and marketplace catalog routes.

Both views are public (no login required) and reuse the marketplace service
layer for all data access. Query-string inputs on the catalog are parsed and
validated here before being handed to :func:`search_published_listings`.
"""

from __future__ import annotations

from decimal import Decimal, InvalidOperation

from flask import current_app, jsonify, render_template, request, url_for
from flask_login import current_user

from app.advertisements.services import (
    get_active_advertisement,
    record_impressions,
)
from app.premium.permissions import can_view_without_ads
from app.premium.services import get_premium_seller_ids
from app.reviews.badges import BADGE_FILTER_OPTIONS, get_badge_map
from app.reviews.reputation import get_reputation_map
from app.extensions import db
from app.main import main_bp
from app.marketplace.discovery import collect_listing_seller_ids, get_homepage_sections
from app.marketplace.services import (
    CATALOG_CONDITION_OPTIONS,
    CATALOG_SORT_OPTIONS,
    SELLER_RATING_OPTIONS,
    get_featured_listing,
    get_featured_listings,
    get_search_suggestions,
    search_listings,
)
from app.models import Category
from app.premium.permissions import can_feature_listing


def _active_categories() -> list[Category]:
    """Active categories ordered for the catalog filter controls."""
    return list(
        db.session.scalars(
            db.select(Category)
            .where(Category.is_active.is_(True))
            .order_by(Category.display_order, Category.name)
        )
    )


def _parse_price(raw: str | None) -> Decimal | None:
    """Parse a non-negative price from user input, or None when unusable."""
    if raw is None or raw.strip() == "":
        return None
    try:
        value = Decimal(raw.strip())
    except (InvalidOperation, ValueError):
        return None
    return value if value >= 0 else None


@main_bp.route("/")
def landing():
    """Public marketing landing page with a few featured listings."""
    # Premium members browse ad-free.
    landing_ad = None if can_view_without_ads(current_user) else get_active_advertisement("landing")
    if landing_ad:
        record_impressions([landing_ad])
    return render_template(
        "main/landing.html",
        featured=get_featured_listings(3),
        landing_ad=landing_ad,
    )


@main_bp.route("/privacy")
def privacy():
    """Public Privacy Policy page."""
    return render_template("main/privacy.html")


@main_bp.route("/terms")
def terms():
    """Public Terms of Service page."""
    return render_template("main/terms.html")


@main_bp.route("/marketplace")
def marketplace():
    """Primary public catalog: search, filter, sort and paginate all listings."""
    page = request.args.get("page", 1, type=int)
    q = (request.args.get("q") or "").strip()

    category_id = request.args.get("category", type=int)

    condition = (request.args.get("condition") or "").strip().lower()
    valid_conditions = {value for value, _ in CATALOG_CONDITION_OPTIONS}
    condition = condition if condition in valid_conditions else ""

    sort = request.args.get("sort", "newest")
    if sort not in dict(CATALOG_SORT_OPTIONS):
        sort = "newest"

    min_price = _parse_price(request.args.get("min_price"))
    max_price = _parse_price(request.args.get("max_price"))

    # Seller-quality filters (validated against the service's option lists).
    seller_rating = (request.args.get("seller_rating") or "").strip()
    if seller_rating not in {value for value, _ in SELLER_RATING_OPTIONS}:
        seller_rating = ""
    trusted_only = request.args.get("trusted") in ("1", "on", "true")
    badge = (request.args.get("badge") or "").strip()
    if badge not in {value for value, _ in BADGE_FILTER_OPTIONS}:
        badge = ""

    # The default (unfiltered) view is the discovery homepage; any active
    # search/filter/sort/page switches to the classic results catalog.
    is_discovery = not any([
        q, category_id, condition, min_price, max_price,
        seller_rating, trusted_only, badge,
        request.args.get("page"), sort != "newest",
    ])
    discovery = None
    discovery_premium_ids = set()
    if is_discovery:
        discovery = get_homepage_sections(current_user)
        discovery_premium_ids = get_premium_seller_ids(collect_listing_seller_ids(discovery))

    pagination = search_listings(
        page,
        q=q or None,
        category_id=category_id,
        condition=condition or None,
        min_price=min_price,
        max_price=max_price,
        sort=sort,
        seller_rating=seller_rating or None,
        trusted_only=trusted_only,
        badge=badge or None,
    )

    # Premium members browse ad-free.
    marketplace_ad = (
        None if can_view_without_ads(current_user)
        else get_active_advertisement("marketplace")
    )
    if marketplace_ad:
        record_impressions([marketplace_ad])

    # Gentle nudge for Premium members who haven't featured a listing yet. The
    # featured lookup only runs for premium members (short-circuit) — no extra
    # query for everyone else. Dismissible per session in the template.
    premium_feature_reminder = (
        can_feature_listing(current_user) and get_featured_listing(current_user) is None
    )

    # One cached-reputation lookup for every seller on the page (grid + discovery)
    # — no AVG/COUNT at render time, no N+1. Badges reuse that same map.
    rep_source = list(pagination.items) + (collect_listing_seller_ids(discovery) if discovery else [])
    reputation_map = get_reputation_map(rep_source)
    badge_map = get_badge_map(rep_source, reputation_map=reputation_map)

    categories = _active_categories()
    filter_chips = _build_filter_chips(
        q=q, category_id=category_id, categories=categories, condition=condition,
        min_price=min_price, max_price=max_price, sort=sort,
        seller_rating=seller_rating, trusted_only=trusted_only, badge=badge,
    )

    return render_template(
        "marketplace/browse.html",
        pagination=pagination,
        premium_seller_ids=get_premium_seller_ids(pagination.items),
        reputation_map=reputation_map,
        badge_map=badge_map,
        discovery=discovery,
        discovery_premium_ids=discovery_premium_ids,
        premium_feature_reminder=premium_feature_reminder,
        categories=categories,
        q=q,
        category_id=category_id,
        condition=condition,
        min_price=request.args.get("min_price", "").strip(),
        max_price=request.args.get("max_price", "").strip(),
        sort=sort,
        seller_rating=seller_rating,
        trusted_only=trusted_only,
        badge=badge,
        sort_options=CATALOG_SORT_OPTIONS,
        condition_options=CATALOG_CONDITION_OPTIONS,
        rating_options=SELLER_RATING_OPTIONS,
        badge_options=BADGE_FILTER_OPTIONS,
        filter_chips=filter_chips,
        has_filters=bool(filter_chips),
        marketplace_ad=marketplace_ad,
        ad_interval=current_app.config.get("MARKETPLACE_AD_INTERVAL", 12),
    )


@main_bp.route("/marketplace/suggestions")
def marketplace_suggestions():
    """Typeahead suggestions (JSON) — up to five listing titles for the query."""
    return jsonify(get_search_suggestions(request.args.get("q"), limit=5))


def _build_filter_chips(*, q, category_id, categories, condition, min_price, max_price,
                        sort, seller_rating, trusted_only, badge):
    """Active-filter chips: each carries a URL back to the catalog with just that
    filter removed. Pure presentation assembly (no business logic)."""
    current = {
        "q": q or None,
        "category": category_id or None,
        "condition": condition or None,
        "min_price": request.args.get("min_price", "").strip() or None,
        "max_price": request.args.get("max_price", "").strip() or None,
        "sort": sort if sort != "newest" else None,
        "seller_rating": seller_rating or None,
        "trusted": "1" if trusted_only else None,
        "badge": badge or None,
    }

    def without(*keys):
        args = {k: v for k, v in current.items() if v is not None and k not in keys}
        return url_for("main.marketplace", **args)

    chips = []
    if q:
        chips.append({"label": q, "remove_url": without("q")})
    if category_id:
        cat = next((c for c in categories if c.id == category_id), None)
        if cat:
            chips.append({"label": cat.name, "remove_url": without("category")})
    if condition:
        chips.append({"label": dict(CATALOG_CONDITION_OPTIONS).get(condition, condition),
                      "remove_url": without("condition")})
    if min_price is not None or max_price is not None:
        low = f"₹{min_price:,.0f}" if min_price is not None else "₹0"
        high = f"₹{max_price:,.0f}" if max_price is not None else "Any"
        chips.append({"label": f"{low} – {high}", "remove_url": without("min_price", "max_price")})
    if seller_rating:
        label = dict(SELLER_RATING_OPTIONS).get(seller_rating, seller_rating)
        chips.append({"label": label, "remove_url": without("seller_rating")})
    if trusted_only:
        chips.append({"label": "Trusted Sellers", "remove_url": without("trusted")})
    if badge:
        chips.append({"label": dict(BADGE_FILTER_OPTIONS).get(badge, badge),
                      "remove_url": without("badge")})
    if sort and sort != "newest":
        chips.append({"label": dict(CATALOG_SORT_OPTIONS).get(sort, sort),
                      "remove_url": without("sort")})
    return chips
