// Recurring auto-charges — creates real transactions when a subscription /
// credit / income source is due.
//
// Idempotent: each record stores `lastChargedYM` (YYYY-MM) of its last
// auto-charge. We never charge the same record twice in the same month.
// Also double-checks against TxStore for an existing tx with matching
// recurringId for current month (belt-and-suspenders).
//
// Migration: on first run, items without `lastChargedYM` get one set
// in a way that won't retroactively create charges for past months.

function ymKey(date) {
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
}

function prevYmKey(date) {
  const d = new Date(date.getFullYear(), date.getMonth() - 1, 1);
  return ymKey(d);
}

// What lastChargedYM should we set on a brand-new record so that:
//   * If today's date >= payment day in current month → it's considered
//     "already happened" (the user explicitly didn't enter it) and we skip;
//   * If today's date < payment day → we leave it as "previous month" so the
//     current month's payment will fire when its day comes.
function initialLastCharged(day, today) {
  if (day > today.getDate()) return prevYmKey(today);
  return ymKey(today);
}

// Has there been an auto-charge tx for this recurring id in this YM?
function hasChargeForMonth(txStoreList, recurringId, ym) {
  return txStoreList.some(t =>
    t.recurringId === recurringId && (t.date || '').slice(0, 7) === ym
  );
}

// Backfill lastChargedYM on records that don't have it (first-run migration
// or imported state from older versions).
function backfillLastCharged(today) {
  let changed = false;
  for (const s of DATA.subs || []) {
    if (s.lastChargedYM == null) { s.lastChargedYM = initialLastCharged(s.day, today); changed = true; }
  }
  for (const c of DATA.credits || []) {
    if (c.lastChargedYM == null) {
      const day = (typeof creditPaymentDayForMonth === 'function')
        ? creditPaymentDayForMonth(c, today.getFullYear(), today.getMonth() + 1)
        : (parseInt(c.nextDate, 10) || 1);
      c.lastChargedYM = initialLastCharged(day, today);
      changed = true;
    }
  }
  for (const s of DATA.incomeSources || []) {
    if (s.lastChargedYM == null) { s.lastChargedYM = initialLastCharged(s.day, today); changed = true; }
  }
  if (changed) {
    persistSubs(); persistCredits(); persistIncomeSources();
  }
}

// Main entry. Returns a list of created tx objects so caller can show toasts /
// notifications.
async function runAutoCharges({ today, pushNotif } = {}) {
  if (!window.TxStore || !TxStore._cache) return [];

  const now = today || new Date();
  const ym = ymKey(now);
  const todayDay = now.getDate();

  backfillLastCharged(now);

  const txs = TxStore.list();
  const created = [];

  // ── Subscriptions ──
  for (const s of DATA.subs || []) {
    if (s.active === false) continue;
    if (s.lastChargedYM === ym) continue;
    if (s.day > todayDay) continue;             // not yet time this month
    if (hasChargeForMonth(txs, s.id, ym)) {
      s.lastChargedYM = ym; continue;            // user added it manually
    }
    if (!s.accountId) continue;                  // no account → can't charge
    // Use day in this month (or last day if 31/30/29 doesn't exist).
    const day = Math.min(s.day, new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate());
    const date = new Date(now.getFullYear(), now.getMonth(), day, 9, 0, 0).toISOString();
    const tx = await TxStore.add({
      type: 'expense',
      amount: s.amount,
      categoryId: 'subs',
      accountId: s.accountId,
      date,
      note: s.title,
      source: 'recurring',
      recurringId: s.id,
    });
    s.lastChargedYM = ym;
    created.push({ kind: 'sub', record: s, tx });
    if (pushNotif) pushNotif({
      icon: 'music',
      title: `Списано: ${s.title}`,
      sub: `${fmtR(s.amount)} ₽ · авто-подписка`,
    });
  }

  // ── Credits ──
  for (const c of DATA.credits || []) {
    if (c.lastChargedYM === ym) continue;
    const day = (typeof creditPaymentDayForMonth === 'function')
      ? creditPaymentDayForMonth(c, now.getFullYear(), now.getMonth() + 1)
      : (parseInt(c.nextDate, 10) || 1);
    if (day > todayDay) continue;
    if (hasChargeForMonth(txs, c.id, ym)) { c.lastChargedYM = ym; continue; }
    if (!c.accountId) continue;
    const amount = (typeof creditPaymentForMonth === 'function')
      ? creditPaymentForMonth(c, now.getFullYear(), now.getMonth() + 1)
      : c.monthly;
    if (!amount || amount <= 0) continue;
    const useDay = Math.min(day, new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate());
    const date = new Date(now.getFullYear(), now.getMonth(), useDay, 9, 0, 0).toISOString();
    const tx = await TxStore.add({
      type: 'expense',
      amount,
      categoryId: 'other',
      accountId: c.accountId,
      date,
      note: `Кредит: ${c.title}`,
      source: 'recurring',
      recurringId: c.id,
    });
    // Bump paid principal — rough split: amount minus monthly interest.
    if (c.rate && c.rate > 0 && c.total > c.paid) {
      const remaining = c.total - c.paid;
      const monthInterest = Math.round(remaining * (c.rate / 100 / 12));
      const principal = Math.max(0, amount - monthInterest);
      c.paid = Math.min(c.total, c.paid + principal);
    } else {
      c.paid = Math.min(c.total, (c.paid || 0) + amount);
    }
    c.lastChargedYM = ym;
    created.push({ kind: 'credit', record: c, tx });
    if (pushNotif) pushNotif({
      icon: 'bank',
      title: `Списано по кредиту: ${c.title}`,
      sub: `${fmtR(amount)} ₽`,
    });
  }

  // ── Income sources ──
  for (const s of DATA.incomeSources || []) {
    if (s.type && s.type !== 'recurring') continue;
    if (s.lastChargedYM === ym) continue;
    if (s.day > todayDay) continue;
    if (hasChargeForMonth(txs, s.id, ym)) { s.lastChargedYM = ym; continue; }

    const day = Math.min(s.day, new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate());
    const date = new Date(now.getFullYear(), now.getMonth(), day, 9, 0, 0).toISOString();

    // Auto-distribute if rules are set, otherwise put it on `accountId`.
    if (Array.isArray(s.distributionRules) && s.distributionRules.length) {
      for (const rule of s.distributionRules) {
        const part = Math.round(s.amount * (rule.percent || 0) / 100);
        if (part <= 0 || !rule.accountId) continue;
        const tx = await TxStore.add({
          type: 'income',
          amount: part,
          categoryId: s.id,
          accountId: rule.accountId,
          date,
          note: `${s.title} (${rule.percent}%)`,
          source: 'recurring',
          recurringId: s.id,
        });
        created.push({ kind: 'income', record: s, tx });
      }
    } else if (s.accountId) {
      const tx = await TxStore.add({
        type: 'income',
        amount: s.amount,
        categoryId: s.id,
        accountId: s.accountId,
        date,
        note: s.title,
        source: 'recurring',
        recurringId: s.id,
      });
      created.push({ kind: 'income', record: s, tx });
    }
    s.lastChargedYM = ym;
    if (pushNotif) pushNotif({
      icon: 'arrowDownRight',
      title: `Начислено: ${s.title}`,
      sub: `+${fmtR(s.amount)} ₽`,
    });
  }

  if (created.length) {
    persistSubs(); persistCredits(); persistIncomeSources();
    if (typeof recomputeUpcoming === 'function') recomputeUpcoming();
  }
  return created;
}

Object.assign(window, { runAutoCharges, ymKey, prevYmKey, initialLastCharged, backfillLastCharged });
