'use strict';

import type { Dollars } from '../utils/money.mts';

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

// Dynamic debt/bill rows — columns read individually.
type Row = Record<string, any>;

interface ScheduleRow {
  month: number;
  payment: number;
  principal: number;
  interest: number;
  balance: number;
}
interface ActiveDebt {
  id: any;
  name: any;
  balance: number;
  minPayment: number;
  monthlyRate: number;
  payoffMonth: number | null;
  totalInterest: number;
}
interface SkippedDebt {
  id: any;
  name: any;
  reason: string;
}
interface DebtProjection {
  months_to_freedom: number | null;
  total_interest_paid: number;
  payoff_date: string | null;
  payoff_display: string | null;
  debts: any[];
  skipped: SkippedDebt[];
  extra_payment: number;
  capped: boolean;
}

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

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

/**
 * One month of interest accrued on a balance at an annual rate.
 */
function monthlyInterest(balance: number, annualRatePct: number): number {
  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: number,
  annualRatePct: number,
  monthlyPayment: number,
): number | null {
  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 }
 */
function amortizationSchedule(
  balance: number,
  annualRatePct: number,
  monthlyPayment: number,
  maxMonths = 360,
): ScheduleRow[] {
  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: ScheduleRow[] = [];
  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: Row[], startDate: Date = new Date()): DebtProjection {
  const active: ActiveDebt[] = [];
  const skipped: SkippedDebt[] = [];

  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: number): string {
    const d = new Date(baseYear, baseMo + m, 1);
    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
  }
  function monthDisplay(m: number): string {
    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.
 */
function debtAprSnapshot(bill: Row): {
  monthly_interest: number;
  principal_per_min_pmt: number;
  interest_pct_of_min: number | null;
  annual_interest_estimate: number;
} | null {
  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: number): Dollars {
  return roundMoney(n);
}

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