import {getFrequencyEnum} from './constants';
import {FrequencyType} from './types';

/**
 * Interface for savings calculation parameters
 */
export interface SavingsCalculationParams {
  savingsTarget: number;
  selectedFrequency: FrequencyType;
  startDate: Date;
  endDate: Date;
  annualInterestRate: number; // e.g., 10 for 10%
  isInterestDisabled?: boolean;
  currentBalance?: number; // For existing savings
}

/**
 * Interface for periodic amount calculation parameters
 */
export interface PeriodicAmountParams {
  periodicAmount: number;
  selectedFrequency: FrequencyType;
  startDate: Date;
  endDate: Date;
  annualInterestRate: number;
  isInterestDisabled?: boolean;
  currentBalance?: number;
}

/**
 * Get the number of days for each frequency type
 */
const getFrequencyDays = (frequency: FrequencyType): number => {
  const frequencyEnum = getFrequencyEnum(frequency);
  switch (frequencyEnum) {
    case 1:
      return 1; // Daily
    case 2:
      return 7; // Weekly
    case 3:
      return 30; // Monthly
    case 4:
      return 1; // Save as you collect (treat as daily for calculation)
    default:
      return 1;
  }
};

/**
 * Calculate the total savings period in days
 */
const getSavingsPeriodDays = (startDate: Date, endDate: Date): number => {
  const timeDiff = endDate.getTime() - startDate.getTime();
  return Math.ceil(timeDiff / (1000 * 3600 * 24));
};

/**
 * Calculate final balance with compound interest
 * Interest accrues daily but compounds monthly (on the 1st of each month)
 */
export const calculateFinalBalanceWithCompoundInterest = (
  params: PeriodicAmountParams,
): number => {
  const {
    periodicAmount,
    selectedFrequency,
    startDate,
    endDate,
    annualInterestRate,
    isInterestDisabled = false,
    currentBalance = 0,
  } = params;

  if (!periodicAmount || !selectedFrequency || !startDate || !endDate)
    return currentBalance;

  const annualRate = annualInterestRate / 100;
  const dailyRate = annualRate / 365;
  const frequencyDays = getFrequencyDays(selectedFrequency);

  let balance = currentBalance;
  let accruedInterest = 0;

  const currentDate = new Date(startDate);
  const finalDate = new Date(endDate);

  // Track when to add periodic deposits
  let nextDepositDate = new Date(startDate);

  while (currentDate <= finalDate) {
    // Add periodic deposit if it's time
    if (currentDate >= nextDepositDate) {
      balance += periodicAmount;
      // Calculate next deposit date
      nextDepositDate = new Date(nextDepositDate);
      nextDepositDate.setDate(nextDepositDate.getDate() + frequencyDays);
    }

    // Accrue daily interest on current balance (only if interest is enabled)
    if (balance > 0 && !isInterestDisabled) {
      accruedInterest += balance * dailyRate;
    }

    // Compound interest monthly (add to balance on 1st of each month)
    if (currentDate.getDate() === 1 && accruedInterest > 0) {
      balance += accruedInterest;
      accruedInterest = 0;
    }

    // Move to next day
    currentDate.setDate(currentDate.getDate() + 1);
  }

  // Add any remaining accrued interest at the end
  balance += accruedInterest;

  return balance;
};

/**
 * Calculate total interest earned over the savings period
 */
export const calculateProjectedInterestEarnings = (
  params: PeriodicAmountParams,
): number => {
  const {
    periodicAmount,
    selectedFrequency,
    startDate,
    endDate,
    annualInterestRate,
    isInterestDisabled = false,
    currentBalance = 0,
  } = params;

  if (
    !periodicAmount ||
    !annualInterestRate ||
    isInterestDisabled ||
    !startDate ||
    !endDate
  )
    return 0;

  const annualRate = annualInterestRate / 100;
  const dailyRate = annualRate / 365;
  const frequencyDays = getFrequencyDays(selectedFrequency);

  let balance = currentBalance;
  let accruedInterest = 0;
  let totalInterestEarned = 0;

  const currentDate = new Date(startDate);
  const finalDate = new Date(endDate);

  // Track when to add periodic deposits
  let nextDepositDate = new Date(startDate);

  while (currentDate <= finalDate) {
    // Add periodic deposit if it's time
    if (currentDate >= nextDepositDate) {
      balance += periodicAmount;
      // Calculate next deposit date
      nextDepositDate = new Date(nextDepositDate);
      nextDepositDate.setDate(nextDepositDate.getDate() + frequencyDays);
    }

    // Accrue daily interest on current balance
    if (balance > 0) {
      accruedInterest += balance * dailyRate;
    }

    // Compound interest monthly (add to balance on 1st of each month)
    if (currentDate.getDate() === 1 && accruedInterest > 0) {
      balance += accruedInterest;
      totalInterestEarned += accruedInterest;
      accruedInterest = 0;
    }

    // Move to next day
    currentDate.setDate(currentDate.getDate() + 1);
  }

  // Add any remaining accrued interest at the end
  totalInterestEarned += accruedInterest;

  return totalInterestEarned;
};

/**
 * Calculate the required periodic amount to reach a savings target, considering compound interest
 * Uses binary search to find the optimal periodic amount
 */
export const calculateRequiredPeriodicAmountWithInterest = (
  params: SavingsCalculationParams,
): number => {
  const {
    savingsTarget,
    selectedFrequency,
    startDate,
    endDate,
    annualInterestRate,
    isInterestDisabled = false,
    currentBalance = 0,
  } = params;

  if (!savingsTarget || !selectedFrequency || !startDate || !endDate) return 0;

  const periodDays = getSavingsPeriodDays(startDate, endDate);
  const frequencyDays = getFrequencyDays(selectedFrequency);
  const numberOfPeriods = Math.floor(periodDays / frequencyDays);

  if (numberOfPeriods <= 0) return 0;

  // If interest is disabled, use simple calculation
  if (isInterestDisabled || annualInterestRate === 0) {
    const remainingTarget = Math.max(0, savingsTarget - currentBalance);
    return Math.ceil(remainingTarget / numberOfPeriods);
  }

  // Account for current balance - we need less savings target
  const remainingTarget = Math.max(0, savingsTarget - currentBalance);

  if (remainingTarget <= 0) return 0;

  // Binary search to find the optimal periodic amount
  let low = 0;
  let high = remainingTarget; // Start with a reasonable upper bound
  let bestAmount = 0;
  const tolerance = 1; // Allow 1 naira tolerance

  // If interest will likely contribute significantly, reduce the upper bound
  if (annualInterestRate > 0) {
    // Rough estimate: reduce upper bound by potential interest contribution
    const estimatedInterestContribution =
      (remainingTarget * (annualInterestRate / 100) * (periodDays / 365)) / 2;
    high = Math.max(100, remainingTarget - estimatedInterestContribution);
  }

  let iterations = 0;
  const maxIterations = 50; // Prevent infinite loops

  while (low <= high && iterations < maxIterations) {
    iterations++;
    const mid = Math.floor((low + high) / 2);

    // Calculate what the final balance would be with this periodic amount
    const finalBalance = calculateFinalBalanceWithCompoundInterest({
      periodicAmount: mid,
      selectedFrequency,
      startDate,
      endDate,
      annualInterestRate,
      isInterestDisabled,
      currentBalance,
    });

    const difference = finalBalance - savingsTarget;

    // If we're within tolerance, we found a good amount
    if (Math.abs(difference) <= tolerance) {
      bestAmount = mid;
      break;
    }

    if (finalBalance < savingsTarget) {
      // Need to save more
      low = mid + 1;
      bestAmount = mid + 1; // Keep track of the last insufficient amount
    } else {
      // We're saving too much, can reduce
      high = mid - 1;
      bestAmount = mid; // This amount reaches the target
    }
  }

  // Ensure we don't return 0 if we couldn't find exact match
  if (bestAmount === 0 && remainingTarget > 0) {
    // Fallback to simple calculation with a small buffer for interest
    const simpleAmount = Math.ceil(remainingTarget / numberOfPeriods);
    const interestBuffer = isInterestDisabled
      ? 0
      : Math.max(1, simpleAmount * 0.05); // 5% buffer
    bestAmount = Math.max(1, simpleAmount - interestBuffer);
  }

  return Math.max(1, Math.ceil(bestAmount)); // Always return at least 1 naira
};

/**
 * Simple calculation without interest (for backward compatibility)
 */
export const calculateRequiredPeriodicAmountSimple = (
  params: Omit<
    SavingsCalculationParams,
    'annualInterestRate' | 'isInterestDisabled'
  >,
): number => {
  const {
    savingsTarget,
    selectedFrequency,
    startDate,
    endDate,
    currentBalance = 0,
  } = params;

  if (!savingsTarget || !selectedFrequency || !startDate || !endDate) return 0;

  const periodDays = getSavingsPeriodDays(startDate, endDate);
  const frequencyDays = getFrequencyDays(selectedFrequency);
  const numberOfPeriods = Math.floor(periodDays / frequencyDays);

  if (numberOfPeriods <= 0) return 0;

  const remainingTarget = Math.max(0, savingsTarget - currentBalance);
  return Math.ceil(remainingTarget / numberOfPeriods);
};
