import { applyPercentFloor, toCentsBigInt } from './cents.js';

/** 1_000_000_000_000.00 cr in cents */
export const PRESTIGE_COST_CENTS = 100_000_000_000_000n;

/** Page unlock: 10_000_000_000.00 cr total wealth (balance + unlocked inventory). */
export const PRESTIGE_VISIBLE_AT_CENTS = 1_000_000_000_000n;

/** Base Pr granted on every prestige. */
export const PR_BASE_REWARD = 1000;

/**
 * For every 250_000_000_000.00 cr above the prestige cost, +1000 Pr on average.
 * (Stored in cents.)
 */
export const PR_EXCESS_CHUNK_CENTS = 25_000_000_000_000n;
export const PR_PER_EXCESS_CHUNK = 1000;

const BASE_AD_COOLDOWN_MS = 60_000;

/**
 * Skill tree upgrades bought with Pr (stackable branches).
 * level = how many times this branch has been purchased.
 * maxLevel = hard cap on purchases.
 */
export const PRESTIGE_SKILLS = {
  chance: {
    id: 'chance',
    field: 'prestigeChanceBonus',
    delta: 1,
    maxLevel: 100,
    title: 'Fortune',
    description: '+1% drop luck on case opens (permanent)',
    unit: '%',
    branch: 'drops',
  },
  gain: {
    id: 'gain',
    field: 'prestigeGainBonus',
    delta: 2,
    maxLevel: 20,
    title: 'Yield',
    description: '+2% credits on sell / auto-sell (permanent)',
    unit: '%',
    branch: 'economy',
  },
  polish: {
    id: 'polish',
    field: 'prestigeWearBonus',
    delta: 1,
    maxLevel: 100,
    title: 'Polish',
    description: 'Better float / wear rolls on case opens',
    unit: '',
    branch: 'drops',
  },
  bulk: {
    id: 'bulk',
    field: 'prestigeOpenBonus',
    delta: 1,
    maxLevel: 20,
    title: 'Bulk',
    description: '+1 max simultaneous case opens (permanent)',
    unit: '',
    branch: 'drops',
  },
  prFarmer: {
    id: 'prFarmer',
    field: 'prestigePrBonus',
    delta: 2,
    maxLevel: 50,
    title: 'PR Farmer',
    description: '+2% Pr obtained when you prestige (permanent)',
    unit: '%',
    branch: 'economy',
  },
  quality: {
    id: 'quality',
    field: 'prestigeQualityBonus',
    delta: 1,
    maxLevel: 100,
    title: 'Quality',
    description: 'Better float / wear rolls on case opens (items below Field-Tested get upgraded)',
    unit: '',
    branch: 'drops',
  },
  noTime: {
    id: 'noTime',
    field: 'prestigeAnimReduction',
    delta: 10,
    maxLevel: 100,
    title: 'noTime',
    description: 'Reduced case opening animation time',
    unit: '%',
    prefix: '−',
    branch: 'drops',
  },
  refund: {
    id: 'refund',
    field: 'prestigeRefundChance',
    delta: 2,
    maxLevel: 20,
    title: 'Refund',
    description: 'Chance to get a free case open refund',
    unit: '%',
    branch: 'drops',
  },
  respin: {
    id: 'respin',
    field: 'prestigeRespinChance',
    delta: 1,
    maxLevel: 20,
    title: 'Respin',
    description: 'Chance to free-respin the open (shows first result, then reels restart). With auto-sell on, items above your threshold are kept',
    unit: '%',
    branch: 'drops',
  },
  vaultCap: {
    id: 'vaultCap',
    field: 'prestigeVaultSlots',
    delta: 1,
    maxLevel: 10,
    title: 'Vault Capacity',
    description: '+1 max staking slot in the vault',
    unit: '',
    prefix: '+',
    branch: 'vault',
  },
  offlineYield: {
    id: 'offlineYield',
    field: 'prestigeOfflineYield',
    delta: 10,
    maxLevel: 100,
    title: 'Offline Yield',
    description: 'Increased dividends for staked items while offline',
    unit: '%',
    branch: 'vault',
  },
  onlineYield: {
    id: 'onlineYield',
    field: 'prestigeOnlineYield',
    delta: 10,
    maxLevel: 100,
    title: 'Online Yield',
    description: 'Increased dividends for staked items while online',
    unit: '%',
    branch: 'vault',
  },
};

/** Cost in Pr for the next purchase of a skill (level = current purchases). */
export function skillUpgradeCost(skillId, level = 0) {
  const skill = PRESTIGE_SKILLS[skillId];
  const lv = Math.max(0, Number(level) || 0);
  const mult = skill?.costMult || 1;
  return Math.round((1000 + lv * 500) * mult);
}

/** Total Pr cost to buy `count` levels starting at `level`. */
export function skillUpgradeCostBulk(skillId, level = 0, count = 1) {
  const n = Math.max(1, Math.min(100, Math.floor(Number(count) || 1)));
  const start = Math.max(0, Number(level) || 0);
  let total = 0;
  for (let i = 0; i < n; i += 1) {
    total += skillUpgradeCost(skillId, start + i);
  }
  return total;
}

export function skillMaxLevel(skillId) {
  const skill = PRESTIGE_SKILLS[skillId];
  const max = Number(skill?.maxLevel);
  return Number.isFinite(max) && max > 0 ? Math.floor(max) : Infinity;
}

/** How many levels can still be bought before the cap. */
export function skillLevelsRemaining(user, skillId) {
  const max = skillMaxLevel(skillId);
  if (!Number.isFinite(max)) return Infinity;
  return Math.max(0, max - skillLevelFromUser(user, skillId));
}

/** True when every prestige skill is at its max level. */
export function isPrestigeSkillTreeMaxed(user) {
  return Object.keys(PRESTIGE_SKILLS).every(
    (id) => skillLevelFromUser(user, id) >= skillMaxLevel(id)
  );
}

export function skillLevelFromUser(user, skillId) {
  const skill = PRESTIGE_SKILLS[skillId];
  if (!skill) return 0;
  const value = Number(user?.[skill.field] ?? 0);
  const delta = Math.max(1, Number(skill.delta) || 1);
  return Math.floor(value / delta);
}

/** Parse Pr amounts (stored as decimal strings; may exceed i64). */
export function toPrBigInt(value) {
  return toCentsBigInt(value);
}

export function prJson(value) {
  return toPrBigInt(value).toString();
}

/** Apply PR Farmer % to a base Pr reward (floor). Returns BigInt. */
export function applyPrBonus(basePr, prBonusPct = 0) {
  const base = toPrBigInt(basePr);
  const pct = Math.max(0, Math.floor(Number(prBonusPct) || 0));
  if (base <= 0n) return 0n;
  if (pct <= 0) return base;
  return (base * BigInt(100 + pct)) / 100n;
}

/**
 * Expected Pr (no RNG) for UI preview. Returns string.
 */
export function estimatePrReward(balanceCents, prBonusPct = 0) {
  const balance = toCentsBigInt(balanceCents);
  const cost = PRESTIGE_COST_CENTS;
  if (balance < cost) return '0';
  const excess = balance - cost;
  const chunk = PR_EXCESS_CHUNK_CENTS;
  const full = excess / chunk;
  const rem = excess % chunk;
  // Round partial chunk: rem/chunk >= 0.5 → +1 chunk of Pr
  const half = chunk / 2n;
  const roundedChunks = full + (rem >= half ? 1n : 0n);
  const base = BigInt(PR_BASE_REWARD) + roundedChunks * BigInt(PR_PER_EXCESS_CHUNK);
  return prJson(applyPrBonus(base, prBonusPct));
}

/**
 * Pr reward: 1000 base + 1000 per full 250B cr excess, plus a fractional
 * roll so partial chunks average to the remaining Pr.
 * PR Farmer adds +N% to the final grant.
 * Returns string (may exceed Number.MAX_SAFE_INTEGER).
 */
export function computePrReward(balanceCents, prBonusPct = 0) {
  const balance = toCentsBigInt(balanceCents);
  const cost = PRESTIGE_COST_CENTS;
  const excess = balance > cost ? balance - cost : 0n;
  const chunk = PR_EXCESS_CHUNK_CENTS;
  const full = excess / chunk;
  const rem = excess % chunk;
  // Math.random() < rem/chunk without Number overflow
  let extra = 0n;
  if (rem > 0n) {
    const scale = 1_000_000_000n;
    const roll = BigInt(Math.floor(Math.random() * 1e9));
    if (roll * chunk < rem * scale) extra = BigInt(PR_PER_EXCESS_CHUNK);
  }
  const base = BigInt(PR_BASE_REWARD) + full * BigInt(PR_PER_EXCESS_CHUNK) + extra;
  return prJson(applyPrBonus(base, prBonusPct));
}

export function adCooldownMs(_user) {
  return BASE_AD_COOLDOWN_MS;
}

export function serializePrestige(user) {
  return {
    count: user?.prestigeCount ?? 0,
    chanceBonus: user?.prestigeChanceBonus ?? 0,
    gainBonus: user?.prestigeGainBonus ?? 0,
    wearBonus: user?.prestigeWearBonus ?? 0,
    openBonus: user?.prestigeOpenBonus ?? 0,
    prBonus: user?.prestigePrBonus ?? 0,
    qualityBonus: user?.prestigeQualityBonus ?? 0,
    animReduction: user?.prestigeAnimReduction ?? 0,
    refundChance: user?.prestigeRefundChance ?? 0,
    respinChance: user?.prestigeRespinChance ?? 0,
    vaultSlots: user?.prestigeVaultSlots ?? 0,
    offlineYield: user?.prestigeOfflineYield ?? 0,
    onlineYield: user?.prestigeOnlineYield ?? 0,
    prBalance: prJson(user?.prBalance ?? 0),
    costCents: Number(PRESTIGE_COST_CENTS),
    visibleAtCents: PRESTIGE_VISIBLE_AT_CENTS,
    prBaseReward: PR_BASE_REWARD,
    prPerExcessChunk: PR_PER_EXCESS_CHUNK,
    excessChunkCents: Number(PR_EXCESS_CHUNK_CENTS),
  };
}

export function serializeSkillTree(user) {
  const pr = toPrBigInt(user?.prBalance ?? 0);
  return Object.values(PRESTIGE_SKILLS).map((skill) => {
    const level = skillLevelFromUser(user, skill.id);
    const maxLevel = skillMaxLevel(skill.id);
    const room = Math.max(0, maxLevel - level);
    const atMax = room <= 0;
    const current = Number(user?.[skill.field] ?? 0);
    const buyCount = atMax ? 0 : 1;
    const buyCount10 = atMax ? 0 : Math.min(10, room);
    const cost = buyCount ? skillUpgradeCost(skill.id, level) : 0;
    const cost10 = buyCount10 ? skillUpgradeCostBulk(skill.id, level, buyCount10) : 0;
    return {
      id: skill.id,
      title: skill.title,
      description: skill.description,
      delta: skill.delta,
      unit: skill.unit,
      prefix: skill.prefix || (skill.unit === '%' || skill.unit === 'cr-cents' ? '+' : ''),
      branch: skill.branch || 'misc',
      level,
      maxLevel: Number.isFinite(maxLevel) ? maxLevel : null,
      atMax,
      room,
      buyCount10,
      current,
      nextValue: atMax ? current : current + skill.delta,
      nextValue10: atMax ? current : current + skill.delta * buyCount10,
      cost,
      cost10,
      canAfford: !atMax && pr >= BigInt(cost),
      canAfford10: buyCount10 >= 2 && pr >= BigInt(cost10),
    };
  });
}

/** Apply lifetime gain % to a credit amount (cents). Returns BigInt. */
export function applyGainMultiplier(cents, gainBonusPct = 0) {
  return applyPercentFloor(cents, gainBonusPct);
}

export function totalLuckPercent(catalogLuck = 0, prestigeChanceBonus = 0) {
  return (Number(catalogLuck) || 0) + (Number(prestigeChanceBonus) || 0);
}

export function isValidSkill(skillId) {
  return Boolean(PRESTIGE_SKILLS[skillId]);
}

export async function getUnlockedInventoryValueCents(db, userId) {
  const rows = await db.inventoryItem.findMany({
    where: { userId, locked: false },
    select: { valueCents: true },
  });
  return rows.reduce((sum, r) => sum + toCentsBigInt(r.valueCents), 0n);
}

/** Visible if already prestiged, or total wealth reaches the unlock threshold. */
export function isPrestigePageVisible(user, totalCents) {
  if ((user?.prestigeCount ?? 0) > 0) return true;
  if (toPrBigInt(user?.prBalance) > 0n) return true;
  return toCentsBigInt(totalCents) >= PRESTIGE_VISIBLE_AT_CENTS;
}
