"""Reputation service (Trust System — Phase 3).

The **only** place ``UserReputation`` values are read in bulk or written. Values
are recomputed (a single bounded aggregate query) exactly when a review or a
completed transaction is created — never during page rendering, which reads the
cached columns directly. This keeps trust display O(1) per page regardless of how
many reviews/transactions a user has.
"""

from __future__ import annotations

from sqlalchemy import case, func
from sqlalchemy.orm import selectinload

from app.extensions import db
from app.models import CompletedTransaction, Review, User, UserReputation

# A review counts as "positive" at 4★ or above (drives positive_review_percentage
# and, later, the Trusted-Seller badge).
POSITIVE_RATING_THRESHOLD = 4


# --- Read path (rendering) — cache only, no AVG/COUNT over source tables --------

def get_reputation(user_id: int) -> UserReputation | None:
    """Cached reputation for one user (or ``None`` if none exists yet)."""
    return db.session.scalar(
        db.select(UserReputation).where(UserReputation.user_id == user_id)
    )


def get_reputation_map(items) -> dict[int, UserReputation]:
    """``{user_id: UserReputation}`` for a page of listings (or user ids).

    One query for the whole page — mirrors ``get_premium_seller_ids`` so listing
    grids show ratings without an N+1 lookup per card. Users without a row are
    simply absent (rendered as "New Seller").
    """
    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 {}
    rows = db.session.scalars(
        db.select(UserReputation).where(UserReputation.user_id.in_(ids))
    ).all()
    return {rep.user_id: rep for rep in rows}


# --- Write path (on review / transaction) ---------------------------------------

def get_or_create_reputation(user: User) -> UserReputation:
    """Return the user's reputation row, creating a zeroed one if absent."""
    return _get_or_create(user.id)


def _get_or_create(user_id: int) -> UserReputation:
    rep = get_reputation(user_id)
    if rep is None:
        rep = UserReputation(user_id=user_id)
        db.session.add(rep)
        db.session.flush()  # assign id; caller commits
    return rep


def update_reputation_after_review(review: Review) -> UserReputation:
    """Refresh the *reviewee's* rating aggregates after a review is created.

    Recomputed from the reviews table in a single grouped query (the one place
    this aggregation is allowed), then cached on ``UserReputation``.
    """
    reviewee_id = review.reviewee_id
    count, avg, positive = db.session.execute(
        db.select(
            func.count(Review.id),
            func.coalesce(func.avg(Review.rating), 0),
            func.coalesce(func.sum(case((Review.rating >= POSITIVE_RATING_THRESHOLD, 1), else_=0)), 0),
        ).where(Review.reviewee_id == reviewee_id)
    ).one()
    count, positive = int(count), int(positive)

    rep = _get_or_create(reviewee_id)
    rep.review_count = count
    rep.average_rating = round(float(avg or 0), 2)
    rep.positive_review_percentage = round((positive / count * 100), 2) if count else 0
    db.session.commit()
    _publish_new_badges(reviewee_id)
    return rep


def _publish_new_badges(user_id: int) -> None:
    """Publish a "badge earned" activity for any badge the user now holds that
    hasn't been announced yet. Idempotent (deduped by the badge title), so it's
    safe to call after every reputation update."""
    from app.activity.services import publish_activity
    from app.models import ActivityEvent
    from app.reviews.badges import get_badges

    user = db.session.get(User, user_id)
    if user is None:
        return
    badges = get_badges(user)
    if not badges:
        return
    announced = set(db.session.scalars(
        db.select(ActivityEvent.title).where(
            ActivityEvent.user_id == user_id, ActivityEvent.event_type == "badge_earned")
    ).all())
    for badge in badges:
        title = f"You earned the {badge['name']} badge"
        if title in announced:
            continue
        publish_activity(user, "badge_earned", title, message=badge["description"])


def update_reputation_after_transaction(transaction: CompletedTransaction) -> None:
    """Refresh completed-sales (seller) and completed-purchases (buyer) counts.

    Recomputed from the transactions table (one COUNT each) then cached.
    """
    seller_sales = db.session.scalar(
        db.select(func.count(CompletedTransaction.id))
        .where(CompletedTransaction.seller_id == transaction.seller_id)
    ) or 0
    buyer_purchases = db.session.scalar(
        db.select(func.count(CompletedTransaction.id))
        .where(CompletedTransaction.buyer_id == transaction.buyer_id)
    ) or 0

    seller_rep = _get_or_create(transaction.seller_id)
    seller_rep.completed_sales = int(seller_sales)
    buyer_rep = _get_or_create(transaction.buyer_id)
    buyer_rep.completed_purchases = int(buyer_purchases)
    db.session.commit()
    _publish_new_badges(transaction.seller_id)


# --- Recent reviews (profile page only; a bounded LIMIT query, not an aggregate) -

def _first_name(user: User) -> str:
    profile = getattr(user, "profile", None)
    if profile and profile.full_name:
        return profile.full_name.strip().split(" ")[0]
    return (user.email.split("@")[0] if user and user.email else "A reviewer")


def get_recent_reviews_for_user(user: User, limit: int = 5) -> list[dict]:
    """The latest reviews received by ``user`` (default 5), reviewer eager-loaded.

    Only the reviewer's **first name** is exposed — never their email."""
    reviews = db.session.scalars(
        db.select(Review)
        .options(selectinload(Review.reviewer).selectinload(User.profile))
        .where(Review.reviewee_id == user.id)
        .order_by(Review.created_at.desc(), Review.id.desc())
        .limit(limit)
    ).all()
    return [
        {
            "rating": r.rating,
            "comment": r.comment,
            "reviewer_first_name": _first_name(r.reviewer),
            "created_at": r.created_at,
        }
        for r in reviews
    ]
