import { nanoid } from 'nanoid';

import { test, expect } from '../../../fixtures/base';
import { DYNAMIC_CRED_ENDPOINT_TOKEN } from '../../../services/dynamic-credential-api-helper';

/**
 * E2E tests for the dynamic credentials feature.
 *
 * Requires:
 *   - capability: 'dynamic-credentials' (Keycloak container + env vars)
 *   - api.enableFeature('dynamicCredentials') (license feature)
 */
test.use({
	capability: 'dynamic-credentials',
	ignoreHTTPSErrors: true, // Keycloak uses a self-signed certificate
});

/**
 * Integration test: external user triggers a workflow via a production webhook.
 * The resolvable oAuth2Api credential is pre-authorized via the Keycloak authorization
 * code flow, then the HTTP Request node uses it to call the Keycloak userinfo endpoint.
 *
 * Flow:
 *   1. Create OAuth2 resolver + resolvable oAuth2Api credential (configured for Keycloak)
 *   2. Build the workflow (webhook + HTTP Request using the credential) — not yet active
 *   3. Get Keycloak access token (ROPC — identifies the external user)
 *   4. Call execution-status → credential reports "missing" → extract authorizationUrl
 *   5. POST to authorizationUrl → Keycloak login page → complete authorization code flow
 *   6. n8n callback stores user's tokens in dynamic_credential_entry
 *   7. Verify execution-status now reports credential as "configured"
 *   8. Activate the workflow (webhook + HTTP Request node using the credential)
 *   9. Trigger the production webhook with the bearer token
 *  10. Wait for execution and assert success (HTTP node resolved credential + called userinfo)
 */
test.describe(
	'Dynamic Credentials: webhook execution @capability:dynamic-credentials @licensed',
	{
		annotation: [{ type: 'owner', description: 'Identity & Access' }],
	},
	() => {
		test('should execute HTTP node with resolvable OAuth2 credential via production webhook @auth:owner', async ({
			api,
			services,
		}) => {
			const keycloak = services.keycloak;

			// Derive Keycloak endpoint URLs from the discovery URL.
			// authUrl: EXTERNAL URL — the test machine visits this for the authorization redirect.
			// accessTokenUrl: INTERNAL URL — n8n exchanges the auth code server-to-server.
			const externalBase = keycloak.discoveryUrl.replace('/.well-known/openid-configuration', '');
			const internalBase = keycloak.internalDiscoveryUrl.replace(
				'/.well-known/openid-configuration',
				'',
			);

			// Create an OAuth2 resolver that validates tokens via Keycloak's userinfo endpoint
			const resolver = await api.dynamicCredentials.createResolver({
				name: `Keycloak OAuth2 Resolver ${nanoid()}`,
				type: 'credential-resolver.oauth2-1.0',
				config: {
					metadataUri: keycloak.internalDiscoveryUrl,
					validation: 'oauth2-userinfo',
				},
			});

			// Create a properly-configured oAuth2Api credential pointing at Keycloak.
			// The credential is resolvable — its tokens are stored per-user by the resolver.
			const credential = await api.credentials.createCredential({
				name: `Keycloak OAuth2 Credential ${nanoid()}`,
				type: 'oAuth2Api',
				data: {
					grantType: 'authorizationCode',
					authUrl: `${externalBase}/protocol/openid-connect/auth`,
					accessTokenUrl: `${internalBase}/protocol/openid-connect/token`,
					clientId: keycloak.clientId,
					clientSecret: keycloak.clientSecret,
					scope: 'openid',
					ignoreSSLIssues: true,
				},
				isResolvable: true,
			});

			// Build a workflow: webhook trigger → HTTP Request (calls Keycloak userinfo with credential)
			// The workflow is created BEFORE authorization so we can obtain the authorizationUrl
			// from the execution-status endpoint (the real flow a marketplace user would follow).
			const { workflowId, webhookPath, createdWorkflow } =
				await api.workflows.createWorkflowFromDefinition({
					name: `Dynamic Credential HTTP Webhook Workflow ${nanoid()}`,
					nodes: [
						{
							id: nanoid(),
							name: 'Webhook',
							type: 'n8n-nodes-base.webhook',
							typeVersion: 2,
							position: [0, 0] as [number, number],
							parameters: {
								httpMethod: 'GET',
								path: 'placeholder',
								responseMode: 'onReceived', // Respond immediately; execution runs async
								// Configure the execution context hook to extract the bearer token
								// from the Authorization header. Without this, the dynamic credential
								// resolver can't identify the user during execution.
								executionsHooksVersion: 1,
								contextEstablishmentHooks: {
									hooks: [
										{
											hookName: 'BearerTokenExtractor',
											isAllowedToFail: false,
										},
									],
								},
							},
						},
						{
							id: nanoid(),
							name: 'HTTP Request',
							type: 'n8n-nodes-base.httpRequest',
							typeVersion: 4.2,
							position: [200, 0] as [number, number],
							parameters: {
								// Keycloak userinfo endpoint — accepts Bearer tokens and returns user info (200)
								url: `${internalBase}/protocol/openid-connect/userinfo`,
								authentication: 'predefinedCredentialType',
								nodeCredentialType: 'oAuth2Api',
							},
							credentials: {
								oAuth2Api: { id: credential.id, name: credential.name },
							},
						},
					],
					connections: {
						Webhook: {
							main: [[{ node: 'HTTP Request', type: 'main', index: 0 }]],
						},
					},
					settings: {
						credentialResolverId: resolver.id,
					},
				});

			// Obtain a Keycloak access token for the test user (ROPC — no browser needed).
			// This token is used as the user identity throughout the flow.
			const accessToken = await keycloak.getAccessToken(
				keycloak.testUser.email,
				keycloak.testUser.password,
			);

			// Step 1: Check execution-status before authorization.
			// The credential is not yet configured → status is "missing".
			// The response includes an authorizationUrl pointing to the n8n authorize endpoint.
			const initialStatus = await api.dynamicCredentials.getExecutionStatus(workflowId, {
				bearerToken: accessToken,
				endpointToken: DYNAMIC_CRED_ENDPOINT_TOKEN,
			});
			expect(initialStatus.credentials).toHaveLength(1);
			expect(initialStatus.credentials![0].credentialStatus).toBe('missing');

			// Step 2: Use the authorizationUrl from execution-status to start the OAuth2 flow.
			// This is the URL a real marketplace user would follow after seeing "missing" status.
			const n8nAuthorizeUrl = initialStatus.credentials![0].authorizationUrl!;
			expect(n8nAuthorizeUrl).toBeTruthy();

			// POST to the n8n authorize endpoint → returns the Keycloak authorization page URL
			const keycloakAuthUrl = await api.dynamicCredentials.startAuthorizationFromStatusUrl(
				n8nAuthorizeUrl,
				accessToken,
			);

			// Step 3: Complete the Keycloak authorization code flow for the test user.
			// Navigates Keycloak's login form and returns the n8n callback URL (with code + state).
			const n8nCallbackUrl = await keycloak.completeAuthorizationCodeFlow(keycloakAuthUrl);
			// GET the n8n callback with the owner session: n8n exchanges the code and stores tokens
			await api.request.get(n8nCallbackUrl);

			// Activate the workflow to register the production webhook URL
			await api.workflows.activate(workflowId, createdWorkflow.versionId as string);

			try {
				// Verify the credential is now "configured" for this user before triggering
				const status = await api.dynamicCredentials.getExecutionStatus(workflowId, {
					bearerToken: accessToken,
					endpointToken: DYNAMIC_CRED_ENDPOINT_TOKEN,
				});
				expect(status.credentials).toHaveLength(1);
				expect(status.credentials![0].credentialStatus).toBe('configured');

				// Trigger the production webhook with the bearer token.
				// n8n extracts the token from the Authorization header for credential resolution.
				const webhookResponse = await api.webhooks.trigger(`/webhook/${webhookPath!}`, {
					method: 'GET',
					headers: {
						Authorization: `Bearer ${accessToken}`,
					},
					maxNotFoundRetries: 5,
				});
				expect(webhookResponse.status()).toBe(200);

				// Wait for the async execution to complete.
				// The HTTP Request node resolves the credential → injects Bearer token → calls Keycloak userinfo → 200
				const execution = await api.workflows.waitForExecution(workflowId, 15000);
				expect((execution as unknown as { status: string }).status).toBe('success');
			} finally {
				// Deactivate to prevent orphaned active webhooks after the test
				await api.workflows.deactivate(workflowId);
			}
		});
	},
);
