"""Weekly leaderboard: rewards for the top spenders, then a reset.

The leaderboard ranks users by `weekly_spent`. Once a week the top two are paid a bonus
straight to their balance (1st +0.5, 2nd +0.25), everyone's `weekly_spent` is zeroed, and
the period restarts. The period start is persisted in the `settings` table so a restart
never loses (or double-runs) a payout.
"""
from __future__ import annotations

import asyncio
import logging
import math
import time

from sqlalchemy import select, update

from app import runtime
from app.config import get_settings
from app.db import get_session
from app.models import User

logger = logging.getLogger(__name__)

# Reward credited to balance (in the shop currency / USDT).
REWARD_1 = 0.5
REWARD_2 = 0.25

PERIOD_KEY = "lb_period_start"  # epoch seconds (str) of the current week's start
PERIOD_SECONDS = 7 * 24 * 3600
_CHECK_INTERVAL = 3600  # how often the loop checks whether the week has elapsed


def _now() -> float:
    return time.time()


def _period_start() -> float:
    raw = runtime.get(PERIOD_KEY)
    try:
        return float(raw) if raw else 0.0
    except ValueError:
        return 0.0


def days_left() -> int:
    """Whole days until the next reset (0..7)."""
    start = _period_start()
    if start <= 0:
        return 7
    remaining = PERIOD_SECONDS - (_now() - start)
    return max(0, min(7, math.ceil(remaining / 86400)))


async def _set_period_start(value: float) -> None:
    async with get_session() as session:
        await runtime.set_value(session, PERIOD_KEY, str(int(value)))


async def ensure_started() -> None:
    if _period_start() <= 0:
        await _set_period_start(_now())


async def run_payout(bot) -> list[dict]:
    """Pay the current top two, reset weekly_spent for everyone, restart the period.

    Returns a list of {place, user_id, reward, spent} for the winners (may be empty).
    """
    settings = get_settings()
    rewards = [REWARD_1, REWARD_2]
    winners: list[dict] = []
    async with get_session() as session:
        result = await session.execute(
            select(User)
            .where(User.weekly_spent > 0)
            .order_by(User.weekly_spent.desc())
            .limit(2)
        )
        top = result.scalars().all()
        for place, user in enumerate(top, 1):
            reward = rewards[place - 1]
            user.balance += reward
            winners.append(
                {"place": place, "user_id": user.user_id, "reward": reward,
                 "spent": user.weekly_spent}
            )
        # reset the week for ALL users
        await session.execute(update(User).values(weekly_spent=0))
        await session.commit()

    await _set_period_start(_now())

    # notify winners (best-effort)
    medals = {1: "🥇", 2: "🥈"}
    for w in winners:
        try:
            await bot.send_message(
                w["user_id"],
                f"{medals.get(w['place'], '🏆')} <b>Weekly leaderboard</b>\n\n"
                f"You finished #{w['place']} this week!\n"
                f"🎁 Reward: <b>+{w['reward']} {settings.currency}</b> added to your balance.",
            )
        except Exception:
            pass
    logger.info("Weekly leaderboard payout done: %s winner(s).", len(winners))
    return winners


async def weekly_loop(bot) -> None:
    """Background task: reset the leaderboard once a week."""
    await ensure_started()
    while True:
        try:
            if _now() - _period_start() >= PERIOD_SECONDS:
                await run_payout(bot)
        except Exception:
            logger.exception("weekly leaderboard loop error")
        await asyncio.sleep(_CHECK_INTERVAL)
