// src/utils/dateUtils.test.ts
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
import {
  calculateSimpleWeekAndYear,
  formatShortDate,
  calculateDaysBetween,
  formatDateRange,
  getCurrentDateISOString,
} from './dateUtils';

describe('dateUtils', () => {
  describe('calculateSimpleWeekAndYear', () => {
    beforeEach(() => {
      // Use fake timers to control the current date in tests
      vi.useFakeTimers();
    });

    afterEach(() => {
      // Restore real timers after each test
      vi.useRealTimers();
    });

    it('should return week 1 for the first day of the year', () => {
      const date = new Date('2024-01-01T12:00:00Z');
      expect(calculateSimpleWeekAndYear(date)).toEqual({ year: 2024, week: 1 });
    });

    it('should return week 1 for the 7th day of the year', () => {
      const date = new Date('2024-01-07T12:00:00Z');
      expect(calculateSimpleWeekAndYear(date)).toEqual({ year: 2024, week: 1 });
    });

    it('should return week 2 for the 8th day of the year', () => {
      const date = new Date('2024-01-08T12:00:00Z');
      expect(calculateSimpleWeekAndYear(date)).toEqual({ year: 2024, week: 2 });
    });

    it('should correctly calculate the week for a date in the middle of the year', () => {
      // July 1st is the 183rd day of a leap year. 182 / 7 = 26. So it's week 27.
      const date = new Date('2024-07-01T12:00:00Z');
      expect(calculateSimpleWeekAndYear(date)).toEqual({ year: 2024, week: 27 });
    });

    it('should correctly calculate the week for the last day of a non-leap year', () => {
      // Dec 31, 2023 is the 365th day. 364 / 7 = 52. So it's week 53.
      const date = new Date('2023-12-31T12:00:00Z');
      expect(calculateSimpleWeekAndYear(date)).toEqual({ year: 2023, week: 53 });
    });

    it('should correctly calculate the week for the last day of a leap year', () => {
      // Dec 31, 2024 is the 366th day. 365 / 7 = 52.14. floor(52.14) + 1 = 53.
      const date = new Date('2024-12-31T12:00:00Z');
      expect(calculateSimpleWeekAndYear(date)).toEqual({ year: 2024, week: 53 });
    });

    it('should use the current date if no date is provided', () => {
      const fakeCurrentDate = new Date('2025-02-10T12:00:00Z'); // 41st day of the year
      vi.setSystemTime(fakeCurrentDate);

      // 40 / 7 = 5.71. floor(5.71) + 1 = 6.
      expect(calculateSimpleWeekAndYear()).toEqual({ year: 2025, week: 6 });
    });
  });

  describe('formatShortDate', () => {
    it('should format a valid YYYY-MM-DD date string correctly', () => {
      expect(formatShortDate('2024-07-26')).toBe('Jul 26');
    });

    it('should handle single-digit days correctly', () => {
      expect(formatShortDate('2025-01-05')).toBe('Jan 5');
    });

    it('should handle dates at the end of the year', () => {
      expect(formatShortDate('2023-12-31')).toBe('Dec 31');
    });

    it('should return null for a null input', () => {
      expect(formatShortDate(null)).toBeNull();
    });

    it('should return null for an undefined input', () => {
      expect(formatShortDate(undefined)).toBeNull();
    });

    it('should return null for an empty string input', () => {
      expect(formatShortDate('')).toBeNull();
    });

    it('should return null for an invalid date string', () => {
      expect(formatShortDate('not-a-real-date')).toBeNull();
    });

    it('should return null for a malformed date string', () => {
      expect(formatShortDate('2024-13-01')).toBeNull(); // Invalid month
    });

    it('should correctly format a full ISO string with time and timezone', () => {
      expect(formatShortDate('2024-12-25T10:00:00Z')).toBe('Dec 25');
    });
  });

  describe('calculateDaysBetween', () => {
    it('should calculate the difference in days between two valid date strings', () => {
      expect(calculateDaysBetween('2023-01-01', '2023-01-05')).toBe(4);
    });

    it('should return a negative number if the end date is before the start date', () => {
      expect(calculateDaysBetween('2023-01-05', '2023-01-01')).toBe(-4);
    });

    it('should handle Date objects', () => {
      const start = new Date('2023-01-01');
      const end = new Date('2023-01-10');
      expect(calculateDaysBetween(start, end)).toBe(9);
    });

    it('should return null if either date is null or undefined', () => {
      expect(calculateDaysBetween(null, '2023-01-01')).toBeNull();
      expect(calculateDaysBetween('2023-01-01', undefined)).toBeNull();
    });

    it('should return null if either date is invalid', () => {
      expect(calculateDaysBetween('invalid', '2023-01-01')).toBeNull();
      expect(calculateDaysBetween('2023-01-01', 'invalid')).toBeNull();
    });
  });

  describe('formatDateRange', () => {
    it('should format a range with two different valid dates', () => {
      expect(formatDateRange('2023-01-01', '2023-01-05')).toBe('Jan 1 - Jan 5');
    });

    it('should format a range with the same start and end date as a single date', () => {
      expect(formatDateRange('2023-01-01', '2023-01-01')).toBe('Jan 1');
    });

    it('should return only the start date if end date is missing', () => {
      expect(formatDateRange('2023-01-01', null)).toBe('Jan 1');
      expect(formatDateRange('2023-01-01', undefined)).toBe('Jan 1');
    });

    it('should return only the end date if start date is missing', () => {
      expect(formatDateRange(null, '2023-01-05')).toBe('Jan 5');
      expect(formatDateRange(undefined, '2023-01-05')).toBe('Jan 5');
    });

    it('should return null if both dates are missing or invalid', () => {
      expect(formatDateRange(null, null)).toBeNull();
      expect(formatDateRange(undefined, undefined)).toBeNull();
      expect(formatDateRange('invalid', 'invalid')).toBeNull();
    });

    it('should handle one valid and one invalid date by showing only the valid one', () => {
      expect(formatDateRange('2023-01-01', 'invalid')).toBe('Jan 1');
      expect(formatDateRange('invalid', '2023-01-05')).toBe('Jan 5');
    });

    it('should handle empty strings as invalid dates', () => {
      expect(formatDateRange('', '2023-01-05')).toBe('Jan 5');
      expect(formatDateRange('2023-01-05', '')).toBe('Jan 5');
      expect(formatDateRange('', '')).toBeNull();
    });

    it('should handle garbage strings as invalid dates', () => {
      expect(formatDateRange('garbage', '2023-01-05')).toBe('Jan 5');
      expect(formatDateRange('2023-01-05', 'garbage')).toBe('Jan 5');
    });

    it('should handle start date being after end date (raw format)', () => {
      // The function currently doesn't validate order, it just formats what it's given.
      // This test ensures it doesn't crash or behave unexpectedly.
      expect(formatDateRange('2023-02-01', '2023-01-01')).toBe('Feb 1 - Jan 1');
    });

    it('should handle dates with time components correctly', () => {
      // parseISO should handle the time component and formatShortDate should strip it
      expect(formatDateRange('2023-01-01T10:00:00', '2023-01-05T15:30:00')).toBe(
        'Jan 1 - Jan 5',
      );
    });

    describe('verbose mode', () => {
      it('should format a range with two different valid dates verbosely', () => {
        expect(formatDateRange('2023-01-01', '2023-01-05', { verbose: true })).toBe(
          'Deals valid from January 1, 2023 to January 5, 2023',
        );
      });

      it('should format a range with the same start and end date verbosely', () => {
        expect(formatDateRange('2023-01-01', '2023-01-01', { verbose: true })).toBe(
          'Valid on January 1, 2023',
        );
      });

      it('should format only the start date verbosely', () => {
        expect(formatDateRange('2023-01-01', null, { verbose: true })).toBe(
          'Deals start January 1, 2023',
        );
      });

      it('should format only the end date verbosely', () => {
        expect(formatDateRange(null, '2023-01-05', { verbose: true })).toBe(
          'Deals end January 5, 2023',
        );
      });

      it('should handle one valid and one invalid date verbosely', () => {
        expect(formatDateRange('2023-01-01', 'invalid', { verbose: true })).toBe(
          'Deals start January 1, 2023',
        );
      });

      it('should handle start date being after end date verbosely', () => {
        expect(formatDateRange('2023-02-01', '2023-01-01', { verbose: true })).toBe(
          'Deals valid from February 1, 2023 to January 1, 2023',
        );
      });
    });
  });

  describe('getCurrentDateISOString', () => {
    it('should return the current date in YYYY-MM-DD format', () => {
      const fakeDate = new Date('2025-12-25T10:00:00Z');
      vi.setSystemTime(fakeDate);
      expect(getCurrentDateISOString()).toBe('2025-12-25');
    });
  });
});
