const ROUND_PRECISION = 100;

const round2 = (value) => Math.round((value + Number.EPSILON) * ROUND_PRECISION) / ROUND_PRECISION;

const addMonths = (date, months) => {
  const baseYear = date.getFullYear();
  const baseMonth = date.getMonth();
  const baseDay = date.getDate();
  const target = new Date(baseYear, baseMonth + months, 1);
  const daysInTargetMonth = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
  target.setDate(Math.min(baseDay, daysInTargetMonth));
  return target;
};

export function calculateMortgageEscrow({
  homePrice,
  propertyTaxValue,
  propertyTaxMode,
  homeInsuranceValue,
  homeInsuranceMode,
  hoaFeesMonthly = 0,
  pmiMonthly = 0,
}) {
  const validHomePrice = Number.isFinite(homePrice) && homePrice > 0 ? homePrice : 0;
  const taxMode = propertyTaxMode === 'percent' ? 'percent' : 'amount';
  const insuranceMode = homeInsuranceMode === 'percent' ? 'percent' : 'amount';

  const annualPropertyTax = taxMode === 'percent'
    ? round2(validHomePrice * ((Number(propertyTaxValue) || 0) / 100))
    : round2(Number(propertyTaxValue) || 0);

  const annualHomeInsurance = insuranceMode === 'percent'
    ? round2(validHomePrice * ((Number(homeInsuranceValue) || 0) / 100))
    : round2(Number(homeInsuranceValue) || 0);

  const monthlyTax = round2(annualPropertyTax / 12);
  const monthlyInsurance = round2(annualHomeInsurance / 12);
  const monthlyHoa = round2(Number(hoaFeesMonthly) || 0);
  const monthlyPmi = round2(Number(pmiMonthly) || 0);
  const monthlyEscrow = round2(monthlyTax + monthlyInsurance + monthlyHoa + monthlyPmi);

  return {
    annualPropertyTax,
    annualHomeInsurance,
    monthlyTax,
    monthlyInsurance,
    monthlyHoa,
    monthlyPmi,
    monthlyEscrow,
  };
}

export function calculateMortgageAmortization({
  principal,
  annualRatePercent,
  termYears,
  startDate,
  extraMonthly = 0,
  extraYearly = 0,
  extraYearlyMonth = 1,
  extraOneTime = 0,
  extraOneTimeDate = null,
  monthlyEscrow = 0,
}) {
  const loanAmount = Number(principal) || 0;
  const years = Number(termYears) || 0;

  if (!Number.isFinite(loanAmount) || loanAmount <= 0 || !Number.isFinite(years) || years <= 0) {
    return null;
  }

  const termMonths = Math.round(years * 12);
  const monthlyRate = (Number(annualRatePercent) || 0) / 100 / 12;
  const validStartDate = startDate instanceof Date && !Number.isNaN(startDate.getTime())
    ? startDate
    : new Date();

  let baseMonthlyPI;
  if (monthlyRate === 0) {
    baseMonthlyPI = loanAmount / termMonths;
  } else {
    const factor = Math.pow(1 + monthlyRate, termMonths);
    baseMonthlyPI = (loanAmount * monthlyRate * factor) / (factor - 1);
  }

  baseMonthlyPI = round2(baseMonthlyPI);
  if (!Number.isFinite(baseMonthlyPI) || baseMonthlyPI <= 0) {
    return null;
  }

  let balance = loanAmount;
  let totalInterest = 0;
  let totalPaid = 0;
  let totalPaidWithEscrow = 0;
  let months = 0;
  let firstMonth = null;
  let crossoverMonth = null;

  const points = [];
  const schedule = [];
  const maxMonths = termMonths + 120;

  for (let i = 0; i < maxMonths && balance > 0.01; i += 1) {
    const periodDate = addMonths(validStartDate, i);
    const interest = round2(balance * monthlyRate);
    let principalBase = round2(baseMonthlyPI - interest);

    if (monthlyRate === 0) {
      principalBase = round2(baseMonthlyPI);
    }

    if (principalBase < 0) {
      return null;
    }

    let extraPrincipal = 0;
    if (extraMonthly > 0) {
      extraPrincipal += Number(extraMonthly) || 0;
    }
    if (extraYearly > 0 && (periodDate.getMonth() + 1) === extraYearlyMonth) {
      extraPrincipal += Number(extraYearly) || 0;
    }
    if (
      extraOneTime > 0
      && extraOneTimeDate
      && periodDate.getFullYear() === extraOneTimeDate.getFullYear()
      && periodDate.getMonth() === extraOneTimeDate.getMonth()
    ) {
      extraPrincipal += Number(extraOneTime) || 0;
    }

    extraPrincipal = round2(extraPrincipal);
    let principalTotal = round2(principalBase + extraPrincipal);

    // Final-month guard to prevent negative balance and cliff spikes.
    if (principalTotal > balance) {
      principalTotal = balance;
    }

    const payment = round2(interest + principalTotal);
    const paymentWithEscrow = round2(payment + (Number(monthlyEscrow) || 0));

    balance = round2(balance - principalTotal);
    if (balance < 0) {
      balance = 0;
    }

    totalInterest = round2(totalInterest + interest);
    totalPaid = round2(totalPaid + payment);
    totalPaidWithEscrow = round2(totalPaidWithEscrow + paymentWithEscrow);
    months = i + 1;

    points.push({ month: months, principal: principalTotal, interest });
    schedule.push({
      month: months,
      interest,
      principal: principalTotal,
      payment,
      paymentWithEscrow,
      balance,
    });

    if (!firstMonth) {
      firstMonth = { principal: principalTotal, interest };
    }
    if (crossoverMonth === null && principalTotal >= interest) {
      crossoverMonth = months;
    }
  }

  if (balance > 0.01) {
    return null;
  }

  return {
    months,
    totalInterest,
    totalPaid,
    totalPaidWithEscrow,
    baseMonthlyPI,
    firstMonth,
    points,
    schedule,
    crossoverMonth,
  };
}
