import {
  gregorianToJalali,
  isJalaliLeapYear,
  isValidJalaliDate,
  jalaliDaysInMonth,
  jalaliToGregorian,
  jalaliToIsoDate,
} from './jalali';

describe('jalali calendar', () => {
  // Anchors verified against frontend/src/lib/i18n/persianCalendar.ts, the source this
  // was ported from, over every day between 1900 and 2100.
  it('converts known Jalali dates to ISO', () => {
    expect(jalaliToIsoDate(1404, 1, 1)).toBe('2025-03-21'); // Nowruz 1404
    expect(jalaliToIsoDate(1404, 7, 25)).toBe('2025-10-17');
    expect(jalaliToIsoDate(1404, 6, 31)).toBe('2025-09-22'); // 31-day first half
    expect(jalaliToIsoDate(1405, 1, 1)).toBe('2026-03-21');
  });

  it('round-trips Gregorian → Jalali → Gregorian', () => {
    for (const [gy, gm, gd] of [
      [2025, 3, 21],
      [2025, 10, 17],
      [2026, 1, 1],
      [2024, 2, 29], // Gregorian leap day
      [1999, 12, 31],
    ] as const) {
      const [jy, jm, jd] = gregorianToJalali(gy, gm, gd);
      expect(jalaliToGregorian(jy, jm, jd)).toEqual([gy, gm, gd]);
    }
  });

  describe('leap years', () => {
    it('gives Esfand 30 days in a leap year and 29 otherwise', () => {
      expect(isJalaliLeapYear(1403)).toBe(true);
      expect(jalaliDaysInMonth(1403, 12)).toBe(30);
      expect(jalaliToIsoDate(1403, 12, 30)).toBe('2025-03-20');

      expect(isJalaliLeapYear(1404)).toBe(false);
      expect(jalaliDaysInMonth(1404, 12)).toBe(29);
      expect(jalaliToIsoDate(1404, 12, 29)).toBe('2026-03-20');
    });

    it('rejects Esfand 30 in a non-leap year', () => {
      expect(isValidJalaliDate(1404, 12, 30)).toBe(false);
      expect(jalaliToIsoDate(1404, 12, 30)).toBeNull();
    });
  });

  describe('month lengths', () => {
    it('is 31 days for months 1-6 and 30 for 7-11', () => {
      for (let m = 1; m <= 6; m += 1)
        expect(jalaliDaysInMonth(1404, m)).toBe(31);
      for (let m = 7; m <= 11; m += 1)
        expect(jalaliDaysInMonth(1404, m)).toBe(30);
    });
  });

  describe('invalid input degrades to null rather than throwing', () => {
    // The resolver feeds this model-supplied values, which may be nonsense.
    it.each([
      ['month 13', 1404, 13, 1],
      ['month 0', 1404, 0, 1],
      ['day 0', 1404, 1, 0],
      ['day 32 in a 31-day month', 1404, 1, 32],
      ['day 31 in a 30-day month', 1404, 7, 31],
      ['year beyond the conversion table', 9999, 1, 1],
      ['non-integer day', 1404, 1, 1.5],
    ])('returns null for %s', (_label, jy, jm, jd) => {
      expect(jalaliToIsoDate(jy, jm, jd)).toBeNull();
    });
  });

  describe('no export throws on an unsupported year', () => {
    // The whole module is reachable from model-supplied values, so it degrades instead of
    // raising — jalaliToIsoDate's guard is not the only entry point.
    it.each([9999, -9999, 3178, Number.NaN, 1.5])('year %s', (jy) => {
      expect(() => isJalaliLeapYear(jy)).not.toThrow();
      expect(() => jalaliDaysInMonth(jy, 1)).not.toThrow();
      expect(isJalaliLeapYear(jy)).toBe(false);
      expect(jalaliDaysInMonth(jy, 1)).toBe(0);
    });

    it('returns 0 days for an impossible month', () => {
      expect(jalaliDaysInMonth(1404, 0)).toBe(0);
      expect(jalaliDaysInMonth(1404, 13)).toBe(0);
    });
  });
});
