/**
 * virusScanService unit tests
 *
 * clamscan is an optional runtime dependency that is NOT installed in this repo.
 * Tests inject a mock ClamAV instance directly via _setInstanceForTesting() so
 * no dynamic-import mocking is needed.
 */

// Mock Config before importing the module
jest.mock('#config', () => ({
	virusScan: {
		enabled: false,
		failOpen: false,
		clamSocket: '',
		clamHost: '127.0.0.1',
		clamPort: 3310,
		clamScanTimeoutMs: 15000,
	},
}));

import { scanFile, _resetForTesting, _setInstanceForTesting } from '#services/file-system/virusScanService';

const mockIsInfected = jest.fn();

beforeEach(() => {
	mockIsInfected.mockReset();
	_resetForTesting();
});

describe('virusScanService', () => {
	describe('when VIRUS_SCAN_ENABLED is not set (default)', () => {
		it('returns isClean:true without contacting ClamAV', async () => {
			const result = await scanFile('/tmp/test-file.jpg');

			expect(result.isClean).toBe(true);
			expect(result.virusName).toBeUndefined();
			expect(result.error).toBeUndefined();
		});

		it('returns isClean:true for any file path', async () => {
			const result = await scanFile('/any/path.exe');
			expect(result.isClean).toBe(true);
		});
	});

	describe('when VIRUS_SCAN_ENABLED=true but ClamAV is unavailable', () => {
		beforeEach(() => {
			// Update the mock config to enable virus scan
			const mockConfig = require('#config');
			mockConfig.virusScan.enabled = true;
		});

		it('degrades gracefully and returns isClean:true (clamscan not installed)', async () => {
			// _clamav is null (from beforeEach) and clamscan is not installed;
			// getClamAV() will catch the import error and return null.
			const result = await scanFile('/tmp/file.pdf');
			expect(result.isClean).toBe(true);
		});
	});

	describe('when VIRUS_SCAN_ENABLED=true and ClamAV is available', () => {
		beforeEach(() => {
			// Update the mock config to enable virus scan
			const mockConfig = require('#config');
			mockConfig.virusScan.enabled = true;
			// Inject the mock clamscan instance directly — no dynamic import needed.
			_setInstanceForTesting({ isInfected: mockIsInfected });
		});

		it('returns isClean:true for a clean file', async () => {
			mockIsInfected.mockResolvedValueOnce({ isInfected: false, viruses: [] });

			const result = await scanFile('/tmp/clean.jpg');
			expect(result.isClean).toBe(true);
			expect(result.virusName).toBeUndefined();
		});

		it('returns isClean:false with virusName for an infected file', async () => {
			mockIsInfected.mockResolvedValueOnce({
				isInfected: true,
				viruses: ['Eicar-Test-Signature'],
			});

			const result = await scanFile('/tmp/infected.exe');
			expect(result.isClean).toBe(false);
			expect(result.virusName).toBe('Eicar-Test-Signature');
		});

		it('uses "unknown" as virusName when virus list is empty', async () => {
			mockIsInfected.mockResolvedValueOnce({ isInfected: true, viruses: [] });

			const result = await scanFile('/tmp/infected.bin');
			expect(result.isClean).toBe(false);
			expect(result.virusName).toBe('unknown');
		});

		it('returns isClean:false (fail-closed) with error field when isInfected() throws', async () => {
			mockIsInfected.mockRejectedValueOnce(new Error('daemon timeout'));

			const result = await scanFile('/tmp/file.zip');
			// C-04 fix: scanner errors must be fail-closed — do NOT accept the file
			expect(result.isClean).toBe(false);
			expect(result.error).toBe('daemon timeout');
			expect((result as any).scanUnavailable).toBe(true);
		});

		it('reuses the same ClamAV instance on successive calls', async () => {
			mockIsInfected
				.mockResolvedValueOnce({ isInfected: false, viruses: [] })
				.mockResolvedValueOnce({ isInfected: false, viruses: [] });

			await scanFile('/tmp/a.jpg');
			await scanFile('/tmp/b.jpg');

			// isInfected was called once per scanFile call (2 total)
			expect(mockIsInfected).toHaveBeenCalledTimes(2);
		});
	});
});
