#!/usr/bin/env python3
"""Task 11 — Admin winner announcement → ranking → leaderboard stats.

Covers:
  A. Admin award route (full HTTP flow): winners announced → prizes paid →
     notifications → point ledger → match finished
  B. Public announcement: /tournament/<id> shows winners podium; /tournaments
     shows RESULTS section
  C. Leaderboard rows show "X matches · Y wins" under player name in all
     three windows (global / weekly / monthly)
  D. Idempotent re-award (no double points/prize)
  E. Guest cannot see announcement broken state; i18n fa translation renders

Run:  python scripts/test_winner_announce.py
"""
import sys, os, json as _json
from datetime import datetime

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

PASS, FAIL = 0, 0
def check(name, cond, extra=""):
    global PASS, FAIL
    if cond:
        PASS += 1
        print(f"  ✓ {name}")
    else:
        FAIL += 1
        print(f"  ✗ FAIL: {name} {extra}")

from app import create_app
from app.extensions import db
from app.models.game import Game, GameMode
from app.models.user import User
from app.models.match import Match, MatchParticipant
from app.models.leaderboard import PointEvent
from app.models.wallet import Wallet, Transaction
from app.models.notification import Notification

app = create_app()
app.config["WTF_CSRF_ENABLED"] = False
ctx = app.app_context()
ctx.push()

print("═" * 60)
print("Setup: sandbox game / match / 3 participants")
print("═" * 60)

game = Game(slug="__wa_test", name="__wa_test")
db.session.add(game)
db.session.flush()
mode = GameMode(game_id=game.id, slug="__wa_test", name="__wa_test", squad_size=1)
db.session.add(mode)
db.session.flush()

users = [User.query.filter_by(username=u).first() for u in ("Champ", "zynox", "ProLeague")]
check("3 test users exist", all(users), [u.username if u else None for u in users])
u_admin = User.query.filter_by(username="admin").first() or User.query.filter_by(email="admin@vapolyx.gg").first()
check("admin user exists", bool(u_admin))

m = Match(game_id=game.id, mode_id=mode.id, title="TEST winner announce match",
          starts_at=datetime.utcnow(), entry_fee=0, prize_pool=300,
          capacity=10, status="upcoming")
db.session.add(m)
db.session.flush()
parts = []
for u in users:
    p = MatchParticipant(match_id=m.id, user_id=u.id, status="registered")
    db.session.add(p)
    parts.append(p)
db.session.commit()

# snapshot wallet balances for cleanup math
def payout_balance(uid):
    w = Wallet.query.filter_by(user_id=uid, kind="payout").first()
    return float(w.balance) if w else 0.0
bal_before = {u.id: payout_balance(u.id) for u in users}
notif_before = Notification.query.filter(Notification.user_id.in_([u.id for u in users])).count()

print("═" * 60)
print("A. Admin award via real HTTP flow")
print("═" * 60)

client = app.test_client()
with client.session_transaction() as s:
    s["user_id"] = u_admin.id

resp = client.get(f"/admin/matches/{m.id}/award")
check("admin award GET 200", resp.status_code == 200, resp.status_code)
html = resp.get_data(as_text=True)
check("award page has rank slot selects", 'name="slot_1"' in html and 'name="slot_6"' in html)
check("award page lists all participants", html.count(f'value="{parts[0].id}"') >= 1 and html.count("<option") >= 3)
check("award page has per-rank prize inputs", 'name="prize_1"' in html and 'name="prize_6"' in html)
check("award page shows announce hint", "اعلام" in html)

winner_pid = parts[0].id
resp = client.post(f"/admin/matches/{m.id}/award", data={
    "winners": [str(winner_pid)],
    "total_prize": "300",
}, follow_redirects=True)
check("admin award POST 200", resp.status_code == 200, resp.status_code)

db.session.expire_all()
m2 = db.session.get(Match, m.id)
check("match status finished", m2.status == "finished", m2.status)
wids = _json.loads(m2.winner_participant_ids or "[]")
check("winner id stored", wids == [winner_pid], wids)

events = PointEvent.query.filter_by(match_id=m2.id).all()
check("3 point events (1 per participant)", len(events) == 3, len(events))
win_ev = next((e for e in events if e.kind == "win"), None)
part_ev = next((e for e in events if e.kind == "participation"), None)
check("win event +100", win_ev and win_ev.points == 100)
check("participation event +20", part_ev and part_ev.points == 20)
check("prize on winner event", win_ev and abs(win_ev.prize - 300.0) < 0.01, win_ev and win_ev.prize)

bal_after = {u.id: payout_balance(u.id) for u in users}
check("winner payout +300", abs(bal_after[users[0].id] - bal_before[users[0].id] - 300) < 0.01)
check("losers payout unchanged", all(abs(bal_after[u.id] - bal_before[u.id]) < 0.01 for u in users[1:]))
txs = Transaction.query.filter_by(kind="prize").filter(Transaction.reason.like(f"%{m2.title}%")).count()
check("prize transaction created", txs >= 1, txs)

notif_after = Notification.query.filter(Notification.user_id.in_([u.id for u in users])).count()
check("notifications sent (win+losers)", notif_after - notif_before >= 3, notif_after - notif_before)

print("═" * 60)
print("B. Public announcement pages")
print("═" * 60)

anon = app.test_client()
resp = anon.get(f"/tournament/{m2.id}")
check("guest tournament detail 200", resp.status_code == 200, resp.status_code)
html = resp.get_data(as_text=True)
check("winners announcement visible to guest", "winners-announce" in html)
check("podium medal rendered", "🥇" in html)
check("champion title rendered", "Champion" in html)
check("winner name shown", users[0].display_name in html)
check("prize share shown", "300.00" in html)
check("points shown on podium", "+100" in html)
check("finished badge shown", "Finished" in html)
check("join button replaced", "JOIN TOURNAMENT" not in html)

resp = anon.get("/tournaments")
check("tournaments page 200", resp.status_code == 200, resp.status_code)
html = resp.get_data(as_text=True)
check("RESULTS section rendered", "RESULTS — WINNERS" in html or "results-winners" in html)
check("winner name in results card", users[0].display_name in html)

# fa locale announcement translation
fa_client = app.test_client()
with fa_client.session_transaction() as s:
    s["lang"] = "fa"
resp = fa_client.get(f"/tournament/{m2.id}")
hfa = resp.get_data(as_text=True)
check("fa: برندگان اعلام شدند rendered", "برندگان اعلام شدند" in hfa)
check("fa: قهرمان rendered", "قهرمان" in hfa)

print("═" * 60)
print("C. Leaderboard shows matches · wins under player")
print("═" * 60)

resp = client.get(f"/leaderboard?game_id={game.id}")
check("leaderboard page 200", resp.status_code == 200, resp.status_code)
html = resp.get_data(as_text=True)
check("lb-player-sub present", "lb-player-sub" in html)
# winner: 1 match · 1 win (singular), losers: 1 match · 0 wins (plural)
# locale-agnostic: admin profile lang may be en or fa (both translations verified)
import re as _re
_en = _re.search(r"🎮 1 match · 🏆 1 win", html) is not None
_fa = _re.search(r"🎮 1 مسابقه · 🏆 1 برد", html) is not None
check("winner stats line (en|fa) singular", _en or _fa, html.count("lb-player-sub"))
_pl = _re.search(r"🎮 1 match · 🏆 0 wins", html) is not None or \
      _re.search(r"🎮 1 مسابقه · 🏆 0 برد", html) is not None
check("loser stats line plural", _pl)
check("no bad grammar '1 matches'", "1 matches" not in html)
# each participant has exactly 1 match
check("3 stat subtitles (one per player)", html.count("lb-player-sub") >= 3, html.count("lb-player-sub"))

from app.services.leaderboard_service import window_rows
rows = window_rows(game.id, days=None)
top = rows[0] if rows else None
check("global top row = winner", top and top.user_id == users[0].id)
check("top row matches=1 wins=1", top and top.matches == 1 and top.wins == 1)
check("loser rows matches=1 wins=0", all(r.wins == 0 and r.matches == 1 for r in rows[1:]))
check("ordering by points desc", [r.points for r in rows] == sorted([r.points for r in rows], reverse=True))

rows_w = window_rows(game.id, days=7)
check("weekly window also shows the match", any(r.user_id == users[0].id and r.wins == 1 for r in rows_w))
rows_m = window_rows(game.id, days=30)
check("monthly window also shows the match", any(r.user_id == users[0].id and r.wins == 1 for r in rows_m))

print("═" * 60)
print("D. Idempotent re-award")
print("═" * 60)

resp = client.post(f"/admin/matches/{m2.id}/award", data={
    "winners": [str(winner_pid)],
    "total_prize": "300",
}, follow_redirects=True)
check("re-award POST 200", resp.status_code == 200)
db.session.expire_all()
check("still exactly 3 point events", PointEvent.query.filter_by(match_id=m2.id).count() == 3,
      PointEvent.query.filter_by(match_id=m2.id).count())
bal_after2 = {u.id: payout_balance(u.id) for u in users}
check("re-award does not double-pay prize", abs(bal_after2[users[0].id] - bal_after[users[0].id]) < 0.01,
      (bal_after2[users[0].id], bal_after[users[0].id]))
check("exactly one prize tx after re-award",
      Transaction.query.filter_by(kind="prize").filter(Transaction.reason == f"Prize - {m2.title}").count() == 1)

print("═" * 60)
print("E. Per-rank prizes (1st/2nd different amounts)")
print("═" * 60)

resp = client.post(f"/admin/matches/{m2.id}/award", data={
    "slot_1": str(parts[0].id), "prize_1": "250",
    "slot_2": str(parts[1].id), "prize_2": "50",
}, follow_redirects=True)
check("per-rank award POST 200", resp.status_code == 200)
db.session.expire_all()
m3 = db.session.get(Match, m2.id)
wids3 = _json.loads(m3.winner_participant_ids or "[]")
check("two winners stored in order", wids3 == [parts[0].id, parts[1].id], wids3)
events3 = PointEvent.query.filter_by(match_id=m3.id).all()
prizes3 = {e.user_id: float(e.prize) for e in events3 if e.prize}
check("1st prize 250 on ledger", prizes3.get(users[0].id) == 250.0, prizes3)
check("2nd prize 50 on ledger", prizes3.get(users[1].id) == 50.0, prizes3)
bal_after3 = {u.id: payout_balance(u.id) for u in users}
check("1st paid exactly 250 (net adjusted)", abs(bal_after3[users[0].id] - (bal_before[users[0].id] + 250)) < 0.01,
      bal_after3[users[0].id] - bal_before[users[0].id])
check("2nd paid exactly 50 (net adjusted)", abs(bal_after3[users[1].id] - (bal_before[users[1].id] + 50)) < 0.01,
      bal_after3[users[1].id] - bal_before[users[1].id])
check("3rd participant unpaid", abs(bal_after3[users[2].id] - bal_before[users[2].id]) < 0.01)

resp = anon.get(f"/tournament/{m3.id}")
html = resp.get_data(as_text=True)
check("podium shows $250.00", "$250.00" in html)
check("podium shows $50.00", "$50.00" in html)

# duplicate rank guard
resp = client.post(f"/admin/matches/{m3.id}/award", data={
    "slot_1": str(parts[0].id), "prize_1": "100",
    "slot_2": str(parts[0].id), "prize_2": "100",
}, follow_redirects=False)
check("duplicate rank rejected (redirect back)", resp.status_code == 302, resp.status_code)

print("═" * 60)
print(f"RESULT: {PASS} passed / {FAIL} failed")
print("═" * 60)

# ── cleanup sandbox ──
PointEvent.query.filter_by(match_id=m2.id).delete(synchronize_session=False)
for p in m2.participants.all():
    db.session.delete(p)
db.session.delete(m2)
db.session.delete(mode)
db.session.delete(game)
# remove prize transactions + revert payout balances (recompute net delta live)
for u in users:
    delta = payout_balance(u.id) - bal_before[u.id]
    if abs(delta) > 0.01:
        w = Wallet.query.filter_by(user_id=u.id, kind="payout").first()
        w.balance = (w.balance or 0) - delta
Transaction.query.filter(Transaction.reason.like(f"%{m2.title}%")).delete(synchronize_session=False)
Notification.query.filter(
    Notification.user_id.in_([u.id for u in users]),
    Notification.created_at >= datetime.utcnow().replace(second=0, microsecond=0) - __import__("datetime").timedelta(minutes=30),
    Notification.title.in_(["🏆 Prize Received", "🎯 Points Earned"]),
).delete(synchronize_session=False)
db.session.commit()
print("sandbox test rows cleaned.")
ctx.pop()
sys.exit(1 if FAIL else 0)
