// #region TranslateDatasourcesApiTest [C:3] [TYPE Module] [SEMANTICS test, translate, datasources, columns, preview, api]
// @BRIEF Unit tests for translation datasources API — column fetching, preview, row-level review actions.
// @LAYER Tests
// @RELATION BINDS_TO -> [TranslateDatasourcesApi]
// @TEST_CONTRACT: fetchDatasources -> GET /translate/datasources?env_id=... with search
// @TEST_CONTRACT: fetchDatasourceColumns -> GET /translate/datasources/{id}/columns
// @TEST_CONTRACT: fetchPreview -> POST /translate/jobs/{id}/preview
// @TEST_CONTRACT: approveRow -> PUT /translate/jobs/{id}/preview/rows/{rowKey}
// @TEST_CONTRACT: editRow -> PUT /translate/jobs/{id}/preview/rows/{rowKey}
// @TEST_CONTRACT: rejectRow -> PUT /translate/jobs/{id}/preview/rows/{rowKey}
// @TEST_CONTRACT: acceptPreview -> POST /translate/jobs/{id}/preview/accept
// @TEST_CONTRACT: fetchPreviewRecords -> GET /translate/preview/{sessionId}/records
// @TEST_EDGE: network_failure -> Normalizes to TranslateApiError
// @TEST_EDGE: env_id_query -> Encoded URI components

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

const mockFetchApi = vi.fn();
const mockPostApi = vi.fn();
const mockRequestApi = vi.fn();

vi.mock('$lib/api.js', () => ({
	api: {
		fetchApi: mockFetchApi,
		postApi: mockPostApi,
		requestApi: mockRequestApi,
	},
}));

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

	it('fetches datasources with envId and search term', async () => {
		const fixture = [{ id: 'ds-1', name: 'Sales DB' }];
		mockFetchApi.mockResolvedValue(fixture);
		const { fetchDatasources } = await import('$lib/api/translate/datasources.js');
		const result = await fetchDatasources('env-1', 'sales');
		expect(result).toEqual(fixture);
		expect(mockFetchApi).toHaveBeenCalledWith(
			'/translate/datasources?env_id=env-1&search=sales'
		);
	});

	it('fetches datasources with default empty search', async () => {
		mockFetchApi.mockResolvedValue([]);
		const { fetchDatasources } = await import('$lib/api/translate/datasources.js');
		const result = await fetchDatasources('env-2');
		expect(result).toEqual([]);
		expect(mockFetchApi).toHaveBeenCalledWith(
			'/translate/datasources?env_id=env-2&search='
		);
	});

	it('encodes special characters in envId and search', async () => {
		mockFetchApi.mockResolvedValue([]);
		const { fetchDatasources } = await import('$lib/api/translate/datasources.js');
		await fetchDatasources('env with/spaces', 'search & special');
		expect(mockFetchApi).toHaveBeenCalledWith(
			'/translate/datasources?env_id=env%20with%2Fspaces&search=search%20%26%20special'
		);
	});

	it('throws normalized error on API failure', async () => {
		mockFetchApi.mockRejectedValue(new Error('Connection refused'));
		const { fetchDatasources } = await import('$lib/api/translate/datasources.js');
		await expect(fetchDatasources('env-1')).rejects.toMatchObject({
			message: 'Connection refused',
			code: 'TRANSLATE_API_ERROR',
			retryable: true,
		});
	});

	it('throws normalized error on string rejection', async () => {
		mockFetchApi.mockRejectedValue('Forbidden');
		const { fetchDatasources } = await import('$lib/api/translate/datasources.js');
		await expect(fetchDatasources('env-1')).rejects.toMatchObject({
			message: 'Forbidden',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('throws normalized error with default message when error has no message', async () => {
		mockFetchApi.mockRejectedValue({});
		const { fetchDatasources } = await import('$lib/api/translate/datasources.js');
		await expect(fetchDatasources('env-1')).rejects.toMatchObject({
			message: 'Failed to fetch datasources',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('fetches columns for a datasource', async () => {
		const fixture = [{ name: 'id', type: 'integer' }, { name: 'name', type: 'string' }];
		mockFetchApi.mockResolvedValue(fixture);
		const { fetchDatasourceColumns } = await import('$lib/api/translate/datasources.js');
		const result = await fetchDatasourceColumns('ds-1', 'env-1');
		expect(result).toEqual(fixture);
		expect(mockFetchApi).toHaveBeenCalledWith(
			'/translate/datasources/ds-1/columns?env_id=env-1'
		);
	});

	it('encodes envId in query string but not datasourceId in path', async () => {
		mockFetchApi.mockResolvedValue([]);
		const { fetchDatasourceColumns } = await import('$lib/api/translate/datasources.js');
		await fetchDatasourceColumns('ds/1', 'env test');
		expect(mockFetchApi).toHaveBeenCalledWith(
			'/translate/datasources/ds/1/columns?env_id=env%20test'
		);
	});

	it('throws normalized error on failure', async () => {
		mockFetchApi.mockRejectedValue(new Error('Timeout'));
		const { fetchDatasourceColumns } = await import('$lib/api/translate/datasources.js');
		await expect(fetchDatasourceColumns('ds-1', 'env-1')).rejects.toMatchObject({
			message: 'Timeout',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('sends POST with sample_size and default envId', async () => {
		const fixture = { rows: [{ id: 1 }] };
		mockPostApi.mockResolvedValue(fixture);
		const { fetchPreview } = await import('$lib/api/translate/datasources.js');
		const result = await fetchPreview('job-1');
		expect(result).toEqual(fixture);
		expect(mockPostApi).toHaveBeenCalledWith('/translate/jobs/job-1/preview', { sample_size: 10 });
	});

	it('sends POST with custom sample_size and envId', async () => {
		mockPostApi.mockResolvedValue({ rows: [] });
		const { fetchPreview } = await import('$lib/api/translate/datasources.js');
		await fetchPreview('job-1', 25, 'env-1');
		expect(mockPostApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/preview',
			{ sample_size: 25, env_id: 'env-1' }
		);
	});

	it('throws normalized error on failure', async () => {
		mockPostApi.mockRejectedValue(new Error('Preview failed'));
		const { fetchPreview } = await import('$lib/api/translate/datasources.js');
		await expect(fetchPreview('job-1')).rejects.toMatchObject({
			message: 'Preview failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('throws normalized error with default message when no message', async () => {
		mockPostApi.mockRejectedValue({});
		const { fetchPreview } = await import('$lib/api/translate/datasources.js');
		await expect(fetchPreview('job-1')).rejects.toMatchObject({
			message: 'Failed to run preview',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('sends POST without envId when envId is empty string', async () => {
		mockPostApi.mockResolvedValue({ rows: [] });
		const { fetchPreview } = await import('$lib/api/translate/datasources.js');
		await fetchPreview('job-1', 5, '');
		expect(mockPostApi).toHaveBeenCalledWith('/translate/jobs/job-1/preview', { sample_size: 5 });
	});
});

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

	it('sends PUT with action=approve', async () => {
		mockRequestApi.mockResolvedValue({ success: true });
		const { approveRow } = await import('$lib/api/translate/datasources.js');
		const result = await approveRow('job-1', 'row-1');
		expect(result).toEqual({ success: true });
		expect(mockRequestApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/preview/rows/row-1',
			'PUT',
			{ action: 'approve' }
		);
	});

	it('sends PUT with languageCode when provided', async () => {
		mockRequestApi.mockResolvedValue({ success: true });
		const { approveRow } = await import('$lib/api/translate/datasources.js');
		await approveRow('job-1', 'row-1', 'fr');
		expect(mockRequestApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/preview/rows/row-1',
			'PUT',
			{ action: 'approve', language_code: 'fr' }
		);
	});

	it('throws normalized error on failure', async () => {
		mockRequestApi.mockRejectedValue(new Error('Approve failed'));
		const { approveRow } = await import('$lib/api/translate/datasources.js');
		await expect(approveRow('job-1', 'row-1')).rejects.toMatchObject({
			message: 'Approve failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('sends PUT with action=edit and translation', async () => {
		mockRequestApi.mockResolvedValue({ updated: true });
		const { editRow } = await import('$lib/api/translate/datasources.js');
		const result = await editRow('job-1', 'row-1', 'Bonjour');
		expect(result).toEqual({ updated: true });
		expect(mockRequestApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/preview/rows/row-1',
			'PUT',
			{ action: 'edit', translation: 'Bonjour' }
		);
	});

	it('sends PUT with languageCode when provided', async () => {
		mockRequestApi.mockResolvedValue({ updated: true });
		const { editRow } = await import('$lib/api/translate/datasources.js');
		await editRow('job-1', 'row-1', 'Hola', 'es');
		expect(mockRequestApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/preview/rows/row-1',
			'PUT',
			{ action: 'edit', translation: 'Hola', language_code: 'es' }
		);
	});

	it('throws normalized error on failure', async () => {
		mockRequestApi.mockRejectedValue(new Error('Edit failed'));
		const { editRow } = await import('$lib/api/translate/datasources.js');
		await expect(editRow('job-1', 'row-1', 'test')).rejects.toMatchObject({
			message: 'Edit failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('sends PUT with action=reject', async () => {
		mockRequestApi.mockResolvedValue({ success: true });
		const { rejectRow } = await import('$lib/api/translate/datasources.js');
		const result = await rejectRow('job-1', 'row-1');
		expect(result).toEqual({ success: true });
		expect(mockRequestApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/preview/rows/row-1',
			'PUT',
			{ action: 'reject' }
		);
	});

	it('sends PUT with languageCode when provided', async () => {
		mockRequestApi.mockResolvedValue({ success: true });
		const { rejectRow } = await import('$lib/api/translate/datasources.js');
		await rejectRow('job-1', 'row-1', 'de');
		expect(mockRequestApi).toHaveBeenCalledWith(
			'/translate/jobs/job-1/preview/rows/row-1',
			'PUT',
			{ action: 'reject', language_code: 'de' }
		);
	});

	it('throws normalized error on failure', async () => {
		mockRequestApi.mockRejectedValue(new Error('Reject failed'));
		const { rejectRow } = await import('$lib/api/translate/datasources.js');
		await expect(rejectRow('job-1', 'row-1')).rejects.toMatchObject({
			message: 'Reject failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('throws normalized error with default message when error has no message', async () => {
		mockRequestApi.mockRejectedValue({});
		const { rejectRow } = await import('$lib/api/translate/datasources.js');
		await expect(rejectRow('job-1', 'row-1')).rejects.toMatchObject({
			message: 'Failed to reject row',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('sends POST to accept preview', async () => {
		mockPostApi.mockResolvedValue({ run_id: 'r-1', status: 'triggered' });
		const { acceptPreview } = await import('$lib/api/translate/datasources.js');
		const result = await acceptPreview('job-1');
		expect(result).toEqual({ run_id: 'r-1', status: 'triggered' });
		expect(mockPostApi).toHaveBeenCalledWith('/translate/jobs/job-1/preview/accept', {});
	});

	it('throws normalized error on failure', async () => {
		mockPostApi.mockRejectedValue(new Error('Accept failed'));
		const { acceptPreview } = await import('$lib/api/translate/datasources.js');
		await expect(acceptPreview('job-1')).rejects.toMatchObject({
			message: 'Accept failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('throws normalized error for string rejection', async () => {
		mockPostApi.mockRejectedValue('Forbidden');
		const { acceptPreview } = await import('$lib/api/translate/datasources.js');
		await expect(acceptPreview('job-1')).rejects.toMatchObject({
			message: 'Forbidden',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('throws normalized error with default message when no message', async () => {
		mockPostApi.mockRejectedValue({});
		const { acceptPreview } = await import('$lib/api/translate/datasources.js');
		await expect(acceptPreview('job-1')).rejects.toMatchObject({
			message: 'Failed to accept preview',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});

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

	it('sends GET to fetch preview records', async () => {
		const fixture = { records: [{ id: 'r-1', content: 'Hello' }] };
		mockFetchApi.mockResolvedValue(fixture);
		const { fetchPreviewRecords } = await import('$lib/api/translate/datasources.js');
		const result = await fetchPreviewRecords('session-1');
		expect(result).toEqual(fixture);
		expect(mockFetchApi).toHaveBeenCalledWith('/translate/preview/session-1/records');
	});

	it('throws normalized error on failure', async () => {
		mockFetchApi.mockRejectedValue(new Error('Records fetch failed'));
		const { fetchPreviewRecords } = await import('$lib/api/translate/datasources.js');
		await expect(fetchPreviewRecords('session-1')).rejects.toMatchObject({
			message: 'Records fetch failed',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('throws normalized error with default message when no message', async () => {
		mockFetchApi.mockRejectedValue({});
		const { fetchPreviewRecords } = await import('$lib/api/translate/datasources.js');
		await expect(fetchPreviewRecords('session-1')).rejects.toMatchObject({
			message: 'Failed to fetch preview records',
			code: 'TRANSLATE_API_ERROR',
		});
	});

	it('throws normalized error for string rejection', async () => {
		mockFetchApi.mockRejectedValue('Forbidden');
		const { fetchPreviewRecords } = await import('$lib/api/translate/datasources.js');
		await expect(fetchPreviewRecords('session-1')).rejects.toMatchObject({
			message: 'Forbidden',
			code: 'TRANSLATE_API_ERROR',
		});
	});
});
// #endregion TranslateDatasourcesApiTest
