import { User } from '@n8n/db';
import {
	bestPracticesRegistry,
	TechniqueDescription,
	WorkflowTechnique,
} from '@n8n/workflow-sdk/prompts/best-practices';
import type { Mocked } from 'vitest';
import { mock } from 'vitest-mock-extended';

import type { Telemetry } from '@/telemetry';

import { USER_CALLED_MCP_TOOL_EVENT } from '../mcp.constants';
import { createGetWorkflowBestPracticesTool } from '../tools/workflow-builder/get-workflow-best-practices.tool';

// v2 SDK types structuredContent as an arbitrary JSON value; narrow for assertions.
const structuredOf = (result: { structuredContent?: unknown }) =>
	result.structuredContent as Record<string, unknown> | undefined;

vi.mock('@n8n/ai-workflow-builder', () => ({
	MCP_GET_WORKFLOW_BEST_PRACTICES_TOOL: {
		toolName: 'get_workflow_best_practices',
		displayTitle: 'Getting workflow best practices',
	},
}));

describe('get-workflow-best-practices MCP tool', () => {
	const user = Object.assign(new User(), { id: 'user-1' });
	let telemetry: Mocked<Telemetry>;

	beforeEach(() => {
		vi.clearAllMocks();
		telemetry = mock<Telemetry>();
	});

	const createTool = (canvasGroupsEnabled = false) =>
		createGetWorkflowBestPracticesTool(user, telemetry, { canvasGroupsEnabled });

	const textOf = (result: { content: Array<{ type: string; text?: string }> }) =>
		result.content.map((c) => c.text ?? '').join('\n');

	test('exposes the expected tool name and read-only annotations', () => {
		const tool = createTool();

		expect(tool.name).toBe('get_workflow_best_practices');
		expect(tool.config.annotations?.readOnlyHint).toBe(true);
		expect(tool.config.annotations?.destructiveHint).toBe(false);
		expect(tool.config.annotations?.idempotentHint).toBe(true);
		expect(tool.config.inputSchema?.technique).toBeDefined();
	});

	describe('technique input schema', () => {
		const techniqueSchema = () => createTool().config.inputSchema!.technique;

		test('accepts every technique key and the list sentinel', () => {
			const schema = techniqueSchema();

			for (const value of ['list', ...Object.values(WorkflowTechnique)]) {
				expect(schema.safeParse(value).success).toBe(true);
			}
		});

		test('names the accepted values when the technique is unknown', () => {
			const result = techniqueSchema().safeParse('webhook');

			expect(result.success).toBe(false);
			const message = result.error?.issues[0]?.message ?? '';
			expect(message).not.toBe('Invalid input');
			expect(message).toContain('list');
			expect(message).toContain(WorkflowTechnique.CHATBOT);
			expect(message).toContain(WorkflowTechnique.WEB_APP);
		});

		test('lists the accepted values in the parameter description', () => {
			const description = techniqueSchema().description ?? '';

			for (const value of ['list', ...Object.values(WorkflowTechnique)]) {
				expect(description).toContain(value);
			}
		});
	});

	test('returns the full technique catalog when technique="list"', async () => {
		const tool = createTool();
		const result = await tool.handler({ technique: 'list' }, {} as never);

		expect(structuredOf(result)?.technique).toBe('list');
		const available = structuredOf(result)?.availableTechniques as Array<{
			technique: string;
			description: string;
			hasDocumentation: boolean;
		}>;

		expect(available).toBeDefined();
		expect(available).toHaveLength(Object.keys(TechniqueDescription).length);

		const chatbotEntry = available.find((t) => t.technique === WorkflowTechnique.CHATBOT);
		expect(chatbotEntry?.hasDocumentation).toBe(true);

		const monitoringEntry = available.find((t) => t.technique === WorkflowTechnique.MONITORING);
		expect(monitoringEntry?.hasDocumentation).toBe(false);

		expect(telemetry.track).toHaveBeenCalledWith(
			USER_CALLED_MCP_TOOL_EVENT,
			expect.objectContaining({
				user_id: 'user-1',
				tool_name: 'get_workflow_best_practices',
				parameters: { technique: 'list' },
				results: expect.objectContaining({ success: true }),
			}),
		);
	});

	test('returns documentation for a technique that has a guide', async () => {
		const tool = createTool();
		const result = await tool.handler({ technique: WorkflowTechnique.CHATBOT }, {} as never);

		const expectedDoc = bestPracticesRegistry[WorkflowTechnique.CHATBOT]!.getDocumentation();

		expect(structuredOf(result)?.technique).toBe(WorkflowTechnique.CHATBOT);
		expect(structuredOf(result)?.documentation).toBe(expectedDoc);
		expect(result.content).toEqual([{ type: 'text', text: expectedDoc }]);
		expect(telemetry.track).toHaveBeenCalledWith(
			USER_CALLED_MCP_TOOL_EVENT,
			expect.objectContaining({
				results: expect.objectContaining({
					success: true,
					data: { technique: WorkflowTechnique.CHATBOT, hasDocumentation: true },
				}),
			}),
		);
	});

	test('returns a friendly message for a known technique without documentation', async () => {
		const tool = createTool();
		const result = await tool.handler({ technique: WorkflowTechnique.MONITORING }, {} as never);

		expect(structuredOf(result)?.technique).toBe(WorkflowTechnique.MONITORING);
		expect(structuredOf(result)?.documentation).toBeUndefined();
		expect(structuredOf(result)?.message).toContain('does not have detailed best-practices');
	});

	describe('node grouping guidance', () => {
		const listText = async (canvasGroupsEnabled: boolean) => {
			const result = await createTool(canvasGroupsEnabled).handler(
				{ technique: 'list' },
				{} as never,
			);
			return textOf(result);
		};

		describe('when canvasGroupsEnabled is true', () => {
			test('appends a grouping guidance section to the technique list', async () => {
				const text = await listText(true);

				expect(text).toContain('## Grouping');
				expect(text).toMatch(/sub-workflow/i); // groups vs sub-workflows
				expect(text).toMatch(/collapsed/i); // created collapsed by default
			});
		});

		describe('when canvasGroupsEnabled is false', () => {
			test('does not mention grouping in the technique list', async () => {
				const text = await listText(false);

				expect(text).not.toContain('## Grouping');
			});
		});
	});
});
