/**
 * Unit tests for virusScanService — fail-closed behaviour (C-04)
 *
 * After the fix, a scan error must return { isClean: false, scanUnavailable: true }
 * instead of silently accepting the file.
 */

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

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

const mockIsInfected = jest.fn();

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

describe('virusScanService — fail-closed (C-04)', () => {
	it('returns isClean:false with scanUnavailable:true when scanner throws', async () => {
		mockIsInfected.mockRejectedValueOnce(new Error('daemon timeout'));

		const result: ScanResult = await scanFile('/tmp/dangerous.zip');

		expect(result.isClean).toBe(false);
		expect(result.scanUnavailable).toBe(true);
		expect(result.error).toBe('daemon timeout');
	});

	it('sets scanUnavailable:true even without an error message', async () => {
		mockIsInfected.mockRejectedValueOnce(null);

		const result: ScanResult = await scanFile('/tmp/file.bin');

		expect(result.isClean).toBe(false);
		expect(result.scanUnavailable).toBe(true);
	});

	it('returns isClean:false for an infected file (existing behavior preserved)', 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');
		expect(result.scanUnavailable).toBeUndefined();
	});

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

		const result = await scanFile('/tmp/clean.jpg');

		expect(result.isClean).toBe(true);
		expect(result.scanUnavailable).toBeUndefined();
	});

	it('does NOT silently accept a file when scanner fails (regression for C-04)', async () => {
		// Simulates zip-bomb causing scanner crash
		mockIsInfected.mockRejectedValueOnce(new Error('out of memory'));

		const result = await scanFile('/tmp/zipbomb.zip');

		// OLD (broken) behaviour was: result.isClean === true
		// NEW (fixed) behaviour: fail-closed
		expect(result.isClean).toBe(false);
	});
});

describe('virusScanService — disabled (VIRUS_SCAN_ENABLED not set)', () => {
	beforeEach(() => {
		delete process.env.VIRUS_SCAN_ENABLED;
		_resetForTesting();
	});

	it('returns isClean:true immediately without contacting scanner', async () => {
		const result = await scanFile('/any/path.exe');
		expect(result.isClean).toBe(true);
		expect(mockIsInfected).not.toHaveBeenCalled();
	});
});
