/**
 * Cancellation Months Utility
 *
 * Generates available cancellation months following the "25th rule":
 * - If current day is before or on the 25th, current month is available
 * - If current day is after the 25th, only next month onwards is available
 *
 * Used by both SIM and Internet cancellation flows.
 */

/**
 * Base cancellation month (value + label)
 */
export interface BaseCancellationMonth {
  /** Month in YYYY-MM format */
  value: string;
  /** Human-readable label (e.g., "January 2025") */
  label: string;
}

/**
 * Extended cancellation month with runDate (for SIM/Freebit API)
 */
export interface CancellationMonthWithRunDate extends BaseCancellationMonth {
  /** Freebit API run date in YYYYMMDD format (1st of next month) */
  runDate: string;
}

/**
 * Options for generating cancellation months
 */
export interface GenerateCancellationMonthsOptions {
  /** Cutoff day of month (default: 25) */
  cutoffDay?: number;
  /** Number of months to generate (default: 12) */
  monthCount?: number;
  /** Whether to include runDate for Freebit API (default: false) */
  includeRunDate?: boolean;
  /** Override "today" for testing */
  referenceDate?: Date;
}

/**
 * Generate available cancellation months following the 25th rule.
 *
 * The 25th rule:
 * - If submitting before or on the 25th, current month is available for cancellation
 * - If submitting after the 25th, only next month onwards is available
 *
 * @example
 * // Basic usage (Internet cancellation)
 * const months = generateCancellationMonths();
 * // Returns: [{ value: "2025-01", label: "January 2025" }, ...]
 *
 * @example
 * // With runDate for SIM/Freebit API
 * const months = generateCancellationMonths({ includeRunDate: true });
 * // Returns: [{ value: "2025-01", label: "January 2025", runDate: "20250201" }, ...]
 */
export function generateCancellationMonths(
  options?: GenerateCancellationMonthsOptions & { includeRunDate?: false }
): BaseCancellationMonth[];
export function generateCancellationMonths(
  options: GenerateCancellationMonthsOptions & { includeRunDate: true }
): CancellationMonthWithRunDate[];
export function generateCancellationMonths(
  options: GenerateCancellationMonthsOptions = {}
): BaseCancellationMonth[] | CancellationMonthWithRunDate[] {
  const {
    cutoffDay = 25,
    monthCount = 12,
    includeRunDate = false,
    referenceDate = new Date(),
  } = options;

  const months: (BaseCancellationMonth | CancellationMonthWithRunDate)[] = [];
  const dayOfMonth = referenceDate.getDate();

  // Start from current month if before/on cutoff, otherwise next month
  const startOffset = dayOfMonth <= cutoffDay ? 0 : 1;

  for (let i = startOffset; i < startOffset + monthCount; i++) {
    const date = new Date(referenceDate.getFullYear(), referenceDate.getMonth() + i, 1);
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const monthStr = String(month).padStart(2, "0");

    const monthEntry: BaseCancellationMonth = {
      value: `${year}-${monthStr}`,
      label: date.toLocaleDateString("en-US", { month: "long", year: "numeric" }),
    };

    if (includeRunDate) {
      // runDate is the 1st of the NEXT month (cancellation takes effect at month end)
      const nextMonth = new Date(year, month, 1);
      const runYear = nextMonth.getFullYear();
      const runMonth = String(nextMonth.getMonth() + 1).padStart(2, "0");

      (monthEntry as CancellationMonthWithRunDate).runDate = `${runYear}${runMonth}01`;
    }

    months.push(monthEntry);
  }

  return months;
}

/**
 * Calculate the last day of a month for cancellation effective date.
 *
 * @param yearMonth - Month in YYYY-MM format
 * @returns Date string in YYYY-MM-DD format (last day of month)
 *
 * @example
 * getCancellationEffectiveDate("2025-01");
 * // Returns: "2025-01-31"
 */
export function getCancellationEffectiveDate(yearMonth: string): string {
  const [year, month] = yearMonth.split("-").map(Number);
  if (!year || !month) {
    throw new Error(`Invalid year-month format: ${yearMonth}. Expected YYYY-MM`);
  }

  // month is 1-indexed, so new Date(year, month, 0) gives last day of that month
  const lastDayOfMonth = new Date(year, month, 0);

  return [
    lastDayOfMonth.getFullYear(),
    String(lastDayOfMonth.getMonth() + 1).padStart(2, "0"),
    String(lastDayOfMonth.getDate()).padStart(2, "0"),
  ].join("-");
}

/**
 * Calculate the Freebit API runDate from a cancellation month.
 *
 * @param yearMonth - Month in YYYY-MM format
 * @returns Date string in YYYYMMDD format (1st of next month)
 *
 * @example
 * getRunDateFromMonth("2025-01");
 * // Returns: "20250201"
 */
export function getRunDateFromMonth(yearMonth: string): string {
  const [year, month] = yearMonth.split("-").map(Number);
  if (!year || !month) {
    throw new Error(`Invalid year-month format: ${yearMonth}. Expected YYYY-MM`);
  }

  const nextMonth = new Date(year, month, 1);
  const runYear = nextMonth.getFullYear();
  const runMonth = String(nextMonth.getMonth() + 1).padStart(2, "0");

  return `${runYear}${runMonth}01`;
}
