"""Fair, quality-aware picker ranking for automatic assignment.

Used by real B2C/B2B checkout and the AI chat "schedule it for me" shortcut,
so all three paths pick consistently. Combines proximity, how loaded a
picker already is that specific day, and their rating into one composite
score, instead of a strict "nearest wins" or "least busy wins" rule — the
point being that a slightly farther but much less busy or noticeably
higher-rated picker can still win over the single closest one. Otherwise
the same 2-3 closest pickers get every order forever, which is neither fair
to the rest of the picker pool nor actually optimal for the customer
(a picker who's already stacked with orders that day is a worse bet than
their raw distance alone suggests)."""

# Proximity and fairness matter about equally — that's the core "closer AND
# less loaded" balance the algorithm exists to strike. Rating is a real but
# smaller factor (a well-reviewed picker deserves a genuine edge, not a
# decisive one — a customer's delivery shouldn't hinge entirely on stars).
# The same-route bonus is a flat top-up: being genuinely on the way to this
# destination already beats a small ranking gap, but not a clearly better
# pick on the other three factors.
WEIGHT_PROXIMITY = 0.40
WEIGHT_FAIRNESS = 0.35
WEIGHT_RATING = 0.25
SAME_ROUTE_BONUS = 0.15

# A picker needs at least this many ratings before their own average counts
# in full. Fewer than that and their score leans toward the platform
# average instead, so one lucky (or unlucky) rating can't swing a brand-new
# picker to the very top or bottom of the list — see _confidence_shrunk_rating.
RATING_CONFIDENCE_THRESHOLD = 10

# What a picker with literally zero ratings anywhere on the platform scores
# — the exact midpoint of the 1-5 scale, i.e. neither rewarded nor
# penalized for not having a track record yet.
DEFAULT_RATING = 3.0


def _confidence_shrunk_rating(rating_summary, platform_average):
    """Blends a picker's own average toward the platform average until
    they've built up enough ratings of their own to trust it outright.
    Standard technique behind IMDB/Yelp-style ranking — prevents a single
    5-star rating from outranking a picker with hundreds averaging 4.8."""
    baseline = platform_average if platform_average is not None else DEFAULT_RATING
    if not rating_summary or not rating_summary.get('count'):
        return baseline
    confidence = min(rating_summary['count'] / RATING_CONFIDENCE_THRESHOLD, 1.0)
    return confidence * rating_summary['average'] + (1 - confidence) * baseline


def rank_pickers(pickers, rating_summaries_by_picker, platform_average_rating):
    """Scores and sorts `pickers` best-first, in place, and returns the same
    list re-sorted. Each picker dict must have 'user_id', 'distance_km', and
    'today_count' (their non-cancelled order count for the target delivery
    date); 'same_route' is optional (B2B/chat callers may not track it).
    Adds 'score' and 'rating_display' to every picker dict. Safe to call
    with an empty list."""
    if not pickers:
        return pickers

    max_distance = max(p['distance_km'] for p in pickers) or 1.0
    max_today_count = max(p.get('today_count', 0) for p in pickers) or 1

    for p in pickers:
        proximity_score = 1 - (p['distance_km'] / max_distance)
        fairness_score = 1 - (p.get('today_count', 0) / max_today_count)

        rating_summary = rating_summaries_by_picker.get(p['user_id'])
        rating_avg = _confidence_shrunk_rating(rating_summary, platform_average_rating)
        rating_score = (rating_avg - 1) / 4  # 1-5 star scale -> 0-1

        score = (
            WEIGHT_PROXIMITY * proximity_score
            + WEIGHT_FAIRNESS * fairness_score
            + WEIGHT_RATING * rating_score
        )
        if p.get('same_route'):
            score += SAME_ROUTE_BONUS

        p['score'] = round(score, 4)
        p['rating_display'] = round(rating_avg, 1)

    pickers.sort(key=lambda item: -item['score'])
    return pickers
