import { vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import { faker } from '@faker-js/faker';

import { useFoldersStore } from '@/stores/folders.store';
import * as workflowsApi from '@/api/workflows';
import type { ChangeLocationSearchResponseItem, IUsedCredential } from '@/Interface';
import { useRootStore } from '@n8n/stores/useRootStore';

vi.mock('@/utils/apiUtils', () => ({
	makeRestApiRequest: vi.fn(),
}));

const createFolder = (
	overrides: Partial<ChangeLocationSearchResponseItem> = {},
): ChangeLocationSearchResponseItem => ({
	createdAt: faker.date.recent().toISOString(),
	updatedAt: faker.date.recent().toISOString(),
	id: faker.string.alphanumeric(10),
	name: faker.lorem.words(3),
	tags: [],
	parentFolder: {
		id: faker.string.alphanumeric(10),
		name: faker.lorem.words(2),
		parentFolderId: null,
	},
	workflowCount: 2,
	subFolderCount: 2,
	path: [faker.lorem.word(), faker.lorem.word()],
	...overrides,
});

describe('folders.store', () => {
	let foldersStore: ReturnType<typeof useFoldersStore>;
	let rootStore: ReturnType<typeof useRootStore>;

	beforeEach(() => {
		setActivePinia(createPinia());
		rootStore = useRootStore();
		foldersStore = useFoldersStore();
	});

	describe('fetchFoldersAvailableForMove', () => {
		const projectId = faker.string.alphanumeric(10);
		const folderId = faker.string.alphanumeric(10);
		const selectFields = [
			'id',
			'name',
			'createdAt',
			'updatedAt',
			'project',
			'tags',
			'parentFolder',
			'workflowCount',
			'subFolderCount',
			'path',
		];

		it('should fetch folders in a single page, empty filter', async () => {
			const folder = createFolder();
			vi.spyOn(workflowsApi, 'getProjectFolders').mockResolvedValue({
				count: 1,
				data: [folder],
			});

			const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId);
			expect(available).toEqual([{ ...folder, resource: 'folder' }]);
			expect(workflowsApi.getProjectFolders).toHaveBeenCalledTimes(1);
			expect(workflowsApi.getProjectFolders).toHaveBeenCalledWith(
				rootStore.restApiContext,
				projectId,
				{
					skip: 0,
					take: 100,
					sortBy: 'updatedAt:desc',
				},
				{
					excludeFolderIdAndDescendants: folderId,
				},
				selectFields,
			);
		});

		it('should fetch folders in a single page with filter', async () => {
			const folder = createFolder({ name: 'Test Folder' });
			vi.spyOn(workflowsApi, 'getProjectFolders').mockResolvedValue({
				count: 1,
				data: [folder],
			});

			const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId, {
				name: 'Test',
			});
			expect(available).toEqual([{ ...folder, resource: 'folder' }]);
			expect(workflowsApi.getProjectFolders).toHaveBeenCalledTimes(1);
			expect(workflowsApi.getProjectFolders).toHaveBeenCalledWith(
				rootStore.restApiContext,
				projectId,
				{
					skip: 0,
					take: 100,
					sortBy: 'updatedAt:desc',
				},
				{
					excludeFolderIdAndDescendants: folderId,
					name: 'Test',
				},
				selectFields,
			);
		});

		it('should fetch folders in multiple pages', async () => {
			const folders = Array.from({ length: 150 }, (_, i) =>
				createFolder({ name: `Folder ${i + 1}` }),
			);
			vi.spyOn(workflowsApi, 'getProjectFolders')
				.mockResolvedValueOnce({
					count: 150,
					data: folders.slice(0, 100),
				})
				.mockResolvedValueOnce({
					count: 150,
					data: folders.slice(100),
				});

			const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId, {
				name: 'Test',
			});

			expect(available).toHaveLength(150);
			expect(workflowsApi.getProjectFolders).toHaveBeenCalledTimes(2);
			expect(workflowsApi.getProjectFolders).toHaveBeenNthCalledWith(
				1,
				rootStore.restApiContext,
				projectId,
				{
					skip: 0,
					take: 100,
					sortBy: 'updatedAt:desc',
				},
				{
					excludeFolderIdAndDescendants: folderId,
					name: 'Test',
				},
				selectFields,
			);
			expect(workflowsApi.getProjectFolders).toHaveBeenNthCalledWith(
				2,
				rootStore.restApiContext,
				projectId,
				{
					skip: 100,
					take: 100,
					sortBy: 'updatedAt:desc',
				},
				{
					excludeFolderIdAndDescendants: folderId,
					name: 'Test',
				},
				selectFields,
			);
		});

		it('should cache the results on breadcrumbs cache', async () => {
			const folder = createFolder();
			vi.spyOn(workflowsApi, 'getProjectFolders').mockResolvedValue({
				count: 1,
				data: [folder],
			});

			const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId);
			expect(available).toEqual([{ ...folder, resource: 'folder' }]);
			expect(foldersStore.breadcrumbsCache).toEqual({
				[folder.id]: {
					id: folder.id,
					name: folder.name,
					parentFolder: folder.parentFolder?.id,
				},
			});
		});
	});

	describe('fetchFolderUsedCredentials', () => {
		const projectId = faker.string.alphanumeric(10);
		const folderId = faker.string.alphanumeric(10);

		it('should fetch credentials used within a folder', async () => {
			const usedCredential: IUsedCredential = {
				id: faker.string.alphanumeric(10),
				name: faker.lorem.words(2),
				credentialType: faker.lorem.word(),
				currentUserHasAccess: true,
				sharedWithProjects: [],
			};

			vi.spyOn(workflowsApi, 'getFolderUsedCredentials').mockResolvedValue([usedCredential]);

			const credentials = await foldersStore.fetchFolderUsedCredentials(projectId, folderId);

			expect(credentials).toEqual([usedCredential]);
			expect(workflowsApi.getFolderUsedCredentials).toHaveBeenCalledWith(
				rootStore.restApiContext,
				projectId,
				folderId,
			);
		});
	});
});
