// Mock dayjs before importing any other modules
const MOCK_DATE_STRING = '2024-01-15T00:30:00Z';
const MOCK_DATE_STRING_NON_LEAP_YEAR = '2023-01-15T00:30:00Z';
const MOCK_DATE_STRING_SPANS_MONTHS = '2024-01-31T00:30:00Z';
const FREQ_DAILY = 'FREQ=DAILY';
const TEN_THIRTY_TIME = '10:30:00';
const NINE_AM_TIME = '09:00:00';
jest.mock('dayjs', () => {
	const originalDayjs = jest.requireActual('dayjs');
	const mockDayjs = jest.fn((date?: string | Date) => {
		if (date) {
			return originalDayjs(date);
		}
		return originalDayjs(MOCK_DATE_STRING);
	});
	Object.keys(originalDayjs).forEach((key) => {
		(mockDayjs as unknown as Record<string, unknown>)[key] = originalDayjs[key];
	});
	return mockDayjs;
});

import { UniversalYAxisUnit } from 'components/YAxisUnitSelector/types';
import { EvaluationWindowState } from 'container/CreateAlertV2/context/types';
import dayjs, { Dayjs } from 'dayjs';
import { rrulestr } from 'rrule';

import { RollingWindowTimeframes } from '../types';
import {
	buildAlertScheduleFromCustomSchedule,
	buildAlertScheduleFromRRule,
	getCumulativeWindowTimeframeText,
	getCustomRollingWindowTimeframeText,
	getEvaluationWindowTypeText,
	getRollingWindowTimeframeText,
	getTimeframeText,
	isValidRRule,
} from '../utils';

jest.mock('rrule', () => ({
	rrulestr: jest.fn(),
}));

jest.mock('components/CustomTimePicker/timezoneUtils', () => ({
	generateTimezoneData: jest.fn().mockReturnValue([
		{ name: 'UTC', value: 'UTC', offset: '+00:00' },
		{ name: 'America/New_York', value: 'America/New_York', offset: '-05:00' },
		{ name: 'Europe/London', value: 'Europe/London', offset: '+00:00' },
	]),
}));

const mockEvaluationWindowState: EvaluationWindowState = {
	windowType: 'rolling',
	timeframe: '5m0s',
	startingAt: {
		number: '0',
		timezone: 'UTC',
		time: '00:00:00',
		unit: UniversalYAxisUnit.MINUTES,
	},
};

const formatDate = (date: Date): string =>
	dayjs(date).format('DD-MM-YYYY HH:mm:ss');

describe('utils', () => {
	beforeEach(() => {
		jest.clearAllMocks();
	});

	describe('getEvaluationWindowTypeText', () => {
		it('should return correct text for rolling window', () => {
			expect(getEvaluationWindowTypeText('rolling')).toBe('Rolling');
		});

		it('should return correct text for cumulative window', () => {
			expect(getEvaluationWindowTypeText('cumulative')).toBe('Cumulative');
		});

		it('should default to empty string for unknown type', () => {
			expect(
				getEvaluationWindowTypeText('unknown' as 'rolling' | 'cumulative'),
			).toBe('');
		});
	});

	describe('getCumulativeWindowTimeframeText', () => {
		it('should return correct text for current hour', () => {
			expect(
				getCumulativeWindowTimeframeText({
					...mockEvaluationWindowState,
					windowType: 'cumulative',
					timeframe: 'currentHour',
				}),
			).toBe('Current hour, starting at minute 0 (UTC)');
		});

		it('should return correct text for current day', () => {
			expect(
				getCumulativeWindowTimeframeText({
					...mockEvaluationWindowState,
					windowType: 'cumulative',
					timeframe: 'currentDay',
				}),
			).toBe('Current day, starting from 00:00:00 (UTC)');
		});

		it('should return correct text for current month', () => {
			expect(
				getCumulativeWindowTimeframeText({
					...mockEvaluationWindowState,
					windowType: 'cumulative',
					timeframe: 'currentMonth',
				}),
			).toBe('Current month, starting from day 0 at 00:00:00 (UTC)');
		});

		it('should default to empty string for unknown timeframe', () => {
			expect(
				getCumulativeWindowTimeframeText({
					...mockEvaluationWindowState,
					windowType: 'cumulative',
					timeframe: 'unknown',
				}),
			).toBe('');
		});
	});

	describe('getRollingWindowTimeframeText', () => {
		it('should return correct text for last 5 minutes', () => {
			expect(
				getRollingWindowTimeframeText(RollingWindowTimeframes.LAST_5_MINUTES),
			).toBe('Last 5 minutes');
		});

		it('should return correct text for last 10 minutes', () => {
			expect(
				getRollingWindowTimeframeText(RollingWindowTimeframes.LAST_10_MINUTES),
			).toBe('Last 10 minutes');
		});

		it('should return correct text for last 15 minutes', () => {
			expect(
				getRollingWindowTimeframeText(RollingWindowTimeframes.LAST_15_MINUTES),
			).toBe('Last 15 minutes');
		});

		it('should return correct text for last 30 minutes', () => {
			expect(
				getRollingWindowTimeframeText(RollingWindowTimeframes.LAST_30_MINUTES),
			).toBe('Last 30 minutes');
		});

		it('should return correct text for last 1 hour', () => {
			expect(
				getRollingWindowTimeframeText(RollingWindowTimeframes.LAST_1_HOUR),
			).toBe('Last 1 hour');
		});

		it('should return correct text for last 2 hours', () => {
			expect(
				getRollingWindowTimeframeText(RollingWindowTimeframes.LAST_2_HOURS),
			).toBe('Last 2 hours');
		});

		it('should return correct text for last 4 hours', () => {
			expect(
				getRollingWindowTimeframeText(RollingWindowTimeframes.LAST_4_HOURS),
			).toBe('Last 4 hours');
		});

		it('should default to Last 5 minutes for unknown timeframe', () => {
			expect(
				getRollingWindowTimeframeText('unknown' as RollingWindowTimeframes),
			).toBe('');
		});
	});

	describe('getCustomRollingWindowTimeframeText', () => {
		it('should return correct text for custom rolling window', () => {
			expect(getCustomRollingWindowTimeframeText(mockEvaluationWindowState)).toBe(
				'Last 0 Minutes',
			);
		});
	});

	describe('getTimeframeText', () => {
		it('should call getCustomRollingWindowTimeframeText for custom rolling window', () => {
			expect(
				getTimeframeText({
					...mockEvaluationWindowState,
					windowType: 'rolling',
					timeframe: 'custom',
					startingAt: {
						...mockEvaluationWindowState.startingAt,
						number: '4',
					},
				}),
			).toBe('Last 4 Minutes');
		});

		it('should call getRollingWindowTimeframeText for rolling window', () => {
			expect(getTimeframeText(mockEvaluationWindowState)).toBe('Last 5 minutes');
		});

		it('should call getCumulativeWindowTimeframeText for cumulative window', () => {
			expect(
				getTimeframeText({
					...mockEvaluationWindowState,
					windowType: 'cumulative',
					timeframe: 'currentDay',
				}),
			).toBe('Current day, starting from 00:00:00 (UTC)');
		});
	});

	describe('buildAlertScheduleFromRRule', () => {
		const mockRRule = {
			all: jest.fn((callback) => {
				const dates = [
					new Date(MOCK_DATE_STRING),
					new Date('2024-01-16T10:30:00Z'),
					new Date('2024-01-17T10:30:00Z'),
				];
				dates.forEach((date, index) => callback(date, index));
			}),
		};

		beforeEach(() => {
			(rrulestr as jest.Mock).mockReturnValue(mockRRule);
		});

		it('should return null for empty rrule string', () => {
			const result = buildAlertScheduleFromRRule('', null, '10:30:00');
			expect(result).toBeNull();
		});

		it('should build schedule from valid rrule string', () => {
			const result = buildAlertScheduleFromRRule(
				FREQ_DAILY,
				null,
				TEN_THIRTY_TIME,
			);

			expect(rrulestr).toHaveBeenCalledWith(FREQ_DAILY);
			expect(result).toStrictEqual([
				new Date(MOCK_DATE_STRING),
				new Date('2024-01-16T10:30:00Z'),
				new Date('2024-01-17T10:30:00Z'),
			]);
		});

		it('should handle rrule with DTSTART', () => {
			const date = dayjs('2024-01-20');
			buildAlertScheduleFromRRule(FREQ_DAILY, date, NINE_AM_TIME);

			// When date is provided, DTSTART is automatically added to the rrule string
			expect(rrulestr).toHaveBeenCalledWith(
				expect.stringMatching(/DTSTART:20240120T\d{6}Z/),
			);
		});

		it('should handle rrule without DTSTART', () => {
			// Test with no date provided - should use original rrule string
			const result = buildAlertScheduleFromRRule(FREQ_DAILY, null, NINE_AM_TIME);

			expect(rrulestr).toHaveBeenCalledWith(FREQ_DAILY);
			expect(result).toHaveLength(3);
		});

		it('should handle escaped newlines', () => {
			buildAlertScheduleFromRRule('FREQ=DAILY\\nINTERVAL=1', null, '10:30:00');

			expect(rrulestr).toHaveBeenCalledWith('FREQ=DAILY\nINTERVAL=1');
		});

		it('should limit occurrences to maxOccurrences', () => {
			const result = buildAlertScheduleFromRRule(FREQ_DAILY, null, '10:30:00', 2);

			expect(result).toHaveLength(2);
		});

		it('should return null on error', () => {
			(rrulestr as jest.Mock).mockImplementation(() => {
				throw new Error('Invalid rrule');
			});

			const result = buildAlertScheduleFromRRule('INVALID', null, '10:30:00');
			expect(result).toBeNull();
		});
	});

	describe('buildAlertScheduleFromCustomSchedule', () => {
		it('should generate monthly occurrences', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'month',
				['1', '15'],
				'10:30:00',
				5,
			);

			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'15-01-2024 10:30:00',
				'01-02-2024 10:30:00',
				'15-02-2024 10:30:00',
				'01-03-2024 10:30:00',
				'15-03-2024 10:30:00',
			]);
		});

		it('should generate weekly occurrences', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'week',
				['monday', 'friday'],
				'12:30:00',
				5,
			);

			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'15-01-2024 12:30:00',
				'19-01-2024 12:30:00',
				'22-01-2024 12:30:00',
				'26-01-2024 12:30:00',
				'29-01-2024 12:30:00',
			]);
		});

		it('should generate weekly occurrences including today if alert time is in the future', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'week',
				['monday', 'friday'],
				'10:30:00',
				5,
			);

			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			// today included (15-01-2024 00:30:00)
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'15-01-2024 10:30:00',
				'19-01-2024 10:30:00',
				'22-01-2024 10:30:00',
				'26-01-2024 10:30:00',
				'29-01-2024 10:30:00',
			]);
		});

		it('should generate weekly occurrences excluding today if alert time is in the past', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'week',
				['monday', 'friday'],
				'00:00:00',
				5,
			);

			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			// today excluded (15-01-2024 00:30:00)
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'19-01-2024 00:00:00',
				'22-01-2024 00:00:00',
				'26-01-2024 00:00:00',
				'29-01-2024 00:00:00',
				'02-02-2024 00:00:00',
			]);
		});

		it('should generate weekly occurrences excluding today if alert time is in the present (right now)', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'week',
				['monday', 'friday'],
				'00:30:00',
				5,
			);

			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			// today excluded (15-01-2024 00:30:00)
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'19-01-2024 00:30:00',
				'22-01-2024 00:30:00',
				'26-01-2024 00:30:00',
				'29-01-2024 00:30:00',
				'02-02-2024 00:30:00',
			]);
		});

		it('should generate monthly occurrences including today if alert time is in the future', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'month',
				['15'],
				'10:30:00',
				5,
			);
			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			// today included (15-01-2024 10:30:00)
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'15-01-2024 10:30:00',
				'15-02-2024 10:30:00',
				'15-03-2024 10:30:00',
				'15-04-2024 10:30:00',
				'15-05-2024 10:30:00',
			]);
		});

		it('should generate monthly occurrences excluding today if alert time is in the past', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'month',
				['15'],
				'00:00:00',
				5,
			);
			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			// today excluded (15-01-2024 10:30:00)
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'15-02-2024 00:00:00',
				'15-03-2024 00:00:00',
				'15-04-2024 00:00:00',
				'15-05-2024 00:00:00',
				'15-06-2024 00:00:00',
			]);
		});

		it('should generate monthly occurrences excluding today if alert time is in the present (right now)', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'month',
				['15'],
				'00:30:00',
				5,
			);
			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			// today excluded (15-01-2024 10:30:00)
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'15-02-2024 00:30:00',
				'15-03-2024 00:30:00',
				'15-04-2024 00:30:00',
				'15-05-2024 00:30:00',
				'15-06-2024 00:30:00',
			]);
		});

		it('should account for february 29th in a leap year', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'month',
				['29'],
				'10:30:00',
				5,
			);

			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'29-01-2024 10:30:00',
				'29-02-2024 10:30:00',
				'29-03-2024 10:30:00',
				'29-04-2024 10:30:00',
				'29-05-2024 10:30:00',
			]);
		});

		it('should skip 31st on 30-day months', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'month',
				['31'],
				'10:30:00',
				5,
			);

			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'31-01-2024 10:30:00',
				'31-03-2024 10:30:00',
				'31-05-2024 10:30:00',
				'31-07-2024 10:30:00',
				'31-08-2024 10:30:00',
			]);
		});

		it('should skip february 29th in a non-leap year', async () => {
			jest.resetModules(); // clear previous mocks

			jest.doMock('dayjs', () => {
				const originalDayjs = jest.requireActual('dayjs');
				const mockDayjs = (date?: string | Date): Dayjs => {
					if (date) {
						return originalDayjs(date);
					}
					return originalDayjs(MOCK_DATE_STRING_NON_LEAP_YEAR);
				};
				Object.assign(mockDayjs, originalDayjs);
				return mockDayjs;
			});

			const { buildAlertScheduleFromCustomSchedule } = await import('../utils');
			const { default: dayjs } = await import('dayjs');

			const formatDate = (date: Date): string =>
				dayjs(date).format('DD-MM-YYYY HH:mm:ss');

			const result = buildAlertScheduleFromCustomSchedule(
				'month',
				['29'],
				'10:30:00',
				5,
			);

			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'29-01-2023 10:30:00',
				'29-03-2023 10:30:00',
				'29-04-2023 10:30:00',
				'29-05-2023 10:30:00',
				'29-06-2023 10:30:00',
			]);
		});

		it('should generate daily occurrences', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'day',
				[],
				'10:40:00',
				5,
			);
			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'15-01-2024 10:40:00',
				'16-01-2024 10:40:00',
				'17-01-2024 10:40:00',
				'18-01-2024 10:40:00',
				'19-01-2024 10:40:00',
			]);
		});

		it('should generate daily occurrences excluding today if alert time is in the past', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'day',
				[],
				'00:00:00',
				5,
			);
			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'16-01-2024 00:00:00',
				'17-01-2024 00:00:00',
				'18-01-2024 00:00:00',
				'19-01-2024 00:00:00',
				'20-01-2024 00:00:00',
			]);
		});

		it('should generate daily occurrences excluding today if alert time is in the present (right now)', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'day',
				[],
				'00:30:00',
				5,
			);
			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'16-01-2024 00:30:00',
				'17-01-2024 00:30:00',
				'18-01-2024 00:30:00',
				'19-01-2024 00:30:00',
				'20-01-2024 00:30:00',
			]);
		});

		it('should generate daily occurrences including today if alert time is in the future', () => {
			const result = buildAlertScheduleFromCustomSchedule(
				'day',
				[],
				'10:30:00',
				5,
			);
			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'15-01-2024 10:30:00',
				'16-01-2024 10:30:00',
				'17-01-2024 10:30:00',
				'18-01-2024 10:30:00',
				'19-01-2024 10:30:00',
			]);
		});

		it('daily occurrences should span across months correctly', async () => {
			jest.resetModules(); // clear previous mocks

			jest.doMock('dayjs', () => {
				const originalDayjs = jest.requireActual('dayjs');
				const mockDayjs = (date?: string | Date): Dayjs => {
					if (date) {
						return originalDayjs(date);
					}
					return originalDayjs(MOCK_DATE_STRING_SPANS_MONTHS);
				};
				Object.assign(mockDayjs, originalDayjs);
				return mockDayjs;
			});

			const { buildAlertScheduleFromCustomSchedule } = await import('../utils');
			const { default: dayjs } = await import('dayjs');

			const formatDate = (date: Date): string =>
				dayjs(date).format('DD-MM-YYYY HH:mm:ss');

			const result = buildAlertScheduleFromCustomSchedule(
				'day',
				[],
				'10:30:00',
				5,
			);
			expect(result).toBeDefined();
			expect(Array.isArray(result)).toBe(true);
			expect(result?.map((res) => formatDate(res))).toStrictEqual([
				'31-01-2024 10:30:00',
				'01-02-2024 10:30:00',
				'02-02-2024 10:30:00',
				'03-02-2024 10:30:00',
				'04-02-2024 10:30:00',
			]);
		});
	});

	describe('isValidRRule', () => {
		beforeEach(() => {
			(rrulestr as jest.Mock).mockReturnValue({});
		});

		it('should return true for valid rrule', () => {
			expect(isValidRRule(FREQ_DAILY)).toBe(true);
			expect(rrulestr).toHaveBeenCalledWith(FREQ_DAILY);
		});

		it('should handle escaped newlines', () => {
			expect(isValidRRule('FREQ=DAILY\\nINTERVAL=1')).toBe(true);
			expect(rrulestr).toHaveBeenCalledWith('FREQ=DAILY\nINTERVAL=1');
		});

		it('should return false for invalid rrule', () => {
			(rrulestr as jest.Mock).mockImplementation(() => {
				throw new Error('Invalid rrule');
			});

			expect(isValidRRule('INVALID')).toBe(false);
		});
	});
});
