"""Trust badge engine (Trust System — Phase 4).

Badges are computed **dynamically** from the cached ``UserReputation`` row and
existing marketplace statistics (the cached ``Listing.favorite_count``) — there
is no badge table, no migration, and no recalculation of the underlying figures.
This module is the single source of truth: badge metadata and rules live here and
nothing about a badge is decided in a template.

Adding a future badge (Campus Verified, Early Adopter, Premium Seller, Eco
Seller…) needs only one entry in :data:`BADGE_METADATA` and one rule in
:data:`_BADGE_RULES` — no template changes.
"""

from __future__ import annotations

from sqlalchemy import and_, func
from sqlalchemy.orm import aliased

from app.extensions import db
from app.models import Listing, UserReputation
from app.reviews.reputation import get_reputation, get_reputation_map

# Thresholds — the single source of truth, shared by the Python rules (display)
# and the SQL filter expressions (search). Adding/changing a badge means editing
# only these tables, never the templates or the search service.
BADGE_THRESHOLDS: dict[str, dict] = {
    "trusted_seller": {"completed_sales": 10, "average_rating": 4.5, "review_count": 5},
    "top_seller": {"completed_sales": 50, "average_rating": 4.8, "review_count": 20},
    "great_communicator": {"positive_review_percentage": 95, "review_count": 20},
    "quick_responder": {"average_response_minutes": 30},
    "popular_seller": {"favourites": 100},
}

# Back-compat aliases (older references).
POPULAR_FAVOURITES_THRESHOLD = BADGE_THRESHOLDS["popular_seller"]["favourites"]
QUICK_RESPONDER_MAX_MINUTES = BADGE_THRESHOLDS["quick_responder"]["average_response_minutes"]

# Badges whose eligibility is a pure comparison of UserReputation columns.
_REPUTATION_BADGES = ("trusted_seller", "top_seller", "great_communicator")

# key -> display metadata. ``priority`` orders the single badge shown on a listing
# card (lower number = higher priority): Top ▸ Trusted ▸ Quick ▸ Great ▸ Popular.
BADGE_METADATA: dict[str, dict] = {
    "top_seller": {
        "icon": "⭐", "name": "Top Seller", "priority": 1,
        "description": "A proven track record of many highly-rated sales.",
    },
    "trusted_seller": {
        "icon": "🛡", "name": "Trusted Seller", "priority": 2,
        "description": "Maintains excellent ratings across multiple successful sales.",
    },
    "quick_responder": {
        "icon": "⚡", "name": "Quick Responder", "priority": 3,
        "description": "Typically replies to buyers within 30 minutes.",
    },
    "great_communicator": {
        "icon": "💬", "name": "Great Communicator", "priority": 4,
        "description": "Consistently praised for positive, helpful communication.",
    },
    "popular_seller": {
        "icon": "🔥", "name": "Popular Seller", "priority": 5,
        "description": "Listings buyers love — hundreds of saves.",
    },
}

def _badge_info(key: str) -> dict:
    return {"key": key, **BADGE_METADATA[key]}


def _meets_reputation(rep, req: dict) -> bool:
    """True when a cached reputation satisfies a reputation-column threshold set."""
    if rep is None:
        return False
    return (
        rep.completed_sales >= req.get("completed_sales", 0)
        and rep.rating >= req.get("average_rating", 0)
        and rep.review_count >= req.get("review_count", 0)
        and float(rep.positive_review_percentage or 0) >= req.get("positive_review_percentage", 0)
    )


def _earned(rep, favourites: int) -> list[dict]:
    """Earned badges (priority-sorted) for a cached reputation + favourite count.
    Reads only cached values — never the raw Review/Favorite tables."""
    keys = [key for key in _REPUTATION_BADGES if _meets_reputation(rep, BADGE_THRESHOLDS[key])]
    # Quick Responder: only when response tracking has populated a value
    # (average_response_minutes is reserved/None today → safely omitted).
    if rep is not None and rep.average_response_minutes is not None \
            and rep.average_response_minutes <= BADGE_THRESHOLDS["quick_responder"]["average_response_minutes"]:
        keys.append("quick_responder")
    if favourites >= BADGE_THRESHOLDS["popular_seller"]["favourites"]:
        keys.append("popular_seller")
    keys.sort(key=lambda k: BADGE_METADATA[k]["priority"])
    return [_badge_info(k) for k in keys]


# Buyer-facing badge filter options (search UI).
BADGE_FILTER_OPTIONS = [("", "Any badge")] + [
    (key, BADGE_METADATA[key]["name"])
    for key in sorted(BADGE_METADATA, key=lambda k: BADGE_METADATA[k]["priority"])
]


def badge_filter_expr(badge_key: str):
    """A SQLAlchemy WHERE expression selecting sellers who hold ``badge_key``,
    reusing this module's thresholds (so search never re-derives eligibility).

    Assumes the query has joined :class:`UserReputation` on the seller. The
    ``popular_seller`` filter instead correlates on the outer ``Listing.seller_id``
    (a favourite-sum subquery), so it works even without a reputation row.
    Returns ``None`` for an unknown badge.
    """
    req = BADGE_THRESHOLDS.get(badge_key)
    if req is None:
        return None
    R = UserReputation
    if badge_key in _REPUTATION_BADGES:
        conds = [R.review_count.isnot(None)]  # a reputation row must exist
        if "completed_sales" in req:
            conds.append(R.completed_sales >= req["completed_sales"])
        if "average_rating" in req:
            conds.append(R.average_rating >= req["average_rating"])
        if "review_count" in req:
            conds.append(R.review_count >= req["review_count"])
        if "positive_review_percentage" in req:
            conds.append(R.positive_review_percentage >= req["positive_review_percentage"])
        return and_(*conds)
    if badge_key == "quick_responder":
        return and_(R.average_response_minutes.isnot(None),
                    R.average_response_minutes <= req["average_response_minutes"])
    if badge_key == "popular_seller":
        inner = aliased(Listing)
        fav_sum = (
            db.select(func.coalesce(func.sum(inner.favorite_count), 0))
            .where(inner.seller_id == Listing.seller_id, inner.deleted_at.is_(None))
            .correlate(Listing)
            .scalar_subquery()
        )
        return fav_sum >= req["favourites"]
    return None


def trusted_seller_filter_expr():
    """WHERE expression for the "Trusted Sellers only" toggle — a seller holding
    the Trusted Seller **or** Top Seller badge (reuses the badge thresholds)."""
    return db.or_(badge_filter_expr("trusted_seller"), badge_filter_expr("top_seller"))


# --- Favourites: aggregate the existing cached Listing.favorite_count -----------

def _seller_favourites(user_id: int) -> int:
    return int(db.session.scalar(
        db.select(func.coalesce(func.sum(Listing.favorite_count), 0))
        .where(Listing.seller_id == user_id, Listing.deleted_at.is_(None))
    ) or 0)


def _favourites_map(user_ids) -> dict[int, int]:
    if not user_ids:
        return {}
    rows = db.session.execute(
        db.select(Listing.seller_id, func.coalesce(func.sum(Listing.favorite_count), 0))
        .where(Listing.seller_id.in_(user_ids), Listing.deleted_at.is_(None))
        .group_by(Listing.seller_id)
    ).all()
    return {seller_id: int(total) for seller_id, total in rows}


# --- Public API -----------------------------------------------------------------

def get_badges(user) -> list[dict]:
    """Earned badges for one user (priority-sorted list of metadata dicts)."""
    return _earned(get_reputation(user.id), _seller_favourites(user.id))


def has_badge(user, badge_key: str) -> bool:
    return any(b["key"] == badge_key for b in get_badges(user))


def get_badge_map(items, reputation_map=None) -> dict[int, list[dict]]:
    """``{user_id: [badge, …]}`` for a page of listings (or user ids).

    Batched like ``get_reputation_map``: at most two queries for the whole page
    (reputation + favourites) — no per-card lookup. Pass an already-computed
    ``reputation_map`` to reuse it and skip the extra reputation query.
    """
    ids: set[int] = set()
    for item in items or ():
        uid = getattr(item, "seller_id", item)
        if isinstance(uid, int):
            ids.add(uid)
    if not ids:
        return {}
    rep_map = reputation_map if reputation_map is not None else get_reputation_map(ids)
    fav_map = _favourites_map(ids)
    return {uid: _earned(rep_map.get(uid), fav_map.get(uid, 0)) for uid in ids}
