import { afterEach, describe, expect, it, vi } from 'vitest';
import { localToday, localTzOffset, shiftDay } from './localDate';

describe('localDate', () => {
  afterEach(() => {
    vi.useRealTimers();
  });

  describe('localToday', () => {
    it('formats the current local date as YYYY-MM-DD', () => {
      vi.useFakeTimers();
      // 2026-03-15 10:30 local
      vi.setSystemTime(new Date(2026, 2, 15, 10, 30, 0));
      expect(localToday()).toBe('2026-03-15');
    });

    it('zero-pads single-digit month and day', () => {
      vi.useFakeTimers();
      vi.setSystemTime(new Date(2026, 0, 5, 0, 0, 0)); // Jan 5
      expect(localToday()).toBe('2026-01-05');
    });

    it('uses local calendar fields, not UTC slicing', () => {
      vi.useFakeTimers();
      // Just after local midnight — getDate() returns the local day.
      vi.setSystemTime(new Date(2026, 11, 31, 0, 0, 1)); // Dec 31
      expect(localToday()).toBe('2026-12-31');
    });

    it('matches the local calendar fields of the mocked clock', () => {
      vi.useFakeTimers();
      const now = new Date(2026, 6, 4, 23, 59, 59); // Jul 4
      vi.setSystemTime(now);
      const mm = String(now.getMonth() + 1).padStart(2, '0');
      const dd = String(now.getDate()).padStart(2, '0');
      expect(localToday()).toBe(`${now.getFullYear()}-${mm}-${dd}`);
    });
  });

  describe('localTzOffset', () => {
    it('is the negation of Date.getTimezoneOffset (minutes east of UTC)', () => {
      const expected = -new Date().getTimezoneOffset();
      expect(localTzOffset()).toBe(expected);
    });

    it('returns an integer number of minutes', () => {
      const off = localTzOffset();
      expect(Number.isInteger(off)).toBe(true);
    });
  });

  describe('shiftDay', () => {
    it('returns the same day for delta 0', () => {
      expect(shiftDay('2026-06-15', 0)).toBe('2026-06-15');
    });

    it('advances by positive delta', () => {
      expect(shiftDay('2026-06-15', 1)).toBe('2026-06-16');
      expect(shiftDay('2026-06-15', 10)).toBe('2026-06-25');
    });

    it('goes back by negative delta', () => {
      expect(shiftDay('2026-06-15', -1)).toBe('2026-06-14');
      expect(shiftDay('2026-06-15', -15)).toBe('2026-05-31');
    });

    it('crosses month boundaries forward', () => {
      expect(shiftDay('2026-01-31', 1)).toBe('2026-02-01');
    });

    it('crosses month boundaries backward', () => {
      expect(shiftDay('2026-03-01', -1)).toBe('2026-02-28');
    });

    it('crosses year boundaries forward', () => {
      expect(shiftDay('2026-12-31', 1)).toBe('2027-01-01');
    });

    it('crosses year boundaries backward', () => {
      expect(shiftDay('2026-01-01', -1)).toBe('2025-12-31');
    });

    it('handles leap-year Feb 29 (2024 is a leap year)', () => {
      expect(shiftDay('2024-02-28', 1)).toBe('2024-02-29');
      expect(shiftDay('2024-02-29', 1)).toBe('2024-03-01');
    });

    it('skips Feb 29 in a non-leap year', () => {
      expect(shiftDay('2026-02-28', 1)).toBe('2026-03-01');
    });

    it('spans a large delta correctly (365 days, non-leap year)', () => {
      // 2026 is not a leap year, so +365 lands on the same calendar date next year.
      expect(shiftDay('2026-06-15', 365)).toBe('2027-06-15');
    });

    it('computes in the UTC frame regardless of the host timezone', () => {
      // Uses UTC midnight + setUTCDate, so the result is timezone-independent.
      expect(shiftDay('2026-06-15', 1)).toBe('2026-06-16');
    });

    it('is symmetric: shifting forward then back returns the original', () => {
      const start = '2026-08-20';
      expect(shiftDay(shiftDay(start, 7), -7)).toBe(start);
    });
  });
});
