import { LicenseState } from '@n8n/backend-common';
import { createWorkflow, shareWorkflowWithUsers, testDb } from '@n8n/backend-test-utils';
import { GLOBAL_MEMBER_ROLE, GLOBAL_OWNER_ROLE, type User } from '@n8n/db';
import { Container } from '@n8n/di';
import { mock } from 'vitest-mock-extended';

import { ProjectService } from '@/services/project.service.ee';
import { WorkflowSharingService } from '@/workflows/workflow-sharing.service';

import { createUser } from '../shared/db/users';

let owner: User;
let member: User;
let anotherMember: User;
let workflowSharingService: WorkflowSharingService;
let projectService: ProjectService;

beforeAll(async () => {
	await testDb.init();
	owner = await createUser({ role: GLOBAL_OWNER_ROLE });
	member = await createUser({ role: GLOBAL_MEMBER_ROLE });
	anotherMember = await createUser({ role: GLOBAL_MEMBER_ROLE });
	const licenseMock = mock<LicenseState>();
	licenseMock.isSharingLicensed.mockReturnValue(true);
	licenseMock.getMaxTeamProjects.mockReturnValue(-1);
	Container.set(LicenseState, licenseMock);
	workflowSharingService = Container.get(WorkflowSharingService);
	projectService = Container.get(ProjectService);
});

beforeEach(async () => {
	await testDb.truncate(['WorkflowEntity', 'SharedWorkflow', 'WorkflowHistory']);
});

afterAll(async () => {
	await testDb.terminate();
});

describe('WorkflowSharingService', () => {
	describe('getSharedWorkflowIds', () => {
		it('should show all workflows to owners', async () => {
			const workflow1 = await createWorkflow({}, member);
			const workflow2 = await createWorkflow({}, anotherMember);
			const sharedWorkflowIds = await workflowSharingService.getSharedWorkflowIds(owner, {
				scopes: ['workflow:read'],
			});
			expect(sharedWorkflowIds).toHaveLength(2);
			expect(sharedWorkflowIds).toContain(workflow1.id);
			expect(sharedWorkflowIds).toContain(workflow2.id);
		});

		it('should show shared workflows to users', async () => {
			const workflow1 = await createWorkflow({}, anotherMember);
			const workflow2 = await createWorkflow({}, anotherMember);
			const workflow3 = await createWorkflow({}, anotherMember);
			await shareWorkflowWithUsers(workflow1, [member]);
			await shareWorkflowWithUsers(workflow3, [member]);
			const sharedWorkflowIds = await workflowSharingService.getSharedWorkflowIds(member, {
				scopes: ['workflow:read'],
			});
			expect(sharedWorkflowIds).toHaveLength(2);
			expect(sharedWorkflowIds).toContain(workflow1.id);
			expect(sharedWorkflowIds).toContain(workflow3.id);
			expect(sharedWorkflowIds).not.toContain(workflow2.id);
		});

		it('should show workflows that the user has access to through a team project they are part of', async () => {
			//
			// ARRANGE
			//
			const project = await projectService.createTeamProject(member, { name: 'Team Project' });
			await projectService.addUser(project.id, { userId: anotherMember.id, role: 'project:admin' });
			const workflow = await createWorkflow(undefined, project);

			//
			// ACT
			//
			const sharedWorkflowIds = await workflowSharingService.getSharedWorkflowIds(anotherMember, {
				scopes: ['workflow:read'],
			});

			//
			// ASSERT
			//
			expect(sharedWorkflowIds).toContain(workflow.id);
		});

		it('should show workflows that the user has update access to', async () => {
			//
			// ARRANGE
			//
			const project1 = await projectService.createTeamProject(member, { name: 'Team Project 1' });
			const workflow1 = await createWorkflow(undefined, project1);
			const project2 = await projectService.createTeamProject(member, { name: 'Team Project 2' });
			const workflow2 = await createWorkflow(undefined, project2);
			await projectService.addUser(project1.id, {
				userId: anotherMember.id,
				role: 'project:admin',
			});
			await projectService.addUser(project2.id, {
				userId: anotherMember.id,
				role: 'project:viewer',
			});

			//
			// ACT
			//
			const sharedWorkflowIds = await workflowSharingService.getSharedWorkflowIds(anotherMember, {
				scopes: ['workflow:update'],
			});

			//
			// ASSERT
			//
			expect(sharedWorkflowIds).toContain(workflow1.id);
			expect(sharedWorkflowIds).not.toContain(workflow2.id);
		});
	});

	describe('getUserIdsWithAccessToWorkflow', () => {
		it('includes the global owner and the owning member, and excludes an unrelated member', async () => {
			const workflow = await createWorkflow({}, member);

			const userIds = await workflowSharingService.getUserIdsWithAccessToWorkflow(workflow.id);

			expect(userIds).toContain(owner.id);
			expect(userIds).toContain(member.id);
			expect(userIds).not.toContain(anotherMember.id);
		});

		it('excludes a project member whose role does not grant workflow:read', async () => {
			const project = await projectService.createTeamProject(member, { name: 'Team Project' });
			const workflow = await createWorkflow(undefined, project);
			await projectService.addUser(project.id, {
				userId: anotherMember.id,
				role: 'project:chatUser',
			});

			const userIds = await workflowSharingService.getUserIdsWithAccessToWorkflow(workflow.id);

			expect(userIds).toContain(member.id);
			expect(userIds).not.toContain(anotherMember.id);
		});

		it('includes a project member whose role grants workflow:read', async () => {
			const project = await projectService.createTeamProject(member, { name: 'Team Project' });
			const workflow = await createWorkflow(undefined, project);
			await projectService.addUser(project.id, {
				userId: anotherMember.id,
				role: 'project:viewer',
			});

			const userIds = await workflowSharingService.getUserIdsWithAccessToWorkflow(workflow.id);

			expect(userIds).toContain(anotherMember.id);
		});
	});

	describe('rolesGrantingScope', () => {
		it('should return no options for users holding the scope globally', async () => {
			const options = await workflowSharingService.rolesGrantingScope(owner, 'workflow:read');

			expect(options).toBeUndefined();
		});

		it('should return the roles granting the scope for other users', async () => {
			const options = await workflowSharingService.rolesGrantingScope(member, 'workflow:read');

			expect(options?.projectRoles).toContain('project:viewer');
			expect(options?.workflowRoles).toContain('workflow:owner');
		});

		it('should return only the roles granting the requested scope', async () => {
			const options = await workflowSharingService.rolesGrantingScope(member, 'workflow:update');

			expect(options?.projectRoles).not.toContain('project:viewer');
			expect(options?.projectRoles).toContain('project:admin');
		});
	});
});
