import { createTeamProject, testDb, testModules } from '@n8n/backend-test-utils';
import {
	type Role,
	GLOBAL_MEMBER_ROLE,
	GLOBAL_OWNER_ROLE,
	ProjectRelationRepository,
	type Project,
	type User,
	PROJECT_ADMIN_ROLE,
	GLOBAL_ADMIN_ROLE,
} from '@n8n/db';
import { Container } from '@n8n/di';
import type { EntityManager } from '@n8n/typeorm';
import { mock } from 'jest-mock-extended';

import { createUser } from '@test-integration/db/users';

import { DataTableAggregateService } from '../data-table-aggregate.service';
import { DataTableService } from '../data-table.service';

beforeAll(async () => {
	await testModules.loadModules(['data-table']);
	await testDb.init();
});

beforeEach(async () => {
	await testDb.truncate(['DataTable', 'DataTableColumn']);
});

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

describe('dataTableAggregate', () => {
	let dataTableService: DataTableService;
	let dataTableAggregateService: DataTableAggregateService;
	const manager = mock<EntityManager>();
	const projectRelationRepository = mock<ProjectRelationRepository>({ manager });

	beforeAll(() => {
		Container.set(ProjectRelationRepository, projectRelationRepository);
		dataTableAggregateService = Container.get(DataTableAggregateService);
		dataTableService = Container.get(DataTableService);
	});

	let user: User;
	let project1: Project;
	let project2: Project;

	beforeEach(async () => {
		project1 = await createTeamProject();
		project2 = await createTeamProject();
		user = await createUser({ role: GLOBAL_OWNER_ROLE });
	});

	afterEach(async () => {
		// Clean up any created user data tables
		await dataTableService.deleteDataTableAll();
	});

	describe('getManyAndCount', () => {
		it('should return the correct data tables for the user', async () => {
			// ARRANGE
			const ds1 = await dataTableService.createDataTable(project1.id, {
				name: 'dataTable1',
				columns: [],
			});
			const ds2 = await dataTableService.createDataTable(project1.id, {
				name: 'dataTable2',
				columns: [],
			});

			projectRelationRepository.find.mockResolvedValueOnce([
				{
					userId: user.id,
					projectId: project1.id,
					role: PROJECT_ADMIN_ROLE,
					user,
					project: project1,
					createdAt: new Date(),
					updatedAt: new Date(),
					setUpdateDate: jest.fn(),
				},
				{
					userId: user.id,
					projectId: project2.id,
					role: { slug: 'project:viewer' } as Role,
					user,
					project: project2,
					createdAt: new Date(),
					updatedAt: new Date(),
					setUpdateDate: jest.fn(),
				},
			]);

			await dataTableService.createDataTable(project2.id, {
				name: 'dataTable3',
				columns: [],
			});

			// ACT
			const result = await dataTableAggregateService.getManyAndCount(user, {
				filter: { projectId: project1.id },
				skip: 0,
				take: 10,
			});

			// ASSERT
			expect(result.data).toEqual(
				expect.arrayContaining([
					expect.objectContaining({ id: ds1.id, name: ds1.name }),
					expect.objectContaining({ id: ds2.id, name: ds2.name }),
				]),
			);
			expect(result.count).toBe(2);
		});

		it('should return an empty array if user has no access to any project', async () => {
			// ARRANGE
			const currentUser = await createUser({ role: GLOBAL_MEMBER_ROLE });

			await dataTableService.createDataTable(project1.id, {
				name: 'dataTable1',
				columns: [],
			});
			projectRelationRepository.find.mockResolvedValueOnce([]);

			// ACT
			const result = await dataTableAggregateService.getManyAndCount(currentUser, {
				skip: 0,
				take: 10,
			});

			// ASSERT
			expect(result.data).toEqual([]);
			expect(result.count).toBe(0);
		});

		it('should list all tables for owners and admins', async () => {
			// ARRANGE
			const currentUser = await createUser({ role: GLOBAL_ADMIN_ROLE });

			const dt1 = await dataTableService.createDataTable(project1.id, {
				name: 'dataTable1',
				columns: [],
			});
			projectRelationRepository.find.mockResolvedValueOnce([]);

			// ACT
			const result = await dataTableAggregateService.getManyAndCount(currentUser, {
				skip: 0,
				take: 10,
			});

			// ASSERT
			expect(result.data).toEqual(
				expect.arrayContaining([expect.objectContaining({ id: dt1.id, name: dt1.name })]),
			);
			expect(result.count).toBe(1);
		});

		it('should return only the data table matching the given data table id filter', async () => {
			// ARRANGE
			await dataTableService.createDataTable(project1.id, {
				name: 'dataTable1',
				columns: [],
			});
			const ds2 = await dataTableService.createDataTable(project1.id, {
				name: 'dataTable2',
				columns: [],
			});
			projectRelationRepository.find.mockResolvedValueOnce([
				{
					userId: user.id,
					projectId: project1.id,
					role: PROJECT_ADMIN_ROLE,
					user,
					project: project1,
					createdAt: new Date(),
					updatedAt: new Date(),
					setUpdateDate: jest.fn(),
				},
				{
					userId: user.id,
					projectId: project2.id,
					role: { slug: 'project:viewer' } as Role,
					user,
					project: project2,
					createdAt: new Date(),
					updatedAt: new Date(),
					setUpdateDate: jest.fn(),
				},
			]);

			// ACT
			const result = await dataTableAggregateService.getManyAndCount(user, {
				filter: { id: ds2.id },
				skip: 0,
				take: 10,
			});

			// ASSERT
			expect(result.data).toEqual([expect.objectContaining({ id: ds2.id, name: ds2.name })]);
			expect(result.count).toBe(1);
		});

		it('should respect pagination (skip/take)', async () => {
			// ARRANGE
			const ds1 = await dataTableService.createDataTable(project1.id, {
				name: 'dataTable1',
				columns: [],
			});
			const ds2 = await dataTableService.createDataTable(project1.id, {
				name: 'dataTable2',
				columns: [],
			});
			const ds3 = await dataTableService.createDataTable(project1.id, {
				name: 'dataTable3',
				columns: [],
			});
			projectRelationRepository.find.mockResolvedValueOnce([
				{
					userId: user.id,
					projectId: project1.id,
					role: PROJECT_ADMIN_ROLE,
					user,
					project: project1,
					createdAt: new Date(),
					updatedAt: new Date(),
					setUpdateDate: jest.fn(),
				},
			]);

			// ACT
			const result = await dataTableAggregateService.getManyAndCount(user, {
				filter: { projectId: project1.id },
				skip: 1,
				take: 1,
			});

			// ASSERT
			expect(result.data.length).toBe(1);
			expect([ds1.id, ds2.id, ds3.id]).toContain(result.data[0].id);
			expect(result.count).toBe(3);
		});
	});
});
