import type { WorkflowBuildOutcome } from '../../../workflow-loop/workflow-loop-state';
import { withDeterministicRouting } from '../workflow-build-routing';

function makeOutcome(overrides: Partial<WorkflowBuildOutcome> = {}): WorkflowBuildOutcome {
	return {
		workItemId: 'src/workflows/main.workflow.ts',
		taskId: 'src/workflows/main.workflow.ts',
		workflowId: 'wf-1',
		submitted: true,
		triggerType: 'manual_or_testable',
		triggerNodes: [{ nodeName: 'Start', nodeType: 'n8n-nodes-base.manualTrigger' }],
		needsUserInput: false,
		summary: 'Workflow saved.',
		...overrides,
	};
}

describe('withDeterministicRouting', () => {
	it('marks manual-trigger workflows as ready for verification', () => {
		const outcome = withDeterministicRouting(makeOutcome());

		expect(outcome.verificationReadiness).toEqual({ status: 'ready' });
	});

	it('marks non-deterministic-trigger workflows as ready (trigger gets a simulated fixture)', () => {
		const outcome = withDeterministicRouting(
			makeOutcome({
				triggerNodes: [{ nodeName: 'On New Email', nodeType: 'n8n-nodes-base.gmailTrigger' }],
			}),
		);

		expect(outcome.verificationReadiness).toEqual({ status: 'ready' });
	});

	it('marks suffix-less trigger types (webhook, cron) as ready', () => {
		for (const nodeType of ['n8n-nodes-base.webhook', 'n8n-nodes-base.cron']) {
			const outcome = withDeterministicRouting(
				makeOutcome({ triggerNodes: [{ nodeName: 'Entry', nodeType }] }),
			);

			expect(outcome.verificationReadiness).toEqual({ status: 'ready' });
		}
	});

	it('marks workflows without any trigger node as not verifiable', () => {
		for (const triggerNodes of [
			[],
			undefined,
			[{ nodeName: 'Set', nodeType: 'n8n-nodes-base.set' }],
		]) {
			const outcome = withDeterministicRouting(makeOutcome({ triggerNodes }));

			expect(outcome.verificationReadiness).toMatchObject({
				status: 'not_verifiable',
				reason: 'no-trigger-node',
			});
		}
	});

	// One-off intent deliberately does NOT get its own readiness status — the
	// readiness union is persisted and old readers hard-fail on unknown
	// variants (see verification-obligation.ts). The intent rides on the
	// outcome as a plain optional field instead.
	it('keeps one-off builds on the ready readiness and passes the intent through', () => {
		const outcome = withDeterministicRouting(makeOutcome({ executionIntent: 'one-off' }));

		expect(outcome.verificationReadiness).toEqual({ status: 'ready' });
		expect(outcome.executionIntent).toBe('one-off');
	});

	it('keeps reusable and unspecified intents ready', () => {
		for (const executionIntent of ['reusable', undefined] as const) {
			const outcome = withDeterministicRouting(makeOutcome({ executionIntent }));

			expect(outcome.verificationReadiness).toEqual({ status: 'ready' });
		}
	});

	it('keeps impossible-to-verify verdicts for one-off builds', () => {
		const outcome = withDeterministicRouting(
			makeOutcome({ executionIntent: 'one-off', triggerNodes: [] }),
		);

		expect(outcome.verificationReadiness).toMatchObject({
			status: 'not_verifiable',
			reason: 'no-trigger-node',
		});
		expect(outcome.executionIntent).toBe('one-off');
	});

	it('keeps workflows with unresolved placeholders ready for verification', () => {
		const outcome = withDeterministicRouting(
			makeOutcome({
				hasUnresolvedPlaceholders: true,
			}),
		);

		expect(outcome.verificationReadiness).toEqual({ status: 'ready' });
		expect(outcome.setupRequirement).toEqual({
			status: 'required',
			reason: 'unresolved-placeholders',
			guidance: 'Route the workflow through setup so the user can fill unresolved values.',
		});
	});

	it('keeps workflows with mocked credentials ready for verification', () => {
		const outcome = withDeterministicRouting(
			makeOutcome({
				mockedNodeNames: ['Send Email'],
				mockedCredentialTypes: ['gmailOAuth2'],
				mockedCredentialsByNode: { 'Send Email': ['gmailOAuth2'] },
			}),
		);

		expect(outcome.verificationReadiness).toEqual({ status: 'ready' });
		expect(outcome.setupRequirement).toEqual({
			status: 'required',
			reason: 'mocked-credentials',
			guidance: 'Route the workflow through setup so the user can add real credentials.',
		});
	});

	it('does not require setup for mocked credentials on nodes the build did not change', () => {
		const outcome = withDeterministicRouting(
			makeOutcome({
				mockedNodeNames: ['Send Email'],
				mockedCredentialTypes: ['gmailOAuth2'],
				mockedCredentialsByNode: { 'Send Email': ['gmailOAuth2'] },
				changedNodeNames: ['Build Message'],
			}),
		);

		expect(outcome.setupRequirement).toEqual({ status: 'not_required' });
	});

	it('requires setup for mocked credentials on nodes the build changed', () => {
		const outcome = withDeterministicRouting(
			makeOutcome({
				mockedNodeNames: ['Send Email'],
				mockedCredentialTypes: ['gmailOAuth2'],
				mockedCredentialsByNode: { 'Send Email': ['gmailOAuth2'] },
				changedNodeNames: ['Send Email'],
			}),
		);

		expect(outcome.setupRequirement).toMatchObject({
			status: 'required',
			reason: 'mocked-credentials',
		});
	});

	it('keeps workflows with pending setup requests ready for verification', () => {
		const outcome = withDeterministicRouting({
			...makeOutcome(),
			workflowNeedsSetup: true,
		});

		expect(outcome.verificationReadiness).toEqual({ status: 'ready' });
		expect(outcome.setupRequirement).toEqual({
			status: 'required',
			reason: 'workflow-needs-setup',
			guidance: 'Route the workflow through setup so the user can fill pending node setup fields.',
		});
		expect('workflowNeedsSetup' in outcome).toBe(false);
	});

	it('does not route setup when the only pending credentials were skipped by the user', () => {
		// Mocked credentials and placeholders both normally force setup — a skipped credential
		// still produces both, so the gate has to sit in front of them.
		const outcome = withDeterministicRouting({
			...makeOutcome({
				mockedNodeNames: ['Post to Slack'],
				mockedCredentialTypes: ['slackApi'],
				mockedCredentialsByNode: { 'Post to Slack': ['slackApi'] },
				hasUnresolvedPlaceholders: true,
			}),
			onlySkippedSetupRemains: true,
		});

		expect(outcome.setupRequirement).toMatchObject({
			status: 'not_required',
			reason: 'skipped-by-user',
		});
		expect(outcome.verificationReadiness).toEqual({ status: 'ready' });
		expect('onlySkippedSetupRemains' in outcome).toBe(false);
	});

	it('still routes setup when a non-skipped credential is pending too', () => {
		const outcome = withDeterministicRouting({
			...makeOutcome({
				mockedNodeNames: ['Post to Slack', 'Log to Sheet'],
				mockedCredentialTypes: ['slackApi', 'googleSheetsOAuth2Api'],
			}),
			workflowNeedsSetup: true,
			onlySkippedSetupRemains: false,
		});

		expect(outcome.setupRequirement).toMatchObject({ status: 'required' });
	});
});
