
const { roundMoney, sumMoney } = require('../utils/money.mts');
/**
 * APR / amortization mathematics.
 * All functions are pure — no DB access, no side effects.
 */

const MAX_MONTHS = 600; // 50-year simulation cap

// ── Primitives ────────────────────────────────────────────────────────────────

/**
 * One month of interest accrued on a balance at an annual rate.
 */
function monthlyInterest(balance, annualRatePct) {
  return Math.max(0, Number(balance) || 0) * (Math.max(0, Number(annualRatePct) || 0) / 100 / 12);
}

/**
 * Months to pay off a single balance with a fixed monthly payment.
 * Returns null if the payment never covers the interest (debt grows forever)
 * or if the balance/payment are invalid.
 */
function monthsToPayoff(balance, annualRatePct, monthlyPayment) {
  const rate = Math.max(0, Number(annualRatePct) || 0) / 100 / 12;
  let bal    = Number(balance);
  const pmt  = Number(monthlyPayment);

  if (!Number.isFinite(bal) || bal <= 0) return 0;
  if (!Number.isFinite(pmt) || pmt <= 0) return null;
  if (rate > 0 && pmt <= bal * rate)     return null; // payment can't overcome interest

  let months = 0;
  while (bal > 0.005 && months < MAX_MONTHS) {
    months++;
    bal += bal * rate;
    bal = Math.max(0, bal - pmt);
  }
  return months >= MAX_MONTHS ? null : months;
}

/**
 * Full month-by-month amortization schedule for a single debt.
 * Each row: { month, payment, principal, interest, balance }
 *
 * @param {number} balance
 * @param {number} annualRatePct
 * @param {number} monthlyPayment
 * @param {number} [maxMonths=360]  Hard cap (prevents huge payloads)
 */
function amortizationSchedule(balance, annualRatePct, monthlyPayment, maxMonths = 360) {
  const rate = Math.max(0, Number(annualRatePct) || 0) / 100 / 12;
  let bal    = Number(balance);
  const pmt  = Number(monthlyPayment);
  const cap  = Math.min(maxMonths, MAX_MONTHS);

  if (!Number.isFinite(bal) || bal <= 0 || !Number.isFinite(pmt) || pmt <= 0) return [];
  if (rate > 0 && pmt <= bal * rate) return [];

  const schedule = [];
  let month = 0;

  while (bal > 0.005 && month < cap) {
    month++;
    const interest  = round2(bal * rate);
    const rawPmt    = Math.min(bal + interest, pmt);
    const payment   = round2(rawPmt);
    const principal = round2(Math.max(0, payment - interest));
    bal             = round2(Math.max(0, bal - principal));

    schedule.push({ month, payment, principal, interest, balance: bal });
  }
  return schedule;
}

// ── Minimum-only projection ───────────────────────────────────────────────────

/**
 * Projects payoff with ONLY minimum payments — no snowball rolling, no extra money.
 * Each debt runs independently at its minimum payment.
 * This is the "do nothing extra" baseline for comparing against the snowball method.
 *
 * Returns the same shape as calculateSnowball so callers can compare directly.
 */
function calculateMinimumOnly(debts, startDate = new Date()) {
  const active  = [];
  const skipped = [];

  for (const d of debts) {
    const bal  = Number(d.current_balance);
    const rate = Math.max(0, Number(d.interest_rate) || 0) / 100 / 12;
    const min  = Math.max(0, Number(d.minimum_payment) || 0);

    if (d.current_balance == null || !Number.isFinite(bal) || bal <= 0) {
      skipped.push({ id: d.id, name: d.name, reason: 'no_balance' });
      continue;
    }
    // Minimum payment too low to ever pay off — flag it
    if (rate > 0 && min > 0 && min <= bal * rate) {
      skipped.push({ id: d.id, name: d.name, reason: 'payment_below_interest' });
      continue;
    }
    if (min <= 0) {
      skipped.push({ id: d.id, name: d.name, reason: 'no_minimum_payment' });
      continue;
    }

    active.push({
      id:            d.id,
      name:          d.name,
      balance:       bal,
      minPayment:    min,
      monthlyRate:   rate,
      payoffMonth:   null,
      totalInterest: 0,
    });
  }

  if (active.length === 0) {
    return {
      months_to_freedom:   null,
      total_interest_paid: 0,
      payoff_date:         null,
      payoff_display:      null,
      debts:               [],
      skipped,
      extra_payment:       0,
      capped:              false,
    };
  }

  // Each debt is independent — no payment rolls when one clears
  let month = 0;
  while (active.some(d => d.balance > 0) && month < MAX_MONTHS) {
    month++;
    for (const d of active) {
      if (d.balance <= 0) continue;
      const interest = d.balance * d.monthlyRate;
      d.balance      += interest;
      d.totalInterest += interest;
      const payment   = Math.min(d.balance, d.minPayment);
      d.balance       = Math.max(0, d.balance - payment);
      if (d.balance < 0.005) d.balance = 0;
    }
    for (const d of active) {
      if (d.balance === 0 && d.payoffMonth === null) d.payoffMonth = month;
    }
  }

  const baseYear = startDate.getFullYear();
  const baseMo   = startDate.getMonth();

  function monthLabel(m) {
    const d = new Date(baseYear, baseMo + m, 1);
    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
  }
  function monthDisplay(m) {
    return new Date(baseYear, baseMo + m, 1)
      .toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
  }

  const debtResults = active.map(d => ({
    id:             d.id,
    name:           d.name,
    payoff_month:   d.payoffMonth,
    payoff_date:    d.payoffMonth ? monthLabel(d.payoffMonth)   : null,
    payoff_display: d.payoffMonth ? monthDisplay(d.payoffMonth) : null,
    total_interest: round2(d.totalInterest),
    months:         d.payoffMonth,
    never_paid:     d.payoffMonth === null,
  }));

  // Only report a payoff date if every active debt actually clears (see _simulate).
  const allPaid       = active.every(d => d.payoffMonth !== null);
  const maxMonth      = Math.max(0, ...active.map(d => d.payoffMonth || 0));
  const totalInterest = sumMoney(active, d => d.totalInterest);

  return {
    months_to_freedom:   allPaid && maxMonth ? maxMonth : null,
    total_interest_paid: round2(totalInterest),
    payoff_date:         allPaid && maxMonth ? monthLabel(maxMonth)   : null,
    payoff_display:      allPaid && maxMonth ? monthDisplay(maxMonth) : null,
    debts:               debtResults,
    skipped,
    extra_payment:       0,
    capped:              month >= MAX_MONTHS || !allPaid,
  };
}

// ── Per-debt APR snapshot ─────────────────────────────────────────────────────

/**
 * Computes the current APR breakdown for a single debt based on its present balance.
 * Returns the metrics needed to understand how expensive the debt is right now.
 *
 * @param {object} bill  Requires: current_balance, interest_rate, minimum_payment
 */
function debtAprSnapshot(bill) {
  const balance = Number(bill.current_balance);
  const apr     = Number(bill.interest_rate) || 0;
  const min     = Number(bill.minimum_payment) || 0;

  if (!Number.isFinite(balance) || balance <= 0) return null;

  const monthly_interest         = round2(monthlyInterest(balance, apr));
  const principal_per_min_pmt    = round2(Math.max(0, min - monthly_interest));
  const interest_pct_of_min      = min > 0 ? round2((monthly_interest / min) * 100) : null;
  const annual_interest_estimate = round2(monthly_interest * 12);

  return {
    monthly_interest,           // dollars of interest accruing this month
    principal_per_min_pmt,      // dollars of principal reduction if paying minimum
    interest_pct_of_min,        // % of minimum payment that goes to interest
    annual_interest_estimate,   // rough yearly cost at current balance
  };
}

// ── Helpers ───────────────────────────────────────────────────────────────────

function round2(n) {
  return roundMoney(n);
}

module.exports = {
  monthlyInterest,
  monthsToPayoff,
  amortizationSchedule,
  calculateMinimumOnly,
  debtAprSnapshot,
};
