# retoor <retoor@molodetz.nl>

from __future__ import annotations

from datetime import datetime, timedelta, timezone

from devplacepy.database import conditional_update_row, get_table

from .. import economy


class GameError(Exception):
    pass


def _now() -> datetime:
    return datetime.now(timezone.utc)


def _iso(value: datetime) -> str:
    return value.isoformat()


def _parse(value: str) -> datetime | None:
    if not value:
        return None
    try:
        return datetime.fromisoformat(value)
    except ValueError:
        return None


def _today() -> str:
    return _now().date().isoformat()


def _iso_week(value: datetime | None = None) -> str:
    value = value or _now()
    year, week, _ = value.isocalendar()
    return f"{year}-W{week:02d}"


def _parse_date(value: str):
    parsed = _parse(value)
    return parsed.date() if parsed else None


def _farms():
    return get_table("game_farms")


def _plots():
    return get_table("game_plots")


def _quests():
    return get_table("game_quests")


def _steals():
    return get_table("game_steals")


def last_steal_at(thief_uid: str, owner_uid: str) -> datetime | None:
    from devplacepy.database import db

    rows = db.query(
        "SELECT stolen_at FROM game_steals WHERE thief_uid = :thief AND owner_uid = :owner "
        "ORDER BY stolen_at DESC LIMIT 1",
        thief=thief_uid,
        owner=owner_uid,
    )
    for row in rows:
        return _parse(row.get("stolen_at") or "")
    return None


def steal_cooldown_remaining(thief_uid: str, owner_uid: str, now: datetime) -> int:
    latest = last_steal_at(thief_uid, owner_uid)
    if not latest:
        return 0
    elapsed = (now - latest).total_seconds()
    return max(0, int(economy.STEAL_COOLDOWN_SECONDS - elapsed))


def raids_against_today(owner_uid: str, now: datetime) -> int:
    from devplacepy.database import db

    cutoff = _iso(now - timedelta(days=1))
    rows = db.query(
        "SELECT COUNT(*) AS raids FROM game_steals "
        "WHERE owner_uid = :owner AND stolen_at >= :cutoff",
        owner=owner_uid,
        cutoff=cutoff,
    )
    for row in rows:
        return int(row.get("raids") or 0)
    return 0


def next_streak(last_daily_at: str, streak: int, now: datetime) -> int:
    return streak + 1 if _parse_date(last_daily_at or "") == now.date() - timedelta(days=1) else 1


def _lvl(farm: dict, key: str) -> int:
    return int(farm.get(key) or 0)


PERK_COLUMN = {perk.key: f"perk_{perk.key}" for perk in economy.PERKS}


def _update_farm(farm_uid: str, fields: dict) -> None:
    fields = {**fields, "uid": farm_uid, "updated_at": _iso(_now())}
    _farms().update(fields, ["uid"])


def conditional_update_farm(farm_uid: str, set_clause: str, where_clause: str, params: dict) -> int:
    return conditional_update_row("game_farms", farm_uid, set_clause, where_clause, params)


def clear_plot(plot_uid: str, expected_planted_at: str) -> int:
    return conditional_update_row(
        "game_plots",
        plot_uid,
        "crop_key = '', planted_at = '', ready_at = '', watered_by = '[]', raided_fraction = 0",
        "crop_key != '' AND planted_at = :expected_planted",
        {"expected_planted": expected_planted_at},
    )


def refund_farm(farm_uid: str, coins: int) -> None:
    if coins <= 0:
        return
    conditional_update_farm(
        farm_uid, "coins = COALESCE(coins, 0) + :refund", "1 = 1", {"refund": coins}
    )


def _refresh_level(farm_uid: str) -> dict:
    for _ in range(4):
        row = _farms().find_one(uid=farm_uid)
        if not row:
            return {}
        xp_seen = int(row.get("xp") or 0)
        level = economy.level_for_xp(xp_seen)
        if int(row.get("level") or 1) == level:
            return row
        rows = conditional_update_farm(
            farm_uid,
            "level = :level",
            "COALESCE(xp, 0) = :xp_seen",
            {"level": level, "xp_seen": xp_seen},
        )
        if rows:
            return {**row, "level": level}
    return _farms().find_one(uid=farm_uid) or {}


def credit_farm(
    farm: dict,
    *,
    coins: int = 0,
    xp: int = 0,
    harvests: int = 0,
    stars: int = 0,
    era_active: bool = False,
    extra: dict | None = None,
) -> dict:
    set_parts = [
        "coins = MAX(0, COALESCE(coins, 0) + :coins_delta)",
        "xp = COALESCE(xp, 0) + :xp_delta",
        "total_harvests = COALESCE(total_harvests, 0) + :harvests_delta",
        "lifetime_coins_earned = COALESCE(lifetime_coins_earned, 0) + :earned_delta",
        "lifetime_harvests = COALESCE(lifetime_harvests, 0) + :harvests_delta",
    ]
    params = {
        "coins_delta": coins,
        "xp_delta": xp,
        "harvests_delta": harvests,
        "earned_delta": max(0, coins),
    }
    if stars:
        set_parts.append("stars = COALESCE(stars, 0) + :stars_delta")
        params["stars_delta"] = stars
    if harvests:
        set_parts.append(
            "harvests_week = CASE WHEN harvests_week_start = :week "
            "THEN COALESCE(harvests_week, 0) + :harvests_delta ELSE :harvests_delta END"
        )
        set_parts.append("harvests_week_start = :week")
        params["week"] = _iso_week()
    if era_active:
        set_parts.append("era_coins = COALESCE(era_coins, 0) + :earned_delta")
        set_parts.append("era_harvests = COALESCE(era_harvests, 0) + :harvests_delta")
    if extra:
        for index, (key, value) in enumerate(extra.items()):
            set_parts.append(f"{key} = :extra_{index}")
            params[f"extra_{index}"] = value
    conditional_update_farm(farm["uid"], ", ".join(set_parts), "1 = 1", params)
    return _refresh_level(farm["uid"])
