// #region TranslateSchedulesApiTest [C:3] [TYPE Module] [SEMANTICS test, translate, schedules, api, cron]
// @BRIEF Unit tests for translation schedule API — fetch, set, delete, enable, disable, next-executions.
// @LAYER Tests
// @RELATION BINDS_TO -> [TranslateSchedulesApi]
// @TEST_CONTRACT: fetchSchedule -> GET /translate/jobs/{id}/schedule with suppressToast
// @TEST_CONTRACT: setSchedule -> PUT /translate/jobs/{id}/schedule with cron payload
// @TEST_CONTRACT: deleteSchedule -> DELETE /translate/jobs/{id}/schedule
// @TEST_CONTRACT: enableSchedule -> POST /translate/jobs/{id}/schedule/enable
// @TEST_CONTRACT: disableSchedule -> POST /translate/jobs/{id}/schedule/disable
// @TEST_CONTRACT: fetchNextExecutions -> GET /translate/jobs/{id}/schedule/next-executions?n=N

import { describe, it, expect, vi, beforeEach } from 'vitest';

const mockApi = {
	fetchApi: vi.fn(),
	postApi: vi.fn(),
	requestApi: vi.fn(),
	deleteApi: vi.fn(),
};

vi.mock('$lib/api.js', () => ({
	api: mockApi,
}));

describe('fetchSchedule', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	it('fetches schedule with suppressToast', async () => {
		mockApi.fetchApi.mockResolvedValue({ cron: '0 9 * * 1', enabled: true });
		const { fetchSchedule } = await import('$lib/api/translate/schedules.js');
		const result = await fetchSchedule('job-1');
		expect(result).toEqual({ cron: '0 9 * * 1', enabled: true });
		expect(mockApi.fetchApi).toHaveBeenCalledWith('/translate/jobs/job-1/schedule', {
			suppressToast: true,
		});
	});

	it('normalizes error on fetch failure', async () => {
		mockApi.fetchApi.mockRejectedValue(new Error('Not found'));
		const { fetchSchedule } = await import('$lib/api/translate/schedules.js');
		await expect(fetchSchedule('job-1')).rejects.toMatchObject({
			message: 'Not found',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

describe('setSchedule', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	it('sends PUT with cron payload', async () => {
		mockApi.requestApi.mockResolvedValue({ cron: '0 0 * * *', enabled: true });
		const { setSchedule } = await import('$lib/api/translate/schedules.js');
		const payload = { cron_expression: '0 0 * * *', enabled: true };
		const result = await setSchedule('job-1', payload);
		expect(result).toEqual({ cron: '0 0 * * *', enabled: true });
		expect(mockApi.requestApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/schedule',
			'PUT',
			payload,
		);
	});

	it('normalizes error on set failure', async () => {
		mockApi.requestApi.mockRejectedValue(new Error('Invalid cron'));
		const { setSchedule } = await import('$lib/api/translate/schedules.js');
		await expect(setSchedule('job-1', {})).rejects.toMatchObject({
			message: 'Invalid cron',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

describe('deleteSchedule', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	it('sends DELETE request', async () => {
		mockApi.deleteApi.mockResolvedValue({ success: true });
		const { deleteSchedule } = await import('$lib/api/translate/schedules.js');
		const result = await deleteSchedule('job-1');
		expect(result).toEqual({ success: true });
		expect(mockApi.deleteApi).toHaveBeenCalledWith('/translate/jobs/job-1/schedule');
	});

	it('normalizes error on delete failure', async () => {
		mockApi.deleteApi.mockRejectedValue(new Error('Delete schedule failed'));
		const { deleteSchedule } = await import('$lib/api/translate/schedules.js');
		await expect(deleteSchedule('job-1')).rejects.toMatchObject({
			message: 'Delete schedule failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

describe('enableSchedule', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	it('sends POST to enable endpoint', async () => {
		mockApi.postApi.mockResolvedValue({ enabled: true });
		const { enableSchedule } = await import('$lib/api/translate/schedules.js');
		const result = await enableSchedule('job-1');
		expect(result).toEqual({ enabled: true });
		expect(mockApi.postApi).toHaveBeenCalledWith('/translate/jobs/job-1/schedule/enable', {});
	});

	it('normalizes error on enable failure', async () => {
		mockApi.postApi.mockRejectedValue(new Error('Enable failed'));
		const { enableSchedule } = await import('$lib/api/translate/schedules.js');
		await expect(enableSchedule('job-1')).rejects.toMatchObject({
			message: 'Enable failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('normalizes error on fetch next executions failure', async () => {
		mockApi.fetchApi.mockRejectedValue(new Error('Next executions failed'));
		const { fetchNextExecutions } = await import('$lib/api/translate/schedules.js');
		await expect(fetchNextExecutions('job-1')).rejects.toMatchObject({
			message: 'Next executions failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

describe('normalizeTranslateError — edge cases', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	it('handles string errors (typeof error === string branch)', async () => {
		mockApi.fetchApi.mockRejectedValue('string error message');
		const { fetchSchedule } = await import('$lib/api/translate/schedules.js');
		await expect(fetchSchedule('job-1')).rejects.toMatchObject({
			message: 'string error message',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('handles null errors (defaultMessage branch)', async () => {
		mockApi.fetchApi.mockRejectedValue(null);
		const { fetchSchedule } = await import('$lib/api/translate/schedules.js');
		await expect(fetchSchedule('job-1')).rejects.toMatchObject({
			message: 'Failed to fetch schedule',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('handles non-Error object without message (defaultMessage branch)', async () => {
		mockApi.requestApi.mockRejectedValue({ foo: 'bar' });
		const { setSchedule } = await import('$lib/api/translate/schedules.js');
		await expect(setSchedule('job-1', {})).rejects.toMatchObject({
			message: 'Failed to set schedule',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

describe('disableSchedule', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	it('sends POST to disable endpoint', async () => {
		mockApi.postApi.mockResolvedValue({ enabled: false });
		const { disableSchedule } = await import('$lib/api/translate/schedules.js');
		const result = await disableSchedule('job-1');
		expect(result).toEqual({ enabled: false });
		expect(mockApi.postApi).toHaveBeenCalledWith('/translate/jobs/job-1/schedule/disable', {});
	});

	it('normalizes error on disable failure', async () => {
		mockApi.postApi.mockRejectedValue(new Error('Disable failed'));
		const { disableSchedule } = await import('$lib/api/translate/schedules.js');
		await expect(disableSchedule('job-1')).rejects.toMatchObject({
			message: 'Disable failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

describe('fetchNextExecutions', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	it('fetches next 5 executions', async () => {
		mockApi.fetchApi.mockResolvedValue(['2025-01-06T09:00:00Z', '2025-01-13T09:00:00Z']);
		const { fetchNextExecutions } = await import('$lib/api/translate/schedules.js');
		const result = await fetchNextExecutions('job-1', 5);
		expect(result).toEqual(['2025-01-06T09:00:00Z', '2025-01-13T09:00:00Z']);
		expect(mockApi.fetchApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/schedule/next-executions?n=5',
			{ suppressToast: true },
		);
	});

	it('uses default n=3', async () => {
		mockApi.fetchApi.mockResolvedValue([]);
		const { fetchNextExecutions } = await import('$lib/api/translate/schedules.js');
		await fetchNextExecutions('job-1');
		expect(mockApi.fetchApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/schedule/next-executions?n=3',
			{ suppressToast: true },
		);
	});
});
// #endregion TranslateSchedulesApiTest
