import * as helpers from '../../../actions/file/helpers';
import { BASE_URL } from '../../../constants';
import * as transport from '../../../transport';
import { createMockExecuteFunction } from '../helpers';

const mockFileCreateResponse = {
	data: {
		id: 'file-123',
		uploadUrl: 'https://upload.example.com/url',
	},
};

const mockFileEvent = `
event: fileEvent
data: {"event":"file_upload_status","status":"available","fileId":"file-123"}`;

// Mock the transport and other dependencies
jest.mock('../../../transport', () => {
	const originalModule = jest.requireActual<typeof transport>('../../../transport');
	return {
		...originalModule,
		apiRequest: jest.fn(async () => {}),
	};
});

describe('Test Airtop file helpers', () => {
	afterAll(() => {
		jest.unmock('../../../transport');
	});

	afterEach(() => {
		jest.clearAllMocks();
	});

	describe('requestAllFiles', () => {
		it('should request all files with pagination', async () => {
			const apiRequestMock = transport.apiRequest as jest.Mock;
			const mockFilesResponse1 = {
				data: {
					files: [{ id: 'file-1' }, { id: 'file-2' }],
					pagination: { hasMore: true },
				},
			};

			const mockFilesResponse2 = {
				data: {
					files: [{ id: 'file-3' }],
					pagination: { hasMore: false },
				},
			};

			apiRequestMock
				.mockResolvedValueOnce(mockFilesResponse1)
				.mockResolvedValueOnce(mockFilesResponse2);

			const result = await helpers.requestAllFiles.call(
				createMockExecuteFunction({}),
				'session-123',
			);

			expect(apiRequestMock).toHaveBeenCalledTimes(2);
			expect(apiRequestMock).toHaveBeenNthCalledWith(
				1,
				'GET',
				'/files',
				{},
				{ offset: 0, limit: 100, sessionIds: 'session-123' },
			);
			expect(apiRequestMock).toHaveBeenNthCalledWith(
				2,
				'GET',
				'/files',
				{},
				{ offset: 100, limit: 100, sessionIds: 'session-123' },
			);

			expect(result).toEqual({
				data: {
					files: [{ id: 'file-1' }, { id: 'file-2' }, { id: 'file-3' }],
					pagination: { hasMore: false },
				},
			});
		});

		it('should handle empty response', async () => {
			const apiRequestMock = transport.apiRequest as jest.Mock;
			const mockEmptyResponse = {
				data: {
					files: [],
					pagination: { hasMore: false },
				},
			};

			apiRequestMock.mockResolvedValueOnce(mockEmptyResponse);

			const result = await helpers.requestAllFiles.call(
				createMockExecuteFunction({}),
				'session-123',
			);

			expect(apiRequestMock).toHaveBeenCalledTimes(1);
			expect(result).toEqual({
				data: {
					files: [],
					pagination: { hasMore: false },
				},
			});
		});
	});

	describe('pollFileUntilAvailable', () => {
		it('should poll until file is available', async () => {
			const apiRequestMock = transport.apiRequest as jest.Mock;
			apiRequestMock
				.mockResolvedValueOnce({ data: { status: 'uploading' } })
				.mockResolvedValueOnce({ data: { status: 'available' } });

			const pollPromise = helpers.pollFileUntilAvailable.call(
				createMockExecuteFunction({}),
				'file-123',
				1000,
				0,
			);

			const result = await pollPromise;

			expect(apiRequestMock).toHaveBeenCalledTimes(2);
			expect(apiRequestMock).toHaveBeenCalledWith('GET', '/files/file-123');
			expect(result).toBe('file-123');
		});

		it('should throw timeout error if file never becomes available', async () => {
			const apiRequestMock = transport.apiRequest as jest.Mock;
			apiRequestMock.mockResolvedValue({ data: { status: 'processing' } });

			const promise = helpers.pollFileUntilAvailable.call(
				createMockExecuteFunction({}),
				'file-123',
				0,
			);

			await expect(promise).rejects.toThrow();
		});
	});

	describe('createAndUploadFile', () => {
		it('should create file entry, upload file, and poll until available', async () => {
			const apiRequestMock = transport.apiRequest as jest.Mock;
			apiRequestMock
				.mockResolvedValueOnce(mockFileCreateResponse)
				.mockResolvedValueOnce({ data: { status: 'available' } });

			const mockExecuteFunction = createMockExecuteFunction({});
			const mockHttpRequest = jest.fn().mockResolvedValueOnce({});
			mockExecuteFunction.helpers.httpRequest = mockHttpRequest;
			const pollingFunctionMock = jest.fn().mockResolvedValueOnce(mockFileCreateResponse.data.id);

			const result = await helpers.createAndUploadFile.call(
				mockExecuteFunction,
				'test.png',
				Buffer.from('test'),
				'customer_upload',
				pollingFunctionMock,
			);

			expect(apiRequestMock).toHaveBeenCalledWith('POST', '/files', {
				fileName: 'test.png',
				fileType: 'customer_upload',
			});

			expect(mockHttpRequest).toHaveBeenCalledWith({
				method: 'PUT',
				url: mockFileCreateResponse.data.uploadUrl,
				body: Buffer.from('test'),
				headers: {
					'Content-Type': 'application/octet-stream',
				},
			});

			expect(pollingFunctionMock).toHaveBeenCalledWith(mockFileCreateResponse.data.id);

			expect(result).toBe(mockFileCreateResponse.data.id);
		});

		it('should throw error if file creation response is missing id or upload URL', async () => {
			const apiRequestMock = transport.apiRequest as jest.Mock;
			apiRequestMock.mockResolvedValueOnce({});

			await expect(
				helpers.createAndUploadFile.call(
					createMockExecuteFunction({}),
					'test.pdf',
					Buffer.from('test'),
					'customer_upload',
				),
			).rejects.toThrow();
		});
	});

	describe('waitForFileInSession', () => {
		it('should resolve when file is available', async () => {
			// Create a mock stream
			const mockStream = {
				on: jest.fn().mockImplementation((event, callback) => {
					if (event === 'data') {
						callback(mockFileEvent);
					}
				}),
				removeAllListeners: jest.fn(),
			};

			const mockHttpRequestWithAuthentication = jest.fn().mockResolvedValueOnce(mockStream);
			const mockExecuteFunction = createMockExecuteFunction({});
			mockExecuteFunction.helpers.httpRequestWithAuthentication = mockHttpRequestWithAuthentication;

			await helpers.waitForFileInSession.call(mockExecuteFunction, 'session-123', 'file-123', 100);

			expect(mockHttpRequestWithAuthentication).toHaveBeenCalledWith('airtopApi', {
				method: 'GET',
				url: `${BASE_URL}/sessions/session-123/events?all=true`,
				encoding: 'stream',
			});

			expect(mockStream.removeAllListeners).toHaveBeenCalled();
		});

		it('should timeout if no event is received', async () => {
			// Create a mock stream
			const mockStream = {
				on: jest.fn().mockImplementation(() => {}),
				removeAllListeners: jest.fn(),
			};
			const mockHttpRequestWithAuthentication = jest.fn().mockResolvedValueOnce(mockStream);

			const mockExecuteFunction = createMockExecuteFunction({});
			mockExecuteFunction.helpers.httpRequestWithAuthentication = mockHttpRequestWithAuthentication;

			const waitPromise = helpers.waitForFileInSession.call(
				mockExecuteFunction,
				'session-123',
				'file-123',
				100,
			);

			await expect(waitPromise).rejects.toThrow();
		});
	});

	describe('pushFileToSession', () => {
		it('should push file to session and wait', async () => {
			const apiRequestMock = transport.apiRequest as jest.Mock;
			const mockFileId = 'file-123';
			const mockSessionId = 'session-123';
			apiRequestMock.mockResolvedValueOnce({});

			// Mock waitForFileInSession
			const waitForFileInSessionMock = jest.fn().mockResolvedValueOnce({});

			// Call the function
			await helpers.pushFileToSession.call(
				createMockExecuteFunction({}),
				mockFileId,
				mockSessionId,
				waitForFileInSessionMock,
			);

			expect(apiRequestMock).toHaveBeenCalledWith('POST', `/files/${mockFileId}/push`, {
				sessionIds: [mockSessionId],
			});

			expect(waitForFileInSessionMock).toHaveBeenCalledWith(mockSessionId, mockFileId);
		});
	});

	describe('triggerFileInput', () => {
		it('should trigger file input in window', async () => {
			const apiRequestMock = transport.apiRequest as jest.Mock;
			apiRequestMock.mockResolvedValueOnce({});
			const mockFileId = 'file-123';
			const mockWindowId = 'window-123';
			const mockSessionId = 'session-123';

			await helpers.triggerFileInput.call(
				createMockExecuteFunction({}),
				mockFileId,
				mockWindowId,
				mockSessionId,
			);

			expect(apiRequestMock).toHaveBeenCalledWith(
				'POST',
				`/sessions/${mockSessionId}/windows/${mockWindowId}/file-input`,
				{ fileId: mockFileId },
			);
		});
	});

	describe('createFileBuffer', () => {
		it('should create buffer from URL', async () => {
			const mockUrl = 'https://example.com/file.pdf';
			const mockBuffer = [1, 2, 3];

			// Mock http request
			const mockHttpRequest = jest.fn().mockResolvedValueOnce(mockBuffer);

			// Create mock execute function with http request helper
			const mockExecuteFunction = createMockExecuteFunction({});
			mockExecuteFunction.helpers.httpRequest = mockHttpRequest;

			const result = await helpers.createFileBuffer.call(mockExecuteFunction, 'url', mockUrl, 0);

			expect(mockHttpRequest).toHaveBeenCalledWith({
				url: mockUrl,
				json: false,
				encoding: 'arraybuffer',
			});
			expect(result).toBe(mockBuffer);
		});

		it('should create buffer from binary data', async () => {
			const mockBinaryPropertyName = 'data';
			const mockBuffer = [1, 2, 3];

			// Mock getBinaryDataBuffer
			const mockGetBinaryDataBuffer = jest.fn().mockResolvedValue(mockBuffer);

			// Create mock execute function with getBinaryDataBuffer helper
			const mockExecuteFunction = createMockExecuteFunction({});
			mockExecuteFunction.helpers.getBinaryDataBuffer = mockGetBinaryDataBuffer;

			const result = await helpers.createFileBuffer.call(
				mockExecuteFunction,
				'binary',
				mockBinaryPropertyName,
				0,
			);

			expect(mockGetBinaryDataBuffer).toHaveBeenCalledWith(0, mockBinaryPropertyName);
			expect(result).toBe(mockBuffer);
		});

		it('should throw error for unsupported source type', async () => {
			await expect(
				helpers.createFileBuffer.call(
					createMockExecuteFunction({}),
					'invalid-source',
					'test-value',
					0,
				),
			).rejects.toThrow();
		});
	});
});
