#!/usr/bin/env python3
"""Weekly / monthly leaderboard — correctness + integration tests.

Covers:
  A. window_rows() time-window math (7d / 30d / all-time) with controlled rows
  B. /leaderboard & / render all three panels with right data
  C. admin match award → PointEvents written (win=100, part=20), idempotent re-award
  D. template details: window chips, YOU badge, hints, no loading screens

Run while app code is importable:  python scripts/test_leaderboard_windows.py
"""
import sys, os
from datetime import datetime, timedelta

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}")

app = create_app = None
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.leaderboard import PointEvent
from app.services.leaderboard_service import window_rows, award_match_points, POINTS_WIN, POINTS_PARTICIPATION

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

print("═" * 60)
print("A. window math with controlled PointEvents (sandbox game/user)")
print("═" * 60)

# pre-clean leftovers from any previous crashed run
from app.models.match import Match, MatchParticipant
_stale = Game.query.filter_by(slug="__lb_test").first()
if _stale:
    for _x in Match.query.filter_by(game_id=_stale.id).all():
        PointEvent.query.filter_by(match_id=_x.id).delete(synchronize_session=False)
        for _p in _x.participants.all():
            db.session.delete(_p)
        db.session.delete(_x)
    PointEvent.query.filter(PointEvent.game_id == _stale.id).delete(synchronize_session=False)
    GameMode.query.filter_by(game_id=_stale.id).delete(synchronize_session=False)
    db.session.delete(_stale)
    db.session.commit()

# Sandbox game + mode → fully isolated from SEED/demo data
game = Game(slug="__lb_test", name="__lb_test")
db.session.add(game)
db.session.flush()
mode = GameMode(game_id=game.id, slug="__lb_test", name="__lb_test", squad_size=1)
db.session.add(mode)
db.session.flush()
userA = User.query.filter(User.username == "Champ").first()
userB = User.query.filter(User.username == "zynox").first()
check("sandbox game + users exist", bool(game and userA and userB))

# Clean sandbox events for these two users on this game, then place controlled rows
PointEvent.query.filter(PointEvent.game_id == game.id,
                        PointEvent.user_id.in_([userA.id, userB.id]),
                        PointEvent.reason.like("TEST:%")).delete(synchronize_session=False)
db.session.flush()
now = datetime.utcnow()
rows_spec = [
    (userA.id, "win",           POINTS_WIN, 3,  0.0),   # inside weekly
    (userA.id, "participation", POINTS_PARTICIPATION, 10, 0.0),  # monthly only
    (userA.id, "win",           POINTS_WIN, 45, 0.0),   # global only
    (userB.id, "win",           POINTS_WIN, 20, 0.0),   # monthly (not weekly)
]
for uid, kind, pts, days_ago, prize in rows_spec:
    e = PointEvent(user_id=uid, game_id=game.id, kind=kind, points=pts, prize=prize,
                   reason="TEST:window", created_at=now - timedelta(days=days_ago))
    db.session.add(e)
db.session.commit()

g_rows = window_rows(game.id, days=None)
w_rows = window_rows(game.id, days=7)
m_rows = window_rows(game.id, days=30)

gA = next((r for r in g_rows if r.user_id == userA.id), None)
wA = next((r for r in w_rows if r.user_id == userA.id), None)
mA = next((r for r in m_rows if r.user_id == userA.id), None)
wB = next((r for r in w_rows if r.user_id == userB.id), None)
mB = next((r for r in m_rows if r.user_id == userB.id), None)

check("weekly: A shows only 3-day win (100 pts, 1 win)", wA and wA.points == 100 and wA.wins == 1,
      f"got {wA and wA.points}")
check("weekly: B (20-day event) NOT in weekly", wB is None)
check("monthly: A = 100+20 = 120 pts, 1 win", mA and mA.points == 120 and mA.wins == 1,
      f"got {mA and mA.points}")
check("monthly: B = 100 pts present", mB and mB.points == 100, f"got {mB and mB.points}")
check("monthly: A ranked above B (120>100)",
      mA and mB and mA.rank < mB.rank)
check("global: A = 220 pts (3+10+45 day events)", gA and gA.points == 220, f"got {gA and gA.points}")
check("global: A win_rate = 2 wins / 3 matches = 66.7%", gA and abs(gA.win_rate - 66.7) < 0.01,
      f"got {gA and gA.win_rate}")
check("rank is 1-based sequential", all(r.rank == i for i, r in enumerate(g_rows, 1)))

print()
print("═" * 60)
print("B. pages render all three panels with correct data")
print("═" * 60)

client = app.test_client()
admin = User.query.filter(User.username == "testadmin").first()
with client.session_transaction() as s:
    s["user_id"] = admin.id
    s["lang"] = "en"

rv = client.get("/leaderboard")
check("/leaderboard 200", rv.status_code == 200, rv.status_code)
html = rv.get_data(as_text=True)
for marker in ['id="lb-panel-global"', 'id="lb-panel-weekly"', 'id="lb-panel-monthly"']:
    check(f"panel {marker}", marker in html)
check("tab chips 7D/30D present", 'data-lb-panel="lb-panel-weekly"' in html and ">7D</span>" in html)
check("window hints render", "Points earned in the last 7 days" in html and "last 30 days" in html)
check("points rules chips render", "+100 pts" in html and "+20 pts" in html)
check("admin (testadmin) has data on game-1 page", "testadmin" in html)
check("YOU badge for current user", 'lb-you-badge' in html)
check("no loading overlay markup", "fightskillLoading" not in html and "loading-progress" not in html)
check("tab JS present (instant switch)", "data-lb-panel" in html and "lb-panel" in html)

rv2 = client.get("/")
html2 = rv2.get_data(as_text=True)
check("root 200 (language page intact, no loop regression)", rv2.status_code == 200, rv2.status_code)

rv3 = client.get("/home")
check("/home 200", rv3.status_code == 200, rv3.status_code)
html3 = rv3.get_data(as_text=True)
check("home preview has weekly/monthly panels", 'id="lb-panel-weekly"' in html3 and 'id="lb-panel-monthly"' in html3)

# FA locale sanity — web.py before_request applies user.lang, so swap the profile lang
_prev_lang = admin.lang
admin.lang = "fa"
db.session.commit()
rv4 = client.get("/leaderboard")
h4 = rv4.get_data(as_text=True)
check("fa: weekly hint translated", "۷ روز گذشته" in h4)
check("fa: YOU badge = شما", ">شما</span>" in h4)
check("fa: dir=rtl", 'dir="rtl"' in h4)
admin.lang = _prev_lang or "en"
db.session.commit()

print()
print("═" * 60)
print("C. admin match award → real point events + idempotency")
print("═" * 60)

from app.models.match import Match, MatchParticipant
m = Match(game_id=game.id, mode_id=mode.id, title="TEST award match", starts_at=now,
          entry_fee=0, prize_pool=100, capacity=10, status="upcoming")
db.session.add(m)
db.session.flush()
for uid in [userA.id, userB.id]:
    db.session.add(MatchParticipant(match_id=m.id, user_id=uid, status="registered"))
db.session.commit()

participants = m.participants.all()
check("match has >=2 participants for award test", len(participants) >= 2, len(participants))

first_events = PointEvent.query.filter_by(match_id=m.id).count()
check("no events before award", first_events == 0)

first_award, summary = award_match_points(m, participants, [participants[0].id])
db.session.commit()
check("first award flag True", first_award is True)
check("one event per participant", PointEvent.query.filter_by(match_id=m.id).count() == len(participants))
win_ev = PointEvent.query.filter_by(match_id=m.id, kind="win").first()
lose_ev = PointEvent.query.filter_by(match_id=m.id, kind="participation").first()
check("winner event = 100 pts", win_ev and win_ev.points == POINTS_WIN)
check("loser event = 20 pts", lose_ev and lose_ev.points == POINTS_PARTICIPATION)

# re-award → replaced, never double-counted
_, _ = award_match_points(m, participants, [participants[0].id])
db.session.commit()
check("re-award keeps exactly one event per participant (idempotent)",
      PointEvent.query.filter_by(match_id=m.id).count() == len(participants))

# prize attached to winner event
_, summary2 = award_match_points(m, participants, [participants[0].id], {participants[0].id: 55.5})
db.session.commit()
w2 = PointEvent.query.filter_by(match_id=m.id, kind="win").first()
check("prize recorded on winner event", w2 and abs(w2.prize - 55.5) < 0.01, w2 and w2.prize)

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

# ── cleanup sandbox rows (TEST: + award test match events + sandbox game) ──
PointEvent.query.filter(PointEvent.reason.like("TEST:%")).delete(synchronize_session=False)
PointEvent.query.filter_by(match_id=m.id).delete(synchronize_session=False)
for p in m.participants.all():
    db.session.delete(p)
db.session.delete(m)
db.session.delete(mode)
db.session.delete(game)
db.session.commit()
print("sandbox test rows cleaned.")
ctx.pop()
sys.exit(1 if FAIL else 0)
