# retoor <retoor@molodetz.nl>

from __future__ import annotations

from devplacepy.cache import TTLCache
from devplacepy.utils import generate_uid

from .. import economy
from .common import GameError, _farms, _iso, _lvl, _now, _plots, conditional_update_farm


_leaderboard_cache = TTLCache(ttl=15, max_size=8)


def get_farm(user_uid: str) -> dict | None:
    return _farms().find_one(user_uid=user_uid)


def ensure_farm(user_uid: str) -> dict:
    farm = get_farm(user_uid)
    if farm:
        return farm
    now = _iso(_now())
    uid = generate_uid()
    _farms().insert(
        {
            "uid": uid,
            "user_uid": user_uid,
            "coins": economy.STARTING_COINS,
            "xp": 0,
            "level": 1,
            "ci_tier": 1,
            "plot_count": economy.STARTING_PLOTS,
            "total_harvests": 0,
            "upkeep_amnesty": 1,
            "created_at": now,
            "updated_at": now,
        }
    )
    for slot_index in range(economy.STARTING_PLOTS):
        _create_plot(uid, user_uid, slot_index, now)
    return get_farm(user_uid)


def _create_plot(farm_uid: str, user_uid: str, slot_index: int, now: str) -> None:
    _plots().insert(
        {
            "uid": generate_uid(),
            "farm_uid": farm_uid,
            "user_uid": user_uid,
            "slot_index": slot_index,
            "crop_key": "",
            "planted_at": "",
            "ready_at": "",
            "watered_by": "[]",
            "created_at": now,
            "updated_at": now,
        }
    )


def get_plots(farm_uid: str) -> list[dict]:
    rows = list(_plots().find(farm_uid=farm_uid))
    rows.sort(key=lambda row: row.get("slot_index", 0))
    return rows


def _plot_at(farm_uid: str, slot_index: int) -> dict | None:
    return _plots().find_one(farm_uid=farm_uid, slot_index=slot_index)


def buy_plot(user: dict) -> dict:
    farm = ensure_farm(user["uid"])
    plot_count = int(farm.get("plot_count", economy.STARTING_PLOTS))
    if plot_count >= economy.MAX_PLOTS:
        raise GameError("All plots are already unlocked.")
    cost = economy.plot_cost(plot_count)
    rows = conditional_update_farm(
        farm["uid"],
        set_clause="coins = coins - :cost, plot_count = :new_count",
        where_clause="plot_count = :current_count AND coins >= :cost",
        params={"cost": cost, "new_count": plot_count + 1, "current_count": plot_count},
    )
    if rows == 0:
        farm = get_farm(user["uid"])
        if int(farm.get("plot_count", economy.STARTING_PLOTS)) != plot_count:
            raise GameError("Plot count already changed - refresh and try again.")
        raise GameError("Not enough coins for a new plot.")
    _create_plot(farm["uid"], user["uid"], plot_count, _iso(_now()))
    return {"plot_count": plot_count + 1, "spent": cost}


def upgrade_ci(user: dict) -> dict:
    farm = ensure_farm(user["uid"])
    ci_tier = int(farm.get("ci_tier", 1))
    next_tier = economy.next_ci_tier(ci_tier)
    if not next_tier:
        raise GameError("CI is already at the top tier.")
    rows = conditional_update_farm(
        farm["uid"],
        set_clause="coins = coins - :cost, ci_tier = :new_tier",
        where_clause="ci_tier = :current_tier AND coins >= :cost",
        params={"cost": next_tier.upgrade_cost, "new_tier": next_tier.tier, "current_tier": ci_tier},
    )
    if rows == 0:
        farm = get_farm(user["uid"])
        if int(farm.get("ci_tier", 1)) != ci_tier:
            raise GameError("CI tier already changed - refresh and try again.")
        raise GameError("Not enough coins to upgrade CI.")
    return {"ci_tier": next_tier.tier, "spent": next_tier.upgrade_cost}


def leaderboard(limit: int = 25) -> list[dict]:
    cached = _leaderboard_cache.get(f"top:{limit}")
    if cached is not None:
        return cached
    farms = sorted(
        _farms().find(),
        key=economy.farm_score,
        reverse=True,
    )[:limit]
    if not farms:
        return []
    from devplacepy.database import get_users_by_uids

    users = get_users_by_uids([farm["user_uid"] for farm in farms])
    entries = []
    for rank, farm in enumerate(farms, start=1):
        owner = users.get(farm["user_uid"])
        if not owner:
            continue
        entries.append(
            {
                "rank": rank,
                "username": owner.get("username", ""),
                "level": int(farm.get("level", 1)),
                "xp": int(farm.get("xp", 0)),
                "coins": int(farm.get("coins", 0)),
                "total_harvests": int(farm.get("total_harvests", 0)),
                "prestige": int(farm.get("prestige") or 0),
                "score": economy.farm_score(farm),
                "title": economy.cosmetic_title_name(farm.get("active_title") or ""),
            }
        )
    _leaderboard_cache.set(f"top:{limit}", entries)
    return entries


def _entry(rank: int, farm: dict, owner: dict, score: int) -> dict:
    return {
        "rank": rank,
        "username": owner.get("username", ""),
        "level": int(farm.get("level", 1)),
        "xp": int(farm.get("xp", 0)),
        "coins": int(farm.get("coins", 0)),
        "total_harvests": int(farm.get("total_harvests", 0)),
        "prestige": int(farm.get("prestige") or 0),
        "score": score,
        "title": economy.cosmetic_title_name(farm.get("active_title") or ""),
    }


def _ranked_entries(farms: list[dict], score_fn, limit: int) -> list[dict]:
    ranked = sorted(farms, key=score_fn, reverse=True)[:limit]
    if not ranked:
        return []
    from devplacepy.database import get_users_by_uids

    users = get_users_by_uids([farm["user_uid"] for farm in ranked])
    entries = []
    for rank, farm in enumerate(ranked, start=1):
        owner = users.get(farm["user_uid"])
        if not owner:
            continue
        entries.append(_entry(rank, farm, owner, score_fn(farm)))
    return entries


def leaderboard_prestige(limit: int = 25) -> list[dict]:
    return _ranked_entries(
        list(_farms().find()),
        lambda f: _lvl(f, "prestige") * 1_000_000 + _lvl(f, "stars"),
        limit,
    )


def leaderboard_harvests_week(limit: int = 25) -> list[dict]:
    return _ranked_entries(list(_farms().find()), lambda f: _lvl(f, "harvests_week"), limit)


def leaderboard_fair_play(limit: int = 25) -> list[dict]:
    return _ranked_entries(
        list(_farms().find()),
        lambda f: economy.fair_play_score(_lvl(f, "harvests_week"), int(f.get("coins", 0))),
        limit,
    )


def leaderboard_time_to_kernel(limit: int = 25) -> list[dict]:
    farms = [
        f
        for f in _farms().find()
        if _lvl(f, "prestige") > 0
        and _lvl(f, "last_kernel_harvest_prestige") == _lvl(f, "prestige")
    ]
    ranked = sorted(farms, key=lambda f: _lvl(f, "time_to_kernel_seconds"))[:limit]
    if not ranked:
        return []
    from devplacepy.database import get_users_by_uids

    users = get_users_by_uids([farm["user_uid"] for farm in ranked])
    entries = []
    for rank, farm in enumerate(ranked, start=1):
        owner = users.get(farm["user_uid"])
        if not owner:
            continue
        entry = _entry(rank, farm, owner, _lvl(farm, "time_to_kernel_seconds"))
        entry["time_to_kernel_seconds"] = _lvl(farm, "time_to_kernel_seconds")
        entries.append(entry)
    return entries


RAID_EFFICIENCY_WINDOW_DAYS = 30


def leaderboard_raid_efficiency(limit: int = 25) -> list[dict]:
    from datetime import timedelta

    from devplacepy.database import db, get_users_by_uids

    cutoff = _iso(_now() - timedelta(days=RAID_EFFICIENCY_WINDOW_DAYS))
    rows = db.query(
        "SELECT thief_uid, COUNT(*) AS raids, SUM(coins) AS total_coins "
        "FROM game_steals WHERE stolen_at >= :cutoff "
        "GROUP BY thief_uid HAVING COUNT(*) >= :min_raids "
        "ORDER BY (SUM(coins) * 1.0 / COUNT(*)) DESC LIMIT :limit",
        cutoff=cutoff,
        min_raids=economy.MIN_RAIDS_FOR_EFFICIENCY_BOARD,
        limit=limit,
    )
    ranked = [
        (row["thief_uid"], int(row["raids"]), int(row["total_coins"] or 0)) for row in rows
    ]
    if not ranked:
        return []
    uids = [uid for uid, _, _ in ranked]
    users = get_users_by_uids(uids)
    farms_table = _farms()
    farms_by_uid = {
        f["user_uid"]: f for f in farms_table.find(farms_table.table.columns.user_uid.in_(uids))
    }
    entries = []
    for rank, (uid, raids, total_coins) in enumerate(ranked, start=1):
        owner = users.get(uid)
        if not owner:
            continue
        farm = farms_by_uid.get(uid, {})
        avg = total_coins / raids
        entry = _entry(rank, farm, owner, round(avg))
        entry["raid_avg"] = round(avg, 1)
        entries.append(entry)
    return entries


LEADERBOARD_BOARDS = {
    "score": leaderboard,
    "prestige": leaderboard_prestige,
    "harvests": leaderboard_harvests_week,
    "raids": leaderboard_raid_efficiency,
    "time_to_kernel": leaderboard_time_to_kernel,
    "fair_play": leaderboard_fair_play,
}

_board_cache = TTLCache(ttl=15, max_size=32)


def leaderboard_for(board: str, limit: int = 25) -> list[dict]:
    cache_key = f"{board}:{limit}"
    cached = _board_cache.get(cache_key)
    if cached is not None:
        return cached
    if board == "era":
        from .era import leaderboard_era

        entries = leaderboard_era(limit)
    else:
        entries = LEADERBOARD_BOARDS.get(board, leaderboard)(limit)
    _board_cache.set(cache_key, entries)
    return entries
