/**
 * Pure date-math helpers for fee installment generation.
 * No NestJS, no DB — unit-testable with Jest.
 *
 * All reads/writes use UTC accessors (getUTCFullYear / getUTCMonth / Date.UTC).
 * This avoids bugs where an endDate stored as 2027-12-31T18:30:00Z — which is
 * 2028-01-01 00:00 local in IST — would otherwise parse as year 2028 when
 * read with getFullYear() on a server running in IST.
 */

export function clampDayToMonth(year: number, month1: number, day: number): number {
    const lastDayOfMonth = new Date(Date.UTC(year, month1, 0)).getUTCDate();
    return Math.min(Math.max(1, day), lastDayOfMonth);
}

/**
 * Walk month-by-month from start's (year, month) to end's (year, month), inclusive.
 * Emits one Date per month at `dueDay`, clamped to the month's last valid day.
 *
 * If the first month's dueDay lands BEFORE start's day-of-month, that month
 * is skipped — e.g. start=2026-06-15 with dueDay=10 skips Jun 10 and emits
 * Jul 10 as the first installment. This matches the intent of "bill from
 * this date forward" rather than emitting a dueDate that's already before
 * the admin's chosen start.
 */
export function generateMonthlyDueDates(start: Date, end: Date, dueDay: number): Date[] {
    const out: Date[] = [];
    let year = start.getUTCFullYear();
    let month = start.getUTCMonth() + 1;
    const startDay = start.getUTCDate();
    const endYear = end.getUTCFullYear();
    const endMonth = end.getUTCMonth() + 1;

    // Skip the first month if its dueDay would land before start's day.
    if (dueDay < startDay) {
        month++;
        if (month > 12) { month = 1; year++; }
    }

    for (let guard = 0; guard < 60; guard++) {
        // If start-month was skipped and end < new start, break early with empty
        if (year > endYear || (year === endYear && month > endMonth)) break;
        const day = clampDayToMonth(year, month, dueDay);
        out.push(new Date(Date.UTC(year, month - 1, day)));
        if (year === endYear && month === endMonth) break;
        month++;
        if (month > 12) { month = 1; year++; }
    }
    return out;
}

/**
 * Emit 4 quarterly installments at months 0, 3, 6, 9 relative to start.
 * Clamps dueDay per month.
 */
export function generateQuarterlyDueDates(start: Date, _end: Date, dueDay: number): Date[] {
    const out: Date[] = [];
    const startYear = start.getUTCFullYear();
    const startMonth = start.getUTCMonth() + 1;
    for (const offset of [0, 3, 6, 9]) {
        const rawMonth = startMonth + offset;
        const year = startYear + Math.floor((rawMonth - 1) / 12);
        const month = ((rawMonth - 1) % 12) + 1;
        const day = clampDayToMonth(year, month, dueDay);
        out.push(new Date(Date.UTC(year, month - 1, day)));
    }
    return out;
}

/**
 * Given an AY start date and a calendar month (1-12), returns the calendar
 * year that month belongs to within the AY window. Assumes AY spans ≤ 12 months.
 *
 * - Calendar AY (Jan-start): all months 1-12 → start.year
 * - April-start: months 4-12 → start.year, months 1-3 → start.year + 1
 * - June-start: months 6-12 → start.year, months 1-5 → start.year + 1
 *
 * Used by transport (and other per-month installment generators) when the
 * caller picks specific months out of an AY rather than walking the whole range.
 */
export function yearForMonthInAY(start: Date, month1: number): number {
    const startYear = start.getUTCFullYear();
    const startMonth = start.getUTCMonth() + 1;
    return month1 >= startMonth ? startYear : startYear + 1;
}

const MONTH_SHORT = [
    '', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
];

/**
 * Build quarterly labels relative to an AY's start month. E.g. for a June-start
 * AY, returns ['Q1 (Jun-Aug)', 'Q2 (Sep-Nov)', 'Q3 (Dec-Feb)', 'Q4 (Mar-May)'].
 * For a calendar AY (Jan start), returns ['Q1 (Jan-Mar)', 'Q2 (Apr-Jun)', ...].
 */
export function buildQuarterLabels(startMonth1: number): string[] {
    const wrap = (m: number) => ((m - 1) % 12) + 1;
    return [0, 3, 6, 9].map((offset, qi) => {
        const first = wrap(startMonth1 + offset);
        const last = wrap(startMonth1 + offset + 2);
        return `Q${qi + 1} (${MONTH_SHORT[first]}-${MONTH_SHORT[last]})`;
    });
}
