/**
* Copyright Super iPaaS Integration LLC, an IBM Company 2024
*/
import fs, {existsSync, statSync, readFileSync, accessSync} from 'fs-extra';
import path, {join} from 'path';
import crypto from 'crypto';
import {hasReadAccess,
	isDirectory,
	isDirOrFileExists,
	isFile,
	isSubDirectoryExists,
	readFile,
	readFileAsBuffer,
	isYamlFile,
	isJsonFile,
	isOtherFile,
	getRandomFileName,
	getSubDirectory,
	writeJsonToFile,
	readJsonFromFile,
	checkFileExists,
	ensureDirectoryExists,
	readFileAsString,
	readDirectoryContents,
	getFileNameFromPath,
	createBuildZip,
	normalizePath,
	getParentDir
} from './fs-helper';


jest.mock('fs');
jest.mock('fs-extra');

jest.mock('path');

jest.mock('crypto', () => ({
	randomUUID: jest.fn(),
}));


jest.mock('./message-helper', () => ({
	__esModule: true,
	showError: jest.fn()
}));


describe('FS Helper test suite', () => {

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

	it('should return true if file exists', () => {
		(existsSync as jest.Mock).mockReturnValue(true);
		expect(isDirOrFileExists('sample path')).toBe(true);
	});

	it('should return false if file does not exists', () => {
		(existsSync as jest.Mock).mockReturnValue(false);
		expect(isDirOrFileExists('sample path')).toBe(false);
	});

	it('should return false if the given path is not directory', () => {
		(existsSync as jest.Mock).mockReturnValue(true);
		(statSync as jest.Mock).mockReturnValue({
			isDirectory: () => false
		});
		expect(isDirectory('sample path')).toBe(false);
	});

	it('should return true if the given path is directory', () => {
		(existsSync as jest.Mock).mockReturnValue(true);
		(statSync as jest.Mock).mockReturnValue({
			isDirectory: () => true
		});
		expect(isDirectory('sample path')).toBe(true);
	});

	it('should return false if the given path does not exists', () => {
		(existsSync as jest.Mock).mockReturnValue(false);
		(statSync as jest.Mock).mockReturnValue({
			isDirectory: () => true
		});
		expect(isDirectory('sample path')).toBe(false);
	});

	it('should return false if sub-directory does not exists', () => {
		(existsSync as jest.Mock).mockReturnValue(true);
		(statSync as jest.Mock).mockReturnValue({
			isDirectory: () => false
		});
		(join as jest.Mock).mockReturnValue('');
		expect(isSubDirectoryExists('sample path', 'test')).toBe(false);
	});

	it('should return true if sub-directory does exists', () => {
		(existsSync as jest.Mock).mockReturnValue(true);
		(statSync as jest.Mock).mockReturnValue({
			isDirectory: () => true
		});
		(join as jest.Mock).mockReturnValue('');
		expect(isSubDirectoryExists('sample path', 'test')).toBe(true);
	});

	it('should return true if the given path is a file', () => {
		(existsSync as jest.Mock).mockReturnValue(true);
		(statSync as jest.Mock).mockReturnValue({
			isDirectory: () => false,
			isFile: () => true
		});
		expect(isFile('sample path')).toBe(true);
	});

	it('should return false if the given path is not a file', () => {
		(existsSync as jest.Mock).mockReturnValue(true);
		(statSync as jest.Mock).mockReturnValue({
			isDirectory: () => false,
			isFile: () => false
		});
		expect(isFile('sample path')).toBe(false);
	});

	it('should return false if the given path does not exists', () => {
		(existsSync as jest.Mock).mockReturnValue(false);
		(statSync as jest.Mock).mockReturnValue({
			isDirectory: () => false,
			isFile: () => true
		});
		expect(isFile('sample path')).toBe(false);
	});

	it('should return content of the file', () => {
		const fileContent = 'File content';
		(existsSync as jest.Mock).mockReturnValue(true);
		(statSync as jest.Mock).mockReturnValue({
			isDirectory: () => false,
			isFile: () => true
		});
		(join as jest.Mock).mockReturnValue('');
		(readFileSync as jest.Mock).mockReturnValue(Buffer.alloc(fileContent.length, fileContent));
		expect(readFile('dir path', 'filename')).toBe(fileContent);
	});

	it('should return true if access permission is available', () => {
		(accessSync as jest.Mock).mockReturnValue(true);
		expect(hasReadAccess('sample path')).toBe(true);
	});

	it('should return false if access permission is not available', () => {
		jest.spyOn(fs, 'accessSync').mockImplementation(() => {
			throw new Error('test');
		});
		expect(hasReadAccess('sample path')).toBe(false);
	});

	it('should return file content as buffer', () => {
		expect(readFileAsBuffer('sample path')).toBeInstanceOf(Buffer);
	});
	it('should generate a random file name with default extension', () => {
		(crypto.randomUUID as jest.Mock).mockReturnValue('random-uuid');
		const fileName = getRandomFileName();
		expect(fileName).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.yml$/i);
	});

	it('should return a random file name with provided extension', () => {
		(crypto.randomUUID as jest.Mock).mockReturnValue('random-uuid');
		const fileName = getRandomFileName('.txt');
		expect(fileName).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.txt$/i);
	});
	it('should add a dot to the extension if not provided', () => {
		(crypto.randomUUID as jest.Mock).mockReturnValue('random-uuid');
		const fileName = getRandomFileName('txt');
		expect(fileName).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.txt$/i);
	});

	it('should return true for .yml files', () => {
		expect(isYamlFile('file.yml')).toBe(true);
	});

	it('should return true for .yaml files', () => {
		expect(isYamlFile('file.yaml')).toBe(true);
	});

	it('should return false for non-yaml files', () => {
		expect(isYamlFile('file.txt')).toBe(false);
	});
	it('should return yaml file as false for null', () => {
		expect(isYamlFile(null as unknown as string)).toBe(false);
	});
	
	it('should return yaml file as false for undefined', () => {
		expect(isYamlFile(undefined as unknown as string)).toBe(false);
	});

	it('should return true for .json files', () => {
		expect(isJsonFile('file.json')).toBe(true);
	});

	it('should return false for non-json files', () => {
		expect(isJsonFile('file.txt')).toBe(false);
	});
	it('should return json file as false for null', () => {
		expect(isJsonFile(null as unknown as string)).toBe(false);
	});
	
	it('should return json file as false for undefined', () => {
		expect(isJsonFile(undefined as unknown as string)).toBe(false);
	});

	it('should return true for .txt files', () => {
		expect(isOtherFile('file.txt')).toBe(true);
	});

	it('should return other file as false for json files', () => {
		expect(isOtherFile('file.json')).toBe(false);
	});
	it('should return other file as false for null', () => {
		expect(isOtherFile(null as unknown as string)).toBe(false);
	});
	
	it('should return other file as false for undefined', () => {
		expect(isOtherFile(undefined as unknown as string)).toBe(false);
	});

	it('should throw an error if the subdirectory does not exist', () => {
		jest.spyOn({ isSubDirectoryExists }, 'isSubDirectoryExists').mockReturnValue(false);
		const parentDirPath = '/parent/dir';
		const folderName = 'subfolder';
		const errorMessage = `The folder '${folderName}' is invalid or does not exist inside parent directory '${parentDirPath}'`;
		expect(() => getSubDirectory(parentDirPath, folderName)).toThrowError(errorMessage);
	});

	it('should return the file name from the given path', () => {
		const filePath = 'path/to/file.txt';
		const expectedFileName = 'file.txt';
		(path.basename as jest.Mock).mockReturnValue(expectedFileName);
		const result = getFileNameFromPath(filePath);
		expect(result).toBe(expectedFileName);
	  });

	  it('should create build zip and return true', async () => {
		const fileContent = 'File content';
		const buffer = Buffer.alloc(fileContent.length, fileContent);
		const folderPath = 'path/to/directory/build.zip';	
		const result = await createBuildZip(buffer, folderPath);
		expect(result).toBe(true);
	});

	it('should return false if the file path do not .zip', async () => {
		const fileContent = 'File content';
		const buffer = Buffer.alloc(fileContent.length, fileContent);
		const folderPath = 'path/to/directory/build';	
		const result = await createBuildZip(buffer, folderPath);
		expect(result).toBe(false);
	});

	describe('writeJsonToFile', () => {
		it('should write JSON to file with the correct options', async () => {
			const filePath = 'path/to/file.json';
			const data = { key: 'value' };
			const options = { spaces: 2 };
		
			await writeJsonToFile(filePath, data, options);
		
			expect(fs.writeJson).toHaveBeenCalledWith(filePath, data, options);
		});
		
		it('should throw an error if writing fails', async () => {
			const filePath = 'path/to/file.json';
			const data = { key: 'value' };
			(fs.writeJson as jest.Mock).mockRejectedValue(new Error('Write failed'));
		
			await expect(writeJsonToFile(filePath, data)).rejects.toThrow('Failed to write JSON to file: Write failed');
		});
	});
	describe('readJsonFromFile', () => {
		const mockFilePath = 'mock/file/path.json';
		const mockJsonData = { key: 'value' };
		it('should read JSON data from a file successfully', async () => {
			(fs.readJson as jest.Mock).mockResolvedValue(mockJsonData);
	
			const result = await readJsonFromFile(mockFilePath);
	
			expect(fs.readJson).toHaveBeenCalledWith(mockFilePath);
			expect(result).toEqual(mockJsonData);
		});
	
		it('should throw an error if reading JSON from the file fails', async () => {
			const errorMessage = 'mock error message';
			(fs.readJson as jest.Mock).mockRejectedValue(new Error(errorMessage));
	
			await expect(readJsonFromFile(mockFilePath)).rejects.toThrow(`${errorMessage}`);
		});
	});

	describe('checkFileExists', () => {
		it('should return true if the file exists', async () => {
			const filePath = 'path/to/file.json';
			(fs.pathExists as jest.Mock).mockResolvedValue(true);
		
			const result = await checkFileExists(filePath);
			expect(result).toBe(true);
			expect(fs.pathExists).toHaveBeenCalledWith(filePath);
		});
		
		it('should return false if the file does not exist', async () => {
			const filePath = 'path/to/file.json';
			(fs.pathExists as jest.Mock).mockResolvedValue(false);
		
			const result = await checkFileExists(filePath);
			expect(result).toBe(false);
		});
		
		it('should throw an error if checking fails', async () => {
			const filePath = 'path/to/file.json';
			(fs.pathExists as jest.Mock).mockRejectedValue(new Error('Check failed'));
		
			await expect(checkFileExists(filePath)).rejects.toThrow('Failed to check if file exists: Check failed');
		});
	});

	describe('ensureDirectoryExists', () => {
		it('should ensure the directory exists', async () => {
			const dirPath = 'path/to/dir';
		
			await ensureDirectoryExists(dirPath);
		
			expect(fs.ensureDir).toHaveBeenCalledWith(dirPath);
		});
		
		it('should throw an error if directory creation fails', async () => {
			const dirPath = 'path/to/dir';
			(fs.ensureDir as jest.Mock).mockRejectedValue(new Error('Ensure dir failed'));
		
			await expect(ensureDirectoryExists(dirPath)).rejects.toThrow('Failed to ensure directory exists: Ensure dir failed');
		});
	});
	describe('readFileAsString', () => {
		it('should return the file content as a string', () => {
			const filePath = 'path/to/file.txt';
			const fileContent = 'File content here';
				
			(fs.existsSync as jest.Mock).mockReturnValue(true);
			(fs.readFileSync as jest.Mock).mockReturnValue(fileContent);
			
			const result = readFileAsString(filePath);
			
			expect(result).toBe(fileContent);
			expect(fs.existsSync).toHaveBeenCalledWith(filePath);
			expect(fs.readFileSync).toHaveBeenCalledWith(filePath, 'utf8');
		});
			
		it('should throw an error if the file does not exist', () => {
			const filePath = 'path/to/nonexistent-file.txt';
			(fs.existsSync as jest.Mock).mockReturnValue(false);
			
			expect(() => readFileAsString(filePath)).toThrow(`The file '${filePath}' does not exist`);
			expect(fs.existsSync).toHaveBeenCalledWith(filePath);
		});
			
		it('should throw an error if reading file fails', () => {
			const filePath = 'path/to/file.txt';
			(fs.existsSync as jest.Mock).mockReturnValue(true);
			(fs.readFileSync as jest.Mock).mockImplementation(() => {
				throw new Error('Failed to read file');
			});
			
			expect(() => readFileAsString(filePath)).toThrow('Failed to read file');
		});
	});

	describe('readDirectoryContents', () => {
		it('should return the list of files in the directory', () => {
			const folderPath = 'path/to/directory';
			const directoryContents = ['file1.txt', 'file2.txt'];
			(fs.readdirSync as jest.Mock).mockReturnValue(directoryContents);
			
			const result = readDirectoryContents(folderPath);
			
			expect(result).toEqual(directoryContents);
			expect(fs.readdirSync).toHaveBeenCalledWith(folderPath);
		});
			
		it('should throw an error if directory reading fails', () => {
			const folderPath = 'path/to/directory';
			(fs.readdirSync as jest.Mock).mockImplementation(() => {
				throw new Error('Failed to read directory');
			});
			
			expect(() => readDirectoryContents(folderPath)).toThrow('Failed to read directory');
		});
	});

});