import { prisma } from './db.js';
import { applyRateFloor, toCentsBigInt, minBigInt, centsJson, creditUserBalance } from './cents.js';
import { getIO } from './realtime.js';
import { publicUser } from './middleware.js';

export const BASE_VAULT_SLOTS = 3;
export const ONLINE_TICK_MS = 300_000; // 5 minutes
export const OFFLINE_TICK_MS = 600_000; // 10 minutes
/** @deprecated use ONLINE_TICK_MS / OFFLINE_TICK_MS */
export const VAULT_TICK_MS = OFFLINE_TICK_MS;
export const ONLINE_BASE_RATE = 0.1; // 10% of value per tick
export const OFFLINE_BASE_RATE = 0.01; // 1% of value per tick
export const WEAR_FLOAT_K = 3;
/** Soft cap: at most 500% of item value per tick */
export const MAX_TICK_VALUE_MULT = 5n;

/** Rarity → yield multiplier (seed rarities + fallbacks). */
export const RARITY_YIELD_MULT = {
  Consumer: 0.8,
  Industrial: 0.9,
  MilSpec: 1.0,
  Restricted: 1.15,
  Classified: 1.35,
  Covert: 1.55,
  Extraordinary: 1.8,
};

export function vaultTickMs(mode) {
  return mode === 'online' ? ONLINE_TICK_MS : OFFLINE_TICK_MS;
}

export function maxVaultSlots(user) {
  return BASE_VAULT_SLOTS + Math.max(0, Math.floor(Number(user?.prestigeVaultSlots) || 0));
}

export function wearYieldMult(floatValue) {
  const f = Math.min(1, Math.max(0, Number(floatValue) || 0));
  return 1 + f * f * WEAR_FLOAT_K;
}

export function rarityYieldMult(rarity) {
  const key = String(rarity || '');
  return RARITY_YIELD_MULT[key] ?? 1.0;
}

export function skillYieldMult(user, mode) {
  const pct =
    mode === 'online'
      ? Number(user?.prestigeOnlineYield) || 0
      : Number(user?.prestigeOfflineYield) || 0;
  if (pct <= 0) return 1;
  return 1 + pct / 100;
}

export function tickPayoutCents(inv, user, mode) {
  const value = toCentsBigInt(inv.valueCents);
  if (value <= 0n) return 0n;
  const baseRate = mode === 'online' ? ONLINE_BASE_RATE : OFFLINE_BASE_RATE;
  const wear = wearYieldMult(inv.floatValue);
  const rarity = rarityYieldMult(inv.item?.rarity ?? inv.rarity);
  const skill = skillYieldMult(user, mode);
  const combined = baseRate * wear * rarity * skill;
  const payout = applyRateFloor(value, combined);
  return minBigInt(value * MAX_TICK_VALUE_MULT, payout);
}

export function estimateTickYield(inv, user, mode) {
  return tickPayoutCents(inv, user, mode);
}

/** Shared vault timer anchor (user-level). Falls back to oldest item stake/yield. */
export function resolveVaultAnchor(user, items, now = Date.now()) {
  if (user?.vaultYieldAt) {
    return new Date(user.vaultYieldAt).getTime();
  }
  let earliest = null;
  for (const inv of items || []) {
    const raw = inv.lastYieldAt || inv.stakedAt;
    if (!raw) continue;
    const ms = new Date(raw).getTime();
    if (!Number.isFinite(ms)) continue;
    if (earliest == null || ms < earliest) earliest = ms;
  }
  return earliest ?? now;
}

export function nextYieldAtMs(user, items, mode, now = Date.now()) {
  if (!items?.length) return null;
  const tickMs = vaultTickMs(mode);
  const anchor = resolveVaultAnchor(user, items, now);
  const ticksDone = Math.floor(Math.max(0, now - anchor) / tickMs);
  return anchor + (ticksDone + 1) * tickMs;
}

/**
 * Settle complete vault ticks for a user (shared timer across all staked items).
 * @param {'online'|'offline'} mode
 * @param {Date|number} [until] settle up to this time (default now)
 * @returns {{ credited: string, ticks: number, itemIds: number[], balance?: string, vault?: object }}
 */
export async function settleVaultYield(userId, mode, until = Date.now()) {
  const id = Number(userId);
  if (!id) return { credited: '0', ticks: 0, itemIds: [] };

  const untilMs = until instanceof Date ? until.getTime() : Number(until) || Date.now();
  const tickMs = vaultTickMs(mode);

  const result = await prisma.$transaction(async (tx) => {
    const user = await tx.user.findUnique({ where: { id } });
    if (!user) return { credited: '0', ticks: 0, itemIds: [], user: null, items: [] };

    const items = await tx.inventoryItem.findMany({
      where: { userId: id, staked: true },
      include: { item: true },
    });
    if (!items.length) {
      if (user.vaultYieldAt) {
        await tx.user.update({ where: { id }, data: { vaultYieldAt: null } });
      }
      return { credited: '0', ticks: 0, itemIds: [], user, items: [] };
    }

    const anchorMs = resolveVaultAnchor(user, items, untilMs);
    const elapsed = Math.max(0, untilMs - anchorMs);
    const ticks = Math.floor(elapsed / tickMs);

    if (ticks <= 0) {
      // Ensure shared timer is persisted (migration from per-item anchors)
      if (!user.vaultYieldAt) {
        const updated = await tx.user.update({
          where: { id },
          data: { vaultYieldAt: new Date(anchorMs) },
        });
        return { credited: '0', ticks: 0, itemIds: [], user: updated, items };
      }
      return { credited: '0', ticks: 0, itemIds: [], user, items };
    }

    let totalCredit = 0n;
    const itemIds = [];
    for (const inv of items) {
      const perTick = tickPayoutCents(inv, user, mode);
      const credit = perTick * BigInt(ticks);
      if (credit > 0n) {
        totalCredit += credit;
        itemIds.push(inv.id);
      }
      await tx.inventoryItem.update({
        where: { id: inv.id },
        data: { lastYieldAt: new Date(anchorMs + ticks * tickMs) },
      });
    }

    const advanced = new Date(anchorMs + ticks * tickMs);
    let updatedUser;
    if (totalCredit > 0n) {
      await creditUserBalance(tx, id, totalCredit);
      updatedUser = await tx.user.update({
        where: { id },
        data: { vaultYieldAt: advanced },
      });
    } else if (!user.vaultYieldAt || user.vaultYieldAt.getTime() !== advanced.getTime()) {
      updatedUser = await tx.user.update({
        where: { id },
        data: { vaultYieldAt: advanced },
      });
    } else {
      updatedUser = user;
    }

    if (totalCredit > 0n) {
      await tx.transaction.create({
        data: {
          userId: id,
          type: 'vault_yield',
          amount: centsJson(totalCredit),
          meta: JSON.stringify({
            mode,
            ticks,
            inventoryItemIds: itemIds,
            tickMs,
          }),
        },
      });
    }

    return {
      credited: totalCredit.toString(),
      ticks,
      itemIds,
      user: updatedUser,
      items,
    };
  });

  const vault = result.user
    ? summarizeVaultState(result.user, result.items || [], mode, untilMs)
    : null;

  if (result.credited !== '0' && result.user) {
    emitVaultYield(id, {
      credited: result.credited,
      ticks: result.ticks,
      balance: result.user.balance?.toString?.() ?? String(result.user.balance),
      user: publicUser(result.user),
      vault,
    });
  }

  return {
    credited: result.credited,
    ticks: result.ticks,
    itemIds: result.itemIds,
    balance: result.user?.balance?.toString?.() ?? undefined,
    vault,
  };
}

function emitVaultYield(userId, payload) {
  const io = getIO();
  if (!io) return;
  io.to(`user:${userId}`).emit('vault:yield', payload);
}

/** Estimate pending yield for UI (incomplete ticks not included). */
export function estimatePendingYield(items, user, mode, now = Date.now()) {
  if (!items?.length) return '0';
  const tickMs = vaultTickMs(mode);
  const anchor = resolveVaultAnchor(user, items, now);
  const ticks = Math.floor(Math.max(0, now - anchor) / tickMs);
  if (ticks <= 0) return '0';
  let total = 0n;
  for (const inv of items) {
    total += tickPayoutCents(inv, user, mode) * BigInt(ticks);
  }
  return total.toString();
}

/** Per-tick rate summary for UI (one tick online / offline). */
export function summarizeVaultRates(items, user) {
  let onlinePerTick = 0n;
  let offlinePerTick = 0n;
  for (const inv of items) {
    onlinePerTick += tickPayoutCents(inv, user, 'online');
    offlinePerTick += tickPayoutCents(inv, user, 'offline');
  }
  return {
    onlinePerTick: onlinePerTick.toString(),
    offlinePerTick: offlinePerTick.toString(),
    tickMsOnline: ONLINE_TICK_MS,
    tickMsOffline: OFFLINE_TICK_MS,
    tickMs: ONLINE_TICK_MS,
  };
}

export function summarizeVaultState(user, items, mode, now = Date.now()) {
  const rates = summarizeVaultRates(items, user);
  const nextMs = nextYieldAtMs(user, items, mode, now);
  return {
    used: items.length,
    slots: maxVaultSlots(user),
    ...rates,
    mode,
    tickMs: vaultTickMs(mode),
    vaultYieldAt: user.vaultYieldAt ? new Date(user.vaultYieldAt).toISOString() : null,
    nextYieldAt: nextMs != null ? new Date(nextMs).toISOString() : null,
    pendingYield: estimatePendingYield(items, user, mode, now),
  };
}
