"""Leaderboard service with time-windowed (global / weekly / monthly) ranking.

Points are recorded in the `point_events` ledger:
  - winner          → POINTS_WIN          (+100)
  - participant     → POINTS_PARTICIPATION (+20)
  - admin grant     → kind="admin"

A windowed leaderboard is a SUM() over the ledger inside the window, so the
same table powers all three tabs and points age out of a window naturally.
"""
from datetime import datetime, timedelta
from collections import namedtuple

from app.extensions import db
from app.models.leaderboard import PointEvent
from app.models.user import User

POINTS_WIN = 100
POINTS_PARTICIPATION = 20

LBRow = namedtuple("LBRow", "user_id rank points wins matches win_rate prize user")

WINDOW_LABELS = {7: "weekly", 30: "monthly", None: "global"}


def window_rows(game_id, days=None, limit=100):
    """Top rows for one leaderboard window.

    days=None → all-time (GLOBAL), days=7 → WEEKLY, days=30 → MONTHLY.
    Ordered by points desc, wins desc. Returns list[LBRow].
    """
    since = (datetime.utcnow() - timedelta(days=days)) if days else None

    q = (
        db.session.query(
            PointEvent.user_id.label("user_id"),
            db.func.sum(PointEvent.points).label("points"),
            db.func.sum(db.case((PointEvent.kind == "win", 1), else_=0)).label("wins"),
            db.func.count(PointEvent.id).label("matches"),
            db.func.coalesce(db.func.sum(PointEvent.prize), 0.0).label("prize"),
        )
        .filter(PointEvent.game_id == game_id)
        .group_by(PointEvent.user_id)
    )
    if since is not None:
        q = q.filter(PointEvent.created_at >= since)

    rows = q.order_by(db.desc("points"), db.desc("wins")).limit(limit).all()

    users = {}
    if rows:
        ids = [r.user_id for r in rows]
        for u in User.query.filter(User.id.in_(ids)).all():
            users[u.id] = u

    out = []
    for idx, r in enumerate(rows, 1):
        matches = r.matches or 0
        wr = round((r.wins / matches) * 100, 1) if matches else 0.0
        out.append(LBRow(
            user_id=r.user_id, rank=idx, points=int(r.points or 0),
            wins=int(r.wins or 0), matches=matches, win_rate=wr,
            prize=round(float(r.prize or 0.0), 2), user=users.get(r.user_id),
        ))
    return out


def compute_leaderboard(game_id, days=None, limit=100):
    """Back-compatible entry point. compute_leaderboard(gid) == all-time."""
    return window_rows(game_id, days=days, limit=limit)


def award_match_points(match, participants, winner_ids, prize_by_participant=None):
    """Write point-ledger rows for a finished match (called by admin award).

    Idempotent-safe: any previous events for this match are replaced, so
    re-awarding a match never double-counts. Returns (first_award, summary)
    where summary is a list of dicts for notifications.
    """
    prize_by_participant = prize_by_participant or {}
    first_award = PointEvent.query.filter_by(match_id=match.id).count() == 0
    PointEvent.query.filter_by(match_id=match.id).delete(synchronize_session=False)

    summary = []
    for p in participants:
        won = p.id in winner_ids
        pts = POINTS_WIN if won else POINTS_PARTICIPATION
        db.session.add(PointEvent(
            user_id=p.user_id, game_id=match.game_id, match_id=match.id,
            kind="win" if won else "participation", points=pts,
            prize=float(prize_by_participant.get(p.id, 0.0)),
            reason=f"Match: {match.title}",
        ))
        summary.append({"user_id": p.user_id, "won": won, "points": pts,
                        "prize": prize_by_participant.get(p.id, 0.0)})
    return first_award, summary
