// #region TranslateRunsApiTest [C:3] [TYPE Module] [SEMANTICS test, translate, runs, api, control]
// @BRIEF Unit tests for translation runs API — trigger, status, history, records, retry, cancel, batches, metrics.
// @LAYER Tests
// @RELATION BINDS_TO -> [TranslateRunsApi]
// @TEST_CONTRACT: triggerRun -> POST with optional full_translation param
// @TEST_CONTRACT: fetchRunStatus -> GET /translate/runs/{id}
// @TEST_CONTRACT: fetchRunHistory -> GET /translate/jobs/{id}/runs with pagination
// @TEST_CONTRACT: fetchRunRecords -> GET /translate/runs/{id}/records with status/dedup filter
// @TEST_CONTRACT: retryFailedBatches -> POST /translate/runs/{id}/retry
// @TEST_CONTRACT: retryInsert -> POST /translate/runs/{id}/retry-insert
// @TEST_CONTRACT: cancelRun -> POST /translate/runs/{id}/cancel
// @TEST_CONTRACT: fetchRunBatches -> GET /translate/runs/{id}/batches
// @TEST_CONTRACT: fetchAllRuns -> GET /translate/runs with filters
// @TEST_CONTRACT: fetchRunDetail -> GET /translate/runs/{id}/detail
// @TEST_CONTRACT: fetchJobMetrics -> GET /translate/jobs/{id}/metrics
// @TEST_CONTRACT: fetchAllMetrics -> GET /translate/metrics

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('triggerRun', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	it('triggers run without full_translation', async () => {
		mockApi.postApi.mockResolvedValue({ run_id: 'run-1', status: 'PENDING' });
		const { triggerRun } = await import('$lib/api/translate/runs.js');
		const result = await triggerRun('job-1');
		expect(result).toEqual({ run_id: 'run-1', status: 'PENDING' });
		expect(mockApi.postApi).toHaveBeenCalledWith('/translate/jobs/job-1/run', {});
	});

	it('triggers full translation run when full=true', async () => {
		mockApi.postApi.mockResolvedValue({ run_id: 'run-2', full: true });
		const { triggerRun } = await import('$lib/api/translate/runs.js');
		const result = await triggerRun('job-1', true);
		expect(result).toEqual({ run_id: 'run-2', full: true });
		expect(mockApi.postApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/run?full_translation=true',
			{},
		);
	});

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

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

	it('fetches run status by ID', async () => {
		mockApi.fetchApi.mockResolvedValue({ id: 'run-1', status: 'RUNNING', progress: 0.5 });
		const { fetchRunStatus } = await import('$lib/api/translate/runs.js');
		const result = await fetchRunStatus('run-1');
		expect(result).toEqual({ id: 'run-1', status: 'RUNNING', progress: 0.5 });
		expect(mockApi.fetchApi).toHaveBeenCalledWith('/translate/runs/run-1');
	});

	it('normalizes error on fetch status failure', async () => {
		mockApi.fetchApi.mockRejectedValue(new Error('Status fetch failed'));
		const { fetchRunStatus } = await import('$lib/api/translate/runs.js');
		await expect(fetchRunStatus('run-1')).rejects.toMatchObject({
			message: 'Status fetch failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('fetches run history without options', async () => {
		mockApi.fetchApi.mockResolvedValue({ runs: [], total: 0 });
		const { fetchRunHistory } = await import('$lib/api/translate/runs.js');
		const result = await fetchRunHistory('job-1');
		expect(result).toEqual({ runs: [], total: 0 });
		expect(mockApi.fetchApi).toHaveBeenCalledWith('/translate/jobs/job-1/runs');
	});

	it('fetches run history with pagination', async () => {
		mockApi.fetchApi.mockResolvedValue({ runs: [{ id: 'r1' }], total: 1 });
		const { fetchRunHistory } = await import('$lib/api/translate/runs.js');
		const result = await fetchRunHistory('job-1', { page: 2, page_size: 10 });
		expect(result).toEqual({ runs: [{ id: 'r1' }], total: 1 });
		expect(mockApi.fetchApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/runs?page=2&page_size=10',
		);
	});

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

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

	it('fetches records without filters', async () => {
		mockApi.fetchApi.mockResolvedValue({ records: [], total: 0 });
		const { fetchRunRecords } = await import('$lib/api/translate/runs.js');
		const result = await fetchRunRecords('run-1');
		expect(result).toEqual({ records: [], total: 0 });
		expect(mockApi.fetchApi).toHaveBeenCalledWith('/translate/runs/run-1/records');
	});

	it('fetches records with status and deduplicate filter', async () => {
		mockApi.fetchApi.mockResolvedValue({ records: [{ id: 'r1' }] });
		const { fetchRunRecords } = await import('$lib/api/translate/runs.js');
		const result = await fetchRunRecords('run-1', {
			page: 1,
			page_size: 50,
			status: 'pending',
			deduplicate: true,
		});
		expect(result).toEqual({ records: [{ id: 'r1' }] });
		expect(mockApi.fetchApi).toHaveBeenCalledWith(
			'/translate/runs/run-1/records?page=1&page_size=50&status=pending&deduplicate=true',
		);
	});

	it('handles empty records gracefully', async () => {
		mockApi.fetchApi.mockResolvedValue({ records: [], total: 0 });
		const { fetchRunRecords } = await import('$lib/api/translate/runs.js');
		const result = await fetchRunRecords('run-empty');
		expect(result).toEqual({ records: [], total: 0 });
	});

	it('normalizes error on fetch records failure', async () => {
		mockApi.fetchApi.mockRejectedValue(new Error('Records fetch failed'));
		const { fetchRunRecords } = await import('$lib/api/translate/runs.js');
		await expect(fetchRunRecords('run-1')).rejects.toMatchObject({
			message: 'Records fetch failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('sends POST to retry endpoint', async () => {
		mockApi.postApi.mockResolvedValue({ status: 'retrying' });
		const { retryFailedBatches } = await import('$lib/api/translate/runs.js');
		const result = await retryFailedBatches('run-1');
		expect(result).toEqual({ status: 'retrying' });
		expect(mockApi.postApi).toHaveBeenCalledWith('/translate/runs/run-1/retry', {});
	});

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

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

	it('sends POST to retry-insert endpoint', async () => {
		mockApi.postApi.mockResolvedValue({ status: 'retrying_insert' });
		const { retryInsert } = await import('$lib/api/translate/runs.js');
		const result = await retryInsert('run-1');
		expect(result).toEqual({ status: 'retrying_insert' });
		expect(mockApi.postApi).toHaveBeenCalledWith('/translate/runs/run-1/retry-insert', {});
	});

	it('normalizes error on retry insert failure', async () => {
		mockApi.postApi.mockRejectedValue(new Error('Retry insert failed'));
		const { retryInsert } = await import('$lib/api/translate/runs.js');
		await expect(retryInsert('run-1')).rejects.toMatchObject({
			message: 'Retry insert failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('sends POST to cancel endpoint', async () => {
		mockApi.postApi.mockResolvedValue({ status: 'CANCELLED' });
		const { cancelRun } = await import('$lib/api/translate/runs.js');
		const result = await cancelRun('run-1');
		expect(result).toEqual({ status: 'CANCELLED' });
		expect(mockApi.postApi).toHaveBeenCalledWith('/translate/runs/run-1/cancel', {});
	});

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

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

	it('fetches batch breakdown for a run', async () => {
		mockApi.fetchApi.mockResolvedValue({
			batches: [{ id: 'b1', status: 'completed', total: 100, processed: 100 }],
		});
		const { fetchRunBatches } = await import('$lib/api/translate/runs.js');
		const result = await fetchRunBatches('run-1');
		expect(result).toEqual({
			batches: [{ id: 'b1', status: 'completed', total: 100, processed: 100 }],
		});
		expect(mockApi.fetchApi).toHaveBeenCalledWith('/translate/runs/run-1/batches');
	});

	it('normalizes error on fetch batches failure', async () => {
		mockApi.fetchApi.mockRejectedValue(new Error('Batches fetch failed'));
		const { fetchRunBatches } = await import('$lib/api/translate/runs.js');
		await expect(fetchRunBatches('run-1')).rejects.toMatchObject({
			message: 'Batches fetch failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('fetches all runs without filters', async () => {
		mockApi.fetchApi.mockResolvedValue({ runs: [], total: 0 });
		const { fetchAllRuns } = await import('$lib/api/translate/runs.js');
		const result = await fetchAllRuns();
		expect(result).toEqual({ runs: [], total: 0 });
		expect(mockApi.fetchApi).toHaveBeenCalledWith('/translate/runs');
	});

	it('fetches all runs with job_id, status, trigger_type, and created_by filters', async () => {
		mockApi.fetchApi.mockResolvedValue({ runs: [{ id: 'r1' }] });
		const { fetchAllRuns } = await import('$lib/api/translate/runs.js');
		const result = await fetchAllRuns({
			job_id: 'job-1',
			status: 'FAILED',
			trigger_type: 'manual',
			created_by: 'user1',
			page: 1,
			page_size: 25,
		});
		expect(result).toEqual({ runs: [{ id: 'r1' }] });
		expect(mockApi.fetchApi).toHaveBeenCalledWith(
			'/translate/runs?page=1&page_size=25&job_id=job-1&status=FAILED&trigger_type=manual&created_by=user1',
		);
	});

	it('normalizes error on fetch all runs failure', async () => {
		mockApi.fetchApi.mockRejectedValue(new Error('All runs fetch failed'));
		const { fetchAllRuns } = await import('$lib/api/translate/runs.js');
		await expect(fetchAllRuns()).rejects.toMatchObject({
			message: 'All runs fetch failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('fetches detailed run info', async () => {
		mockApi.fetchApi.mockResolvedValue({
			id: 'run-1',
			status: 'COMPLETED',
			stats: { total: 100, translated: 95, failed: 5 },
		});
		const { fetchRunDetail } = await import('$lib/api/translate/runs.js');
		const result = await fetchRunDetail('run-1');
		expect(result).toEqual({
			id: 'run-1',
			status: 'COMPLETED',
			stats: { total: 100, translated: 95, failed: 5 },
		});
		expect(mockApi.fetchApi).toHaveBeenCalledWith('/translate/runs/run-1/detail');
	});

	it('normalizes error on fetch run detail failure', async () => {
		mockApi.fetchApi.mockRejectedValue(new Error('Detail fetch failed'));
		const { fetchRunDetail } = await import('$lib/api/translate/runs.js');
		await expect(fetchRunDetail('run-1')).rejects.toMatchObject({
			message: 'Detail fetch failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('fetches metrics for a job', async () => {
		mockApi.fetchApi.mockResolvedValue({ total_runs: 10, avg_duration: 120 });
		const { fetchJobMetrics } = await import('$lib/api/translate/runs.js');
		const result = await fetchJobMetrics('job-1');
		expect(result).toEqual({ total_runs: 10, avg_duration: 120 });
		expect(mockApi.fetchApi).toHaveBeenCalledWith('/translate/jobs/job-1/metrics');
	});

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

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

	it('fetches cross-job aggregate metrics', async () => {
		mockApi.fetchApi.mockResolvedValue({ total_jobs: 5, total_runs: 42 });
		const { fetchAllMetrics } = await import('$lib/api/translate/runs.js');
		const result = await fetchAllMetrics();
		expect(result).toEqual({ total_jobs: 5, total_runs: 42 });
		expect(mockApi.fetchApi).toHaveBeenCalledWith('/translate/metrics');
	});

	it('normalizes error on fetch all metrics failure', async () => {
		mockApi.fetchApi.mockRejectedValue(new Error('All metrics failed'));
		const { fetchAllMetrics } = await import('$lib/api/translate/runs.js');
		await expect(fetchAllMetrics()).rejects.toMatchObject({
			message: 'All metrics failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});
// #endregion TranslateRunsApiTest
