import { LicenseState } from '@n8n/backend-common';
import { createTeamProject, mockInstance, testDb } from '@n8n/backend-test-utils';
import type { Project } from '@n8n/db';
import {
	ProjectRepository,
	ProjectSecretsProviderAccessRepository,
	SecretsProviderConnectionRepository,
} from '@n8n/db';
import { Container } from '@n8n/di';
import { mock } from 'jest-mock-extended';
import { Cipher } from 'n8n-core';

import { ExternalSecretsConfig } from '@/modules/external-secrets.ee/external-secrets.config';
import { ExternalSecretsProviders } from '@/modules/external-secrets.ee/external-secrets-providers.ee';

import { MockProviders, createDummyProvider } from '../../shared/external-secrets/utils';
import { createAdmin, createMember, createOwner } from '../shared/db/users';
import type { SuperAgentTest } from '../shared/types';
import * as utils from '../shared/utils';

const mockProvidersInstance = new MockProviders();
// Register mock providers for types used in tests
mockProvidersInstance.setProviders({
	awsSecretsManager: createDummyProvider({
		name: 'awsSecretsManager',
		properties: [
			{
				name: 'region',
				displayName: 'Region',
				type: 'string',
				default: '',
			},
			{
				name: 'accessKeyId',
				displayName: 'Access Key ID',
				type: 'string',
				default: '',
				typeOptions: {
					password: true,
				},
			},
			{
				name: 'secretAccessKey',
				displayName: 'Secret Access Key',
				type: 'string',
				default: '',
				typeOptions: {
					password: true,
				},
			},
			{
				name: 'sessionToken',
				displayName: 'Session Token',
				type: 'string',
				default: '',
				typeOptions: {
					password: true,
				},
			},
		],
	}),
	gcpSecretsManager: createDummyProvider({ name: 'gcpSecretsManager' }),
	vault: createDummyProvider({ name: 'vault' }),
});
mockInstance(ExternalSecretsProviders, mockProvidersInstance);

const licenseMock = mock<LicenseState>();
licenseMock.isLicensed.mockReturnValue(true);
Container.set(LicenseState, licenseMock);

mockInstance(ExternalSecretsConfig, {
	externalSecretsForProjects: true,
});

describe('Secret Providers Connections API', () => {
	const testServer = utils.setupTestServer({
		endpointGroups: ['externalSecrets'],
		enabledFeatures: ['feat:externalSecrets'],
		modules: ['external-secrets'],
	});

	let ownerAgent: SuperAgentTest;
	let memberAgent: SuperAgentTest;
	let adminAgent: SuperAgentTest;
	let agents: Record<string, SuperAgentTest>;
	let teamProject1: Project;
	let teamProject2: Project;

	const FORBIDDEN_MESSAGE = 'User is missing a scope required to perform this action';

	beforeAll(async () => {
		const owner = await createOwner();
		ownerAgent = testServer.authAgentFor(owner);

		const [member, admin] = await Promise.all([createMember(), createAdmin()]);
		memberAgent = testServer.authAgentFor(member);
		adminAgent = testServer.authAgentFor(admin);

		agents = {
			owner: ownerAgent,
			admin: adminAgent,
			member: memberAgent,
		};

		teamProject1 = await createTeamProject('Engineering');
		teamProject2 = await createTeamProject('Marketing');
	});

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

	describe('Create connection', () => {
		test('should create a global connection by default with encrypted settings', async () => {
			const payload = {
				providerKey: 'awsProd',
				type: 'awsSecretsManager',
				projectIds: [],
				settings: {
					region: 'us-east-1',
					accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
					secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
				},
			};

			const response = await ownerAgent
				.post('/secret-providers/connections')
				.send(payload)
				.expect(200);

			expect(response.body.data).toEqual({
				id: expect.any(String),
				name: 'awsProd',
				type: 'awsSecretsManager',
				secretsCount: 2,
				state: 'connected',
				secrets: [{ name: 'test1' }, { name: 'test2' }],
				projects: [],
				settings: expect.any(Object),
				createdAt: expect.any(String),
				updatedAt: expect.any(String),
			});

			// Validate encrypted items in the DB
			const savedConnection = await Container.get(
				SecretsProviderConnectionRepository,
			).findOneByOrFail({ providerKey: 'awsProd' });

			expect(savedConnection.encryptedSettings).toBeTruthy();
			expect(savedConnection.encryptedSettings).not.toContain('us-east-1');
			expect(savedConnection.encryptedSettings).not.toContain('AKIAIOSFODNN7EXAMPLE');

			// Check we can decrypt them
			const cipher = Container.get(Cipher);
			const decryptedSettings = JSON.parse(cipher.decrypt(savedConnection.encryptedSettings));
			expect(decryptedSettings).toEqual(payload.settings);
		});

		test('should create a connection and assign its projects', async () => {
			const payload = {
				providerKey: 'awsShared',
				type: 'awsSecretsManager',
				projectIds: [teamProject1.id, teamProject2.id],
				settings: {
					region: 'us-west-2',
					accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
					secretAccessKey: 'secret123',
				},
			};

			const response = await ownerAgent
				.post('/secret-providers/connections')
				.send(payload)
				.expect(200);

			expect(response.body.data.projects).toHaveLength(2);
			expect(response.body.data.projects).toEqual(
				expect.arrayContaining([
					{ id: teamProject1.id, name: 'Engineering' },
					{ id: teamProject2.id, name: 'Marketing' },
				]),
			);

			const getResponse = await ownerAgent
				.get('/secret-providers/connections/awsShared')
				.expect(200);

			expect(getResponse.body.data.projects).toHaveLength(2);
		});

		test('should reject duplicate provider key', async () => {
			const payload = {
				providerKey: 'duplicateTest',
				type: 'awsSecretsManager',
				projectIds: [],
				settings: { region: 'us-east-1' },
			};

			await ownerAgent.post('/secret-providers/connections').send(payload).expect(200);

			const response = await ownerAgent
				.post('/secret-providers/connections')
				.send(payload)
				.expect(400);

			expect(response.body.message).toBe('There is already an entry with this name');
		});
	});

	describe('Update connection', () => {
		test('should update connection type', async () => {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'updateTypeTest',
					type: 'awsSecretsManager',
					projectIds: [teamProject1.id],
					settings: { region: 'us-east-1' },
				})
				.expect(200);

			const response = await ownerAgent
				.patch('/secret-providers/connections/updateTypeTest')
				.send({
					type: 'gcpSecretsManager',
					settings: { projectId: 'my-project' },
				})
				.expect(200);

			expect(response.body.data.type).toBe('gcpSecretsManager');
			expect(response.body.data.projects).toHaveLength(1);

			const getResponse = await ownerAgent
				.get('/secret-providers/connections/updateTypeTest')
				.expect(200);

			expect(getResponse.body.data.type).toBe('gcpSecretsManager');
		});

		test('should completely replace settings (not merge)', async () => {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'replaceSettingsTest',
					type: 'awsSecretsManager',
					projectIds: [],
					settings: {
						region: 'us-east-1',
						accessKeyId: 'OLD_KEY',
						secretAccessKey: 'OLD_SECRET',
					},
				})
				.expect(200);

			await ownerAgent
				.patch('/secret-providers/connections/replaceSettingsTest')
				.send({
					settings: {
						region: 'eu-west-1',
						secretAccessKey: 'NEW_SECRET',
					},
				})
				.expect(200);

			const updated = await Container.get(SecretsProviderConnectionRepository).findOneByOrFail({
				providerKey: 'replaceSettingsTest',
			});

			const cipher = Container.get(Cipher);
			const decryptedSettings = JSON.parse(cipher.decrypt(updated.encryptedSettings));

			expect(decryptedSettings).toEqual({
				region: 'eu-west-1',
				secretAccessKey: 'NEW_SECRET',
			});
			expect(decryptedSettings).not.toHaveProperty('accessKeyId');
		});

		test('should update project access', async () => {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'updateProjectsTest',
					type: 'awsSecretsManager',
					projectIds: [teamProject1.id],
					settings: { region: 'us-east-1' },
				})
				.expect(200);

			const response = await ownerAgent
				.patch('/secret-providers/connections/updateProjectsTest')
				.send({ projectIds: [teamProject2.id] })
				.expect(200);

			expect(response.body.data.projects).toEqual([{ id: teamProject2.id, name: 'Marketing' }]);

			const getResponse = await ownerAgent
				.get('/secret-providers/connections/updateProjectsTest')
				.expect(200);

			expect(getResponse.body.data.projects).toHaveLength(1);
			expect(getResponse.body.data.projects[0].id).toBe(teamProject2.id);
		});

		test('should convert to global by passing empty projectIds', async () => {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'toGlobalTest',
					type: 'awsSecretsManager',
					projectIds: [teamProject1.id, teamProject2.id],
					settings: { region: 'us-east-1' },
				})
				.expect(200);

			const response = await ownerAgent
				.patch('/secret-providers/connections/toGlobalTest')
				.send({ projectIds: [] })
				.expect(200);

			expect(response.body.data.projects).toEqual([]);

			const getResponse = await ownerAgent
				.get('/secret-providers/connections/toGlobalTest')
				.expect(200);

			expect(getResponse.body.data.projects).toEqual([]);
		});

		test('should fail for non-existent connection', async () => {
			const response = await ownerAgent
				.patch('/secret-providers/connections/non-existent')
				.send({ type: 'gcpSecretsManager' })
				.expect(404);

			expect(response.body.message).toBe('Connection with key "non-existent" not found');
		});
	});

	describe('Delete connection', () => {
		test('should delete connection and cascade project access', async () => {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'deleteTest',
					type: 'awsSecretsManager',
					projectIds: [teamProject1.id, teamProject2.id],
					settings: { region: 'us-east-1' },
				})
				.expect(200);

			// Save the connection ID for verification after deletion
			const savedConnection = await Container.get(
				SecretsProviderConnectionRepository,
			).findOneByOrFail({ providerKey: 'deleteTest' });
			const connectionId = savedConnection.id;

			await ownerAgent.delete('/secret-providers/connections/deleteTest').expect(204);

			// Verify deletion via GET
			await ownerAgent.get('/secret-providers/connections/deleteTest').expect(404);

			// Verify cascade delete
			const remainingAccess = await Container.get(ProjectSecretsProviderAccessRepository).find({
				where: { secretsProviderConnectionId: connectionId },
			});
			expect(remainingAccess).toHaveLength(0);
		});

		test('should fail for non-existent connection', async () => {
			const response = await ownerAgent
				.delete('/secret-providers/connections/non-existent')
				.expect(404);

			expect(response.body.message).toBe('Connection with key "non-existent" not found');
		});
	});

	describe('List connections', () => {
		test('should list all connections with eager-loaded projects', async () => {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'listTest1',
					type: 'awsSecretsManager',
					projectIds: [teamProject1.id],
					settings: { region: 'us-east-1' },
				})
				.expect(200);

			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'listTest2',
					type: 'gcpSecretsManager',
					projectIds: [teamProject2.id],
					settings: { projectId: 'my-project' },
				})
				.expect(200);

			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'listTest3',
					type: 'vault',
					projectIds: [],
					settings: { vaultUrl: 'https://vault.example.com' },
				})
				.expect(200);

			const response = await ownerAgent.get('/secret-providers/connections').expect(200);

			expect(response.body.data).toHaveLength(3);

			for (const connection of response.body.data) {
				expect(connection).toMatchObject({
					id: expect.any(String),
					name: expect.any(String),
					type: expect.any(String),
					state: expect.any(String),
					projects: expect.any(Array),
					createdAt: expect.any(String),
					updatedAt: expect.any(String),
				});
				// LIST endpoint should NOT include settings (lightweight response)
				expect(connection).not.toHaveProperty('settings');
			}

			const connection1 = response.body.data.find((c: { name: string }) => c.name === 'listTest1');
			expect(connection1.projects).toHaveLength(1);

			const connection3 = response.body.data.find((c: { name: string }) => c.name === 'listTest3');
			expect(connection3.projects).toEqual([]);
		});

		test('should return empty array when no connections exist', async () => {
			const response = await ownerAgent.get('/secret-providers/connections').expect(200);

			expect(response.body.data).toEqual([]);
		});
	});

	describe('Get single connection', () => {
		test('should return full connection details', async () => {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'getTest',
					type: 'awsSecretsManager',
					projectIds: [teamProject1.id, teamProject2.id],
					settings: {
						region: 'us-east-1',
						accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
					},
				})
				.expect(200);

			const response = await ownerAgent.get('/secret-providers/connections/getTest').expect(200);

			expect(response.body.data).toMatchObject({
				id: expect.any(String),
				name: 'getTest',
				type: 'awsSecretsManager',
				state: expect.any(String),
				createdAt: expect.any(String),
				updatedAt: expect.any(String),
			});
			expect(response.body.data.projects).toHaveLength(2);
		});

		test('should fail for non-existent connection', async () => {
			const response = await ownerAgent
				.get('/secret-providers/connections/does-not-exist')
				.expect(404);

			expect(response.body.message).toBe('Connection with key "does-not-exist" not found');
		});
	});

	describe('Security', () => {
		test('should return redacted settings without exposing sensitive data', async () => {
			const sensitiveSettings = {
				region: 'us-east-1',
				accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
				secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
				sessionToken: 'very-secret-session-token',
			};

			// Create connection with sensitive settings
			const createResponse = await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'securityTest',
					type: 'awsSecretsManager',
					projectIds: [teamProject1.id],
					settings: sensitiveSettings,
				})
				.expect(200);

			// Detail endpoint (POST) should include settings field
			expect(createResponse.body.data).toHaveProperty('settings');
			expect(createResponse.body.data.settings).toEqual(expect.any(Object));

			// Password fields should be redacted (contain BLANK_VALUE marker)
			const responseSettings = createResponse.body.data.settings;
			const responseString = JSON.stringify(responseSettings);
			expect(responseString).toContain('__n8n_BLANK_VALUE_');

			// Actual secret values should NOT appear in response
			expect(responseString).not.toContain('AKIAIOSFODNN7EXAMPLE');
			expect(responseString).not.toContain('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
			expect(responseString).not.toContain('very-secret-session-token');

			// GET detail endpoint should also include redacted settings
			const getResponse = await ownerAgent
				.get('/secret-providers/connections/securityTest')
				.expect(200);

			expect(getResponse.body.data).toHaveProperty('settings');
			const getResponseString = JSON.stringify(getResponse.body.data.settings);
			expect(getResponseString).toContain('__n8n_BLANK_VALUE_');
			expect(getResponseString).not.toContain('AKIAIOSFODNN7EXAMPLE');
			expect(getResponseString).not.toContain('very-secret-session-token');

			// LIST endpoint should NOT include settings
			const listResponse = await ownerAgent.get('/secret-providers/connections').expect(200);

			const securityTestConnection = listResponse.body.data.find(
				(conn: { name: string }) => conn.name === 'securityTest',
			);
			expect(securityTestConnection).toBeDefined();
			expect(securityTestConnection).not.toHaveProperty('settings');

			// Verify entire list response doesn't contain secrets
			const listResponseString = JSON.stringify(listResponse.body.data);
			expect(listResponseString).not.toContain('AKIAIOSFODNN7EXAMPLE');
			expect(listResponseString).not.toContain('very-secret-session-token');

			// DELETE endpoint should return 204 with no body
			await ownerAgent.delete('/secret-providers/connections/securityTest').expect(204);
		});
	});

	describe('Access Control', () => {
		async function createTestConnection(providerKey: string, projectIds: string[] = []) {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey,
					type: 'awsSecretsManager',
					projectIds,
					settings: { region: 'us-east-1' },
				})
				.expect(200);
		}

		describe('Create connection', () => {
			test.each([
				{ role: 'owner', allowed: true },
				{ role: 'admin', allowed: true },
				{ role: 'member', allowed: false },
			])('should allow=$allowed for $role to create connection', async ({ role, allowed }) => {
				const providerKey = `createTest${role.charAt(0).toUpperCase() + role.slice(1)}`;
				const response = await agents[role]
					.post('/secret-providers/connections')
					.send({
						providerKey,
						type: 'awsSecretsManager',
						projectIds: [],
						settings: { region: 'us-east-1' },
					})
					.expect(allowed ? 200 : 403);

				if (allowed) {
					expect(response.body.data.name).toBe(providerKey);
				} else {
					expect(response.body.message).toBe(FORBIDDEN_MESSAGE);
				}
			});
		});

		describe('read connections', () => {
			beforeEach(async () => {
				await createTestConnection('readTestConnection', [teamProject1.id]);
			});

			describe('list all connections', () => {
				test.each([
					{ role: 'owner', allowed: true },
					{ role: 'admin', allowed: true },
					{ role: 'member', allowed: false },
				])('should allow=$allowed for $role to list connections', async ({ role, allowed }) => {
					const response = await agents[role]
						.get('/secret-providers/connections')
						.expect(allowed ? 200 : 403);

					if (allowed) {
						expect(Array.isArray(response.body.data)).toBe(true);
						const connection = response.body.data.find(
							(c: { name: string }) => c.name === 'readTestConnection',
						);
						expect(connection).toBeDefined();
					} else {
						expect(response.body.message).toBe(FORBIDDEN_MESSAGE);
					}
				});
			});

			describe('get single connection', () => {
				test.each([
					{ role: 'owner', allowed: true },
					{ role: 'admin', allowed: true },
					{ role: 'member', allowed: false },
				])(
					'should allow=$allowed for $role to get single connection',
					async ({ role, allowed }) => {
						const response = await agents[role]
							.get('/secret-providers/connections/readTestConnection')
							.expect(allowed ? 200 : 403);

						if (allowed) {
							expect(response.body.data.name).toBe('readTestConnection');
						} else {
							expect(response.body.message).toBe(FORBIDDEN_MESSAGE);
						}
					},
				);
			});
		});

		describe('update connections', () => {
			beforeEach(async () => {
				await createTestConnection('updateAuthTest');
			});

			test.each([
				{ role: 'owner', allowed: true },
				{ role: 'admin', allowed: true },
				{ role: 'member', allowed: false },
			])('should allow=$allowed for $role to update connection', async ({ role, allowed }) => {
				const response = await agents[role]
					.patch('/secret-providers/connections/updateAuthTest')
					.send({ projectIds: [teamProject1.id] })
					.expect(allowed ? 200 : 403);

				if (allowed) {
					expect(response.body.data.projects).toHaveLength(1);
				} else {
					expect(response.body.message).toBe(FORBIDDEN_MESSAGE);
				}
			});
		});

		describe('delete connections', () => {
			beforeEach(async () => {
				await Promise.all(
					['owner', 'admin', 'member'].map(
						async (role) =>
							await createTestConnection(
								`deleteAuth${role.charAt(0).toUpperCase() + role.slice(1)}Test`,
							),
					),
				);
			});

			test.each([
				{ role: 'owner', allowed: true },
				{ role: 'admin', allowed: true },
				{ role: 'member', allowed: false },
			])('should allow=$allowed for $role to delete connection', async ({ role, allowed }) => {
				const providerKey = `deleteAuth${role.charAt(0).toUpperCase() + role.slice(1)}Test`;
				const response = await agents[role]
					.delete(`/secret-providers/connections/${providerKey}`)
					.expect(allowed ? 204 : 403);

				if (allowed) {
					await ownerAgent.get(`/secret-providers/connections/${providerKey}`).expect(404);
				} else {
					expect(response.body.message).toBe(FORBIDDEN_MESSAGE);
					await ownerAgent.get(`/secret-providers/connections/${providerKey}`).expect(200);
				}
			});
		});
	});

	describe('Relationships with Project entity', () => {
		test('multiple providers can be associated with the same project', async () => {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'provider1',
					type: 'awsSecretsManager',
					projectIds: [teamProject1.id],
					settings: { region: 'us-east-1' },
				})
				.expect(200);

			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'provider2',
					type: 'gcpSecretsManager',
					projectIds: [teamProject1.id],
					settings: { projectId: 'my-project' },
				})
				.expect(200);

			const response = await ownerAgent.get('/secret-providers/connections').expect(200);

			const provider1 = response.body.data.find((c: { name: string }) => c.name === 'provider1');
			const provider2 = response.body.data.find((c: { name: string }) => c.name === 'provider2');

			expect(provider1).toBeDefined();
			expect(provider2).toBeDefined();
			expect(provider1.projects[0].id).toBe(teamProject1.id);
			expect(provider2.projects[0].id).toBe(teamProject1.id);

			// Database validation
			const accessForProject = await Container.get(ProjectSecretsProviderAccessRepository).find({
				where: { projectId: teamProject1.id },
			});
			expect(accessForProject).toHaveLength(2);
		});

		test('should clean up project access when project is deleted', async () => {
			const tempProject = await createTeamProject('Temporary');
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'orphanTest',
					type: 'awsSecretsManager',
					projectIds: [tempProject.id],
					settings: { region: 'us-east-1' },
				})
				.expect(200);

			await Container.get(ProjectRepository).delete(tempProject.id);

			// Verify no orphaned access entries
			const orphaned = await Container.get(ProjectSecretsProviderAccessRepository).find({
				where: { projectId: tempProject.id },
			});
			expect(orphaned).toHaveLength(0);

			// Connection should still exist but with empty projects
			const response = await ownerAgent.get('/secret-providers/connections/orphanTest').expect(200);
			expect(response.body.data.projects).toEqual([]);
		});
	});

	describe('Reload connection secrets', () => {
		beforeAll(async () => {
			const { DummyProvider } = await import('../../shared/external-secrets/utils');
			mockProvidersInstance.setProviders({ awsSecretsManager: DummyProvider });
		});

		afterEach(async () => {
			const { DummyProvider } = await import('../../shared/external-secrets/utils');
			mockProvidersInstance.setProviders({ awsSecretsManager: DummyProvider });
		});

		test('should successfully reload connection secrets', async () => {
			const { ExternalSecretsManager } = await import(
				'@/modules/external-secrets.ee/external-secrets-manager.ee'
			);

			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'reloadSuccess',
					type: 'awsSecretsManager',
					projectIds: [],
					settings: { username: 'user', password: 'pass' },
				})
				.expect(200);

			const manager = Container.get(ExternalSecretsManager);
			await manager.reloadAllProviders();

			const provider = manager.getProvider('reloadSuccess');
			expect(provider).toBeDefined();
			expect(provider?.state).toBe('connected');

			const updateSpy = jest.spyOn(provider!, 'update');

			const reloadResponse = await ownerAgent
				.post('/secret-providers/connections/reloadSuccess/reload')
				.expect(200);

			expect(reloadResponse.body.data).toEqual({ success: true });
			expect(updateSpy).toHaveBeenCalled();
		});

		test('should return 404 for non-existent connection', async () => {
			const response = await ownerAgent
				.post('/secret-providers/connections/non-existent/reload')
				.expect(404);

			expect(response.body.message).toBe('Connection with key "non-existent" not found');
		});

		describe('access control', () => {
			test.each([
				{ role: 'owner', allowed: true },
				{ role: 'admin', allowed: true },
				{ role: 'member', allowed: false },
			])('should allow=$allowed for $role to reload connection', async ({ role, allowed }) => {
				const { ExternalSecretsManager } = await import(
					'@/modules/external-secrets.ee/external-secrets-manager.ee'
				);

				const providerKey = `reloadAccess${role.charAt(0).toUpperCase() + role.slice(1)}`;
				await ownerAgent
					.post('/secret-providers/connections')
					.send({
						providerKey,
						type: 'awsSecretsManager',
						projectIds: [],
						settings: { region: 'us-east-1' },
					})
					.expect(200);

				await Container.get(ExternalSecretsManager).reloadAllProviders();

				const response = await agents[role]
					.post(`/secret-providers/connections/${providerKey}/reload`)
					.expect(allowed ? 200 : 403);

				if (allowed) {
					expect(response.body.data).toEqual({ success: true });
				} else {
					expect(response.body.message).toBe(FORBIDDEN_MESSAGE);
				}
			});
		});
	});

	describe('Test connection', () => {
		beforeAll(async () => {
			const { DummyProvider } = await import('../../shared/external-secrets/utils');
			mockProvidersInstance.setProviders({ awsSecretsManager: DummyProvider });
		});

		afterEach(async () => {
			const { DummyProvider } = await import('../../shared/external-secrets/utils');
			mockProvidersInstance.setProviders({ awsSecretsManager: DummyProvider });
		});

		test('should successfully test connection with valid settings', async () => {
			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'testSuccess',
					type: 'awsSecretsManager',
					projectIds: [],
					settings: { username: 'user', password: 'pass' },
				})
				.expect(200);

			const testResponse = await ownerAgent
				.post('/secret-providers/connections/testSuccess/test')
				.expect(200);

			expect(testResponse.body.data).toMatchObject({ success: true });
		});

		test('should return failure when provider test fails', async () => {
			const { TestFailProvider } = await import('../../shared/external-secrets/utils');

			mockProvidersInstance.setProviders({ awsSecretsManager: TestFailProvider });

			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'testFail',
					type: 'awsSecretsManager',
					projectIds: [],
					settings: { region: 'us-east-1' },
				})
				.expect(200);

			const testResponse = await ownerAgent
				.post('/secret-providers/connections/testFail/test')
				.expect(200);

			expect(testResponse.body.data).toMatchObject({ success: false });
		});

		test('should return error when connection fails', async () => {
			const { FailedProvider } = await import('../../shared/external-secrets/utils');

			mockProvidersInstance.setProviders({ awsSecretsManager: FailedProvider });

			await ownerAgent
				.post('/secret-providers/connections')
				.send({
					providerKey: 'testConnectionFail',
					type: 'awsSecretsManager',
					projectIds: [],
					settings: { region: 'us-east-1' },
				})
				.expect(200);

			const testResponse = await ownerAgent
				.post('/secret-providers/connections/testConnectionFail/test')
				.expect(200);

			expect(testResponse.body.data.success).toBe(false);
			expect(testResponse.body.data.error).toBeDefined();
		});

		test('should return 404 for non-existent connection', async () => {
			const response = await ownerAgent
				.post('/secret-providers/connections/non-existent/test')
				.expect(404);

			expect(response.body.message).toBe('Connection with key "non-existent" not found');
		});

		describe('access control', () => {
			test.each([
				{ role: 'owner', allowed: true },
				{ role: 'admin', allowed: true },
				{ role: 'member', allowed: false },
			])('should allow=$allowed for $role to test connection', async ({ role, allowed }) => {
				const providerKey = `testAccess${role.charAt(0).toUpperCase() + role.slice(1)}`;
				await ownerAgent
					.post('/secret-providers/connections')
					.send({
						providerKey,
						type: 'awsSecretsManager',
						projectIds: [],
						settings: { region: 'us-east-1' },
					})
					.expect(200);

				const response = await agents[role]
					.post(`/secret-providers/connections/${providerKey}/test`)
					.expect(allowed ? 200 : 403);

				if (allowed) {
					expect(response.body.data).toHaveProperty('success');
				} else {
					expect(response.body.message).toBe(FORBIDDEN_MESSAGE);
				}
			});
		});
	});
});
