# retoor <retoor@molodetz.nl>

from __future__ import annotations

import json
from datetime import datetime, timedelta

from .. import economy
from .common import (
    PERK_COLUMN,
    _iso_week,
    _lvl,
    _now,
    _parse,
    _parse_date,
    _quests,
    next_streak,
    steal_cooldown_remaining,
)
from .cosmetics import owned_cosmetic_keys
from .era import active_era_name
from .farm import get_farm, get_plots
from .infrastructure import INFRA_COLUMN, owns_infrastructure
from .market import market_factor_for
from .mastery import MASTERY_COLUMN
from .quests import ensure_quests


def _watered_by(plot: dict) -> list[str]:
    try:
        value = json.loads(plot.get("watered_by") or "[]")
        return value if isinstance(value, list) else []
    except (ValueError, TypeError):
        return []


def _plot_state(plot: dict, now: datetime) -> str:
    if not plot.get("crop_key"):
        return "empty"
    ready_at = _parse(plot.get("ready_at", ""))
    if ready_at and now >= ready_at:
        return "ready"
    return "growing"


def serialize_plot(
    plot: dict,
    *,
    viewer_uid: str,
    owner_uid: str,
    now: datetime,
    yield_level: int = 0,
    prestige: int = 0,
    growth_level: int = 0,
    ci_tier: int = 1,
    legacy_mult_level: int = 0,
    legacy_speed_level: int = 0,
    defense_level: int = 0,
    steal_locked_until: int = 0,
    steal_grace_bonus: int = 0,
    steal_floor: float = 0.1,
    steal_reduction: float = 0.0,
    steal_cap: float = 1.0,
    registry_boost: bool = False,
    canary: bool = False,
    underdog: bool = False,
    contract_boost: bool = False,
) -> dict:
    state = _plot_state(plot, now)
    crop = economy.crop_for(plot.get("crop_key", ""))
    ready_at = _parse(plot.get("ready_at", ""))
    remaining = 0
    if state == "growing" and ready_at:
        remaining = max(0, int((ready_at - now).total_seconds()))
    watered = _watered_by(plot)
    is_owner = viewer_uid == owner_uid
    golden = economy.is_golden(plot.get("uid", ""), plot.get("planted_at", ""))
    can_water = (
        state == "growing"
        and not is_owner
        and bool(viewer_uid)
        and viewer_uid not in watered
        and len(watered) < economy.MAX_WATERS_PER_PLOT
    )
    grace = economy.effective_steal_grace(defense_level, steal_grace_bonus)
    protected = bool(ready_at) and now < ready_at + timedelta(seconds=grace)
    immune = bool(crop and crop.steal_immune)
    stripped = float(plot.get("raided_fraction") or 0.0) >= 1.0
    eligible = (
        state == "ready"
        and not is_owner
        and bool(viewer_uid)
        and bool(crop)
        and ready_at is not None
        and not immune
        and not stripped
    )
    can_steal = eligible and not protected and steal_locked_until <= 0
    steal_reason = ""
    if immune and state == "ready" and not is_owner:
        steal_reason = "immune"
    elif stripped and state == "ready" and not is_owner:
        steal_reason = "stripped"
    elif eligible and protected:
        steal_reason = "protected"
    elif eligible and steal_locked_until > 0:
        steal_reason = "cooldown"
    market_factor = market_factor_for(crop.key) if crop else 1.0
    raided_fraction = float(plot.get("raided_fraction") or 0.0)
    steal_share = min(
        economy.effective_steal_fraction(
            defense_level, steal_floor, steal_reduction, steal_cap
        ),
        max(0.0, 1.0 - raided_fraction),
    )
    steal_coins = (
        max(
            1,
            round(
                economy.realizable_harvest_coins(
                    crop,
                    yield_level,
                    prestige,
                    legacy_mult_level,
                    market_factor,
                    golden,
                    contract_boost,
                    underdog,
                )
                * steal_share
            ),
        )
        if can_steal
        else 0
    )
    fertilize_cost = 0
    if state == "growing" and is_owner and crop:
        full_grow = economy.grow_seconds_for(
            crop, ci_tier, growth_level, legacy_speed_level, registry_boost
        )
        reduce_by = int(remaining * economy.FERTILIZE_FRACTION)
        realizable = economy.realizable_harvest_coins(
            crop,
            yield_level,
            prestige,
            legacy_mult_level,
            market_factor,
            golden,
            contract_boost,
            underdog,
            canary,
        )
        fertilize_cost = economy.fertilize_click_cost(realizable, reduce_by, full_grow)
    return {
        "slot": plot.get("slot_index", 0),
        "state": state,
        "crop_key": plot.get("crop_key", ""),
        "crop_name": crop.name if crop else "",
        "crop_icon": crop.icon if crop else "",
        "reward_coins": crop.reward_coins if crop else 0,
        "reward_xp": crop.reward_xp if crop else 0,
        "ready_at": plot.get("ready_at", ""),
        "remaining_seconds": remaining,
        "watered_count": len(watered),
        "max_waters": economy.MAX_WATERS_PER_PLOT,
        "can_water": can_water,
        "can_steal": can_steal,
        "steal_coins": steal_coins,
        "steal_cooldown_seconds": steal_locked_until if steal_reason == "cooldown" else 0,
        "steal_reason": steal_reason,
        "is_golden": golden and (is_owner or can_steal),
        "fertilize_cost": fertilize_cost,
        "raided_fraction": round(raided_fraction, 4),
        "raided_pct": round(raided_fraction * 100),
    }


def serialize_farm(
    farm: dict, *, viewer: dict | None, owner: dict, now: datetime | None = None
) -> dict:
    from .actions import _auto_harvest, boost_flags
    from .defense import charge_upkeep
    from .treasury import grant_status, treasury_balance

    now = now or _now()
    viewer_uid = viewer["uid"] if viewer else ""
    owner_uid = owner["uid"]
    is_owner = viewer_uid == owner_uid
    auto = {"harvested": 0, "coins": 0, "xp": 0}
    if is_owner and _lvl(farm, "legacy_autoharvest") > 0:
        farm, auto = _auto_harvest(farm, owner_uid, now)
    if is_owner:
        farm = charge_upkeep(farm, now)
        farm = _reset_harvests_week(farm, now)
    prestige = _lvl(farm, "prestige")
    growth_level = _lvl(farm, "perk_growth")
    discount_level = _lvl(farm, "perk_discount")
    yield_level = _lvl(farm, "perk_yield")
    legacy_mult_level = _lvl(farm, "legacy_multiplier")
    legacy_speed_level = _lvl(farm, "legacy_speed")
    defense_level = _lvl(farm, "legacy_defense")
    ci_tier = int(farm.get("ci_tier", 1))
    steal_locked_until = (
        steal_cooldown_remaining(viewer_uid, owner_uid, now)
        if viewer_uid and not is_owner
        else 0
    )
    building_defense_level = _lvl(farm, "defense_level")
    defense_tier_info = economy.defense_tier(building_defense_level)
    steal_floor = defense_tier_info.steal_fraction_floor
    steal_cap = (
        economy.OBSERVABILITY_STEAL_CAP
        if owns_infrastructure(farm, "observability")
        else 1.0
    )
    registry_boost = owns_infrastructure(farm, "registry")
    underdog_active, contract_active = boost_flags(farm, now)
    plots = get_plots(farm["uid"])
    serialized_plots = [
        serialize_plot(
            plot,
            viewer_uid=viewer_uid,
            owner_uid=owner_uid,
            now=now,
            yield_level=yield_level,
            prestige=prestige,
            growth_level=growth_level,
            ci_tier=ci_tier,
            legacy_mult_level=legacy_mult_level,
            legacy_speed_level=legacy_speed_level,
            defense_level=defense_level,
            steal_locked_until=steal_locked_until,
            steal_grace_bonus=defense_tier_info.grace_bonus,
            steal_floor=steal_floor,
            steal_reduction=defense_tier_info.steal_reduction,
            steal_cap=steal_cap,
            registry_boost=registry_boost,
            canary=owns_infrastructure(farm, "canary"),
            underdog=underdog_active,
            contract_boost=contract_active,
        )
        for plot in plots
    ]
    progress = economy.level_progress(int(farm.get("xp", 0)))
    level = progress["level"]
    next_tier = economy.next_ci_tier(ci_tier)
    ci_entry = economy.CI_BY_TIER.get(ci_tier)
    streak = _lvl(farm, "streak")
    daily_available = is_owner and _daily_available(farm, now)
    mastery_earned = _lvl(farm, "mastery_points_earned_total")
    era_name = active_era_name()
    underdog_until = _parse(farm.get("underdog_boost_until") or "")
    underdog_seconds = (
        max(0, int((underdog_until - now).total_seconds())) if underdog_until else 0
    )
    contract_until = _parse(farm.get("contract_boost_until") or "")
    contract_seconds = (
        max(0, int((contract_until - now).total_seconds())) if contract_until else 0
    )
    income_scale = economy.social_reward_scale(prestige, legacy_mult_level)
    effective_streak = next_streak(farm.get("last_daily_at") or "", streak, now)
    coins = int(farm.get("coins", 0))
    refactor_fee = economy.refactor_cost(prestige, coins)
    carryover_level = _lvl(farm, "legacy_carryover")
    grant = (
        grant_status(farm, now)
        if is_owner
        else {"available": False, "amount": 0, "reason": ""}
    )
    return {
        "owner_username": owner.get("username", ""),
        "owner_uid": owner_uid,
        "is_owner": is_owner,
        "coins": int(farm.get("coins", 0)),
        "xp": int(farm.get("xp", 0)),
        "level": level,
        "level_into": progress["into_level"],
        "level_span": progress["span"],
        "level_is_max": progress["is_max"],
        "total_harvests": int(farm.get("total_harvests", 0)),
        "plot_count": int(farm.get("plot_count", economy.STARTING_PLOTS)),
        "max_plots": economy.MAX_PLOTS,
        "next_plot_cost": (
            economy.plot_cost(int(farm.get("plot_count", economy.STARTING_PLOTS)))
            if int(farm.get("plot_count", economy.STARTING_PLOTS)) < economy.MAX_PLOTS
            else 0
        ),
        "ci_tier": ci_tier,
        "ci_label": ci_entry.label if ci_entry else "",
        "ci_speed": economy.ci_speed(ci_tier),
        "ci_next_tier": next_tier.tier if next_tier else 0,
        "ci_next_label": next_tier.label if next_tier else "",
        "ci_next_cost": next_tier.upgrade_cost if next_tier else 0,
        "plots": serialized_plots,
        "crops": [
            economy.crop_payload(
                crop,
                ci_tier,
                level,
                growth_level,
                discount_level,
                yield_level,
                prestige,
                legacy_mult_level,
                legacy_speed_level,
                market_factor_for(crop.key),
                mastery_earned,
                era_name,
                registry_boost,
            )
            for crop in economy.CROPS
        ],
        "prestige": prestige,
        "prestige_multiplier": economy.prestige_multiplier(prestige),
        "prestige_min_level": economy.PRESTIGE_MIN_LEVEL,
        "prestige_available": is_owner and level >= economy.PRESTIGE_MIN_LEVEL,
        "refactor_cost": refactor_fee,
        "refactor_affordable": is_owner
        and level >= economy.PRESTIGE_MIN_LEVEL
        and coins >= refactor_fee,
        "refactor_carryover_pct": round(
            economy.refactor_carryover_fraction(carryover_level) * 100
        ),
        "refactor_carryover_preview": economy.refactor_carryover(
            coins, refactor_fee, carryover_level
        ),
        "grant_available": grant["available"],
        "grant_amount": grant["amount"],
        "grant_reason": grant["reason"],
        "treasury_balance": treasury_balance() if is_owner else 0,
        "streak": streak,
        "daily_available": daily_available,
        "daily_streak_reset": daily_available and effective_streak == 1 and streak > 0,
        "daily_reward": economy.daily_reward(
            effective_streak if daily_available else max(1, streak), income_scale
        ),
        "perks": _serialize_perks(farm) if is_owner else [],
        "quests": _serialize_quests(owner_uid, now) if is_owner else [],
        "stars": _lvl(farm, "stars"),
        "legacy": _serialize_legacy(farm) if is_owner else [],
        "steal_cooldown_seconds": steal_locked_until,
        "mastery_points": _lvl(farm, "mastery_points"),
        "mastery_points_earned_total": mastery_earned,
        "mastery": _serialize_mastery(farm) if is_owner else [],
        "infrastructure": _serialize_infrastructure(farm) if is_owner else [],
        "defense_level": building_defense_level,
        "defense_tier_name": defense_tier_info.name,
        "defense_upkeep_daily": economy.daily_upkeep(defense_tier_info, int(farm.get("coins", 0))),
        "defense_downgrade_available": is_owner and building_defense_level > 0,
        "defense_next_cost": (
            economy.next_defense_tier(building_defense_level).upgrade_cost
            if economy.next_defense_tier(building_defense_level)
            else 0
        ),
        "cosmetics": _serialize_cosmetics(owner_uid) if is_owner else [],
        "active_title": farm.get("active_title") or "",
        "underdog_boost_seconds_remaining": underdog_seconds,
        "contract_boost_seconds_remaining": contract_seconds,
        "auto_harvested": int(auto.get("harvested") or 0),
        "auto_harvest_coins": int(auto.get("coins") or 0),
        "auto_harvest_xp": int(auto.get("xp") or 0),
        "steal_max_per_victim_per_day": economy.STEAL_MAX_PER_VICTIM_PER_DAY,
        "mastery_analytics_unlocked": bool(_lvl(farm, "mastery_analytics")),
        "lifetime_coins_earned": _lvl(farm, "lifetime_coins_earned"),
        "lifetime_harvests": _lvl(farm, "lifetime_harvests"),
        "harvests_week": _lvl(farm, "harvests_week"),
        "era_active": era_name is not None,
        "era_name": era_name or "",
        "era_coins": _lvl(farm, "era_coins"),
        "era_harvests": _lvl(farm, "era_harvests"),
    }


def _reset_harvests_week(farm: dict, now: datetime) -> dict:
    from .common import _update_farm

    current_week = _iso_week(now)
    if farm.get("harvests_week_start") == current_week:
        return farm
    fields = {"harvests_week": 0, "harvests_week_start": current_week}
    _update_farm(farm["uid"], fields)
    return {**farm, **fields}


def _serialize_mastery(farm: dict) -> list[dict]:
    upgrades = []
    for m in economy.MASTERY_UPGRADES:
        level = _lvl(farm, MASTERY_COLUMN[m.key])
        maxed = level >= m.max_level
        upgrades.append(
            {
                "key": m.key,
                "name": m.name,
                "icon": m.icon,
                "description": m.description,
                "level": level,
                "max_level": m.max_level,
                "cost": 0 if maxed else economy.mastery_cost(m, level),
                "maxed": maxed,
                "effect": m.description,
            }
        )
    return upgrades


def _serialize_infrastructure(farm: dict) -> list[dict]:
    rows = []
    for infra in economy.INFRASTRUCTURE:
        owned = bool(_lvl(farm, INFRA_COLUMN[infra.key]))
        rows.append(
            {
                "key": infra.key,
                "name": infra.name,
                "icon": infra.icon,
                "description": infra.description,
                "cost": infra.cost,
                "min_prestige": infra.min_prestige,
                "owned": owned,
            }
        )
    return rows


def _serialize_cosmetics(user_uid: str) -> list[dict]:
    owned = owned_cosmetic_keys(user_uid)
    return [
        {
            "key": c.key,
            "name": c.name,
            "icon": c.icon,
            "description": c.description,
            "cost_coins": c.cost_coins,
            "kind": c.kind,
            "owned": c.key in owned,
        }
        for c in economy.COSMETICS
        if c.era_key is None
    ]


def _daily_available(farm: dict, now: datetime) -> bool:
    last = _parse_date(farm.get("last_daily_at") or "")
    return last != now.date()


def _serialize_perks(farm: dict) -> list[dict]:
    perks = []
    for perk in economy.PERKS:
        level = _lvl(farm, PERK_COLUMN[perk.key])
        maxed = level >= perk.max_level
        perks.append(
            {
                "key": perk.key,
                "name": perk.name,
                "icon": perk.icon,
                "description": perk.description,
                "level": level,
                "max_level": perk.max_level,
                "cost": 0 if maxed else economy.perk_cost(perk, level),
                "maxed": maxed,
                "effect": economy.perk_value_text(perk, level),
            }
        )
    return perks


def _serialize_legacy(farm: dict) -> list[dict]:
    upgrades = []
    for up in economy.LEGACY_UPGRADES:
        level = _lvl(farm, f"legacy_{up.key}")
        maxed = level >= up.max_level
        upgrades.append(
            {
                "key": up.key,
                "name": up.name,
                "icon": up.icon,
                "description": up.description,
                "level": level,
                "max_level": up.max_level,
                "cost": 0 if maxed else economy.legacy_cost(up, level),
                "maxed": maxed,
                "effect": economy.legacy_value_text(up, level),
            }
        )
    return upgrades


def _serialize_quests(user_uid: str, now: datetime) -> list[dict]:
    from .quests import ensure_weekly_contract

    farm = get_farm(user_uid)
    if not farm:
        return []
    day = now.date().isoformat()
    ensure_quests(farm, day)
    rows = sorted(
        _quests().find(farm_uid=farm["uid"], day=day, scope="daily"),
        key=lambda row: row.get("slot_index", 0),
    )
    if _lvl(farm, "mastery_contracts"):
        iso_week = _iso_week(now)
        ensure_weekly_contract(farm, iso_week)
        rows += sorted(
            _quests().find(farm_uid=farm["uid"], day=iso_week, scope="weekly"),
            key=lambda row: row.get("slot_index", 0),
        )
    quests = []
    for row in rows:
        goal = int(row.get("goal") or 0)
        progress = min(goal, int(row.get("progress") or 0))
        claimed = bool(row.get("claimed"))
        quests.append(
            {
                "kind": row.get("kind", ""),
                "label": row.get("label", ""),
                "goal": goal,
                "progress": progress,
                "reward_coins": int(row.get("reward_coins") or 0),
                "reward_xp": int(row.get("reward_xp") or 0),
                "claimed": claimed,
                "can_claim": (not claimed) and goal > 0 and progress >= goal,
                "scope": row.get("scope", "daily"),
                "reward_stars": int(row.get("reward_stars") or 0),
            }
        )
    return quests
