import { ModuleRegistry, Logger } from '@n8n/backend-common';
import { InstanceSettingsLoaderConfig } from '@n8n/config';
import { type AuthenticatedRequest } from '@n8n/db';
import { Body, Post, Get, Patch, RestController, GlobalScope } from '@n8n/decorators';
import type { Response } from 'express';

import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { EventService } from '@/events/event.service';
import { listQueryMiddleware } from '@/middlewares';
import type { ListQuery } from '@/requests';
import { WorkflowService } from '@/workflows/workflow.service';

import { UpdateAllowedRedirectUrisDto } from './dto/update-allowed-redirect-uris.dto';
import { UpdateMcpSettingsDto } from './dto/update-mcp-settings.dto';
import { UpdateWorkflowsAvailabilityDto } from './dto/update-workflows-availability.dto';
import { McpServerApiKeyService } from './mcp-api-key.service';
import { McpSettingsService } from './mcp.settings.service';

@RestController('/mcp')
export class McpSettingsController {
	constructor(
		private readonly mcpSettingsService: McpSettingsService,
		private readonly logger: Logger,
		private readonly moduleRegistry: ModuleRegistry,
		private readonly mcpServerApiKeyService: McpServerApiKeyService,
		private readonly workflowService: WorkflowService,
		private readonly instanceSettingsLoaderConfig: InstanceSettingsLoaderConfig,
		private readonly eventService: EventService,
	) {}

	@GlobalScope('mcp:manage')
	@Patch('/settings')
	async updateSettings(req: AuthenticatedRequest, _res: Response, @Body dto: UpdateMcpSettingsDto) {
		if (this.instanceSettingsLoaderConfig.mcpManagedByEnv) {
			throw new ForbiddenError('MCP settings are managed via environment variables');
		}

		if (dto.mcpAccessEnabled !== undefined) {
			await this.mcpSettingsService.setEnabled(dto.mcpAccessEnabled);
			this.eventService.emit('mcp-access-updated', {
				user: req.user,
				enabled: dto.mcpAccessEnabled,
			});
		}

		if (dto.autoExposeNewWorkflows !== undefined) {
			await this.mcpSettingsService.setAutoExposeNewWorkflows(dto.autoExposeNewWorkflows);
		}

		await this.refreshMcpModuleSettings();

		// Always return both values (read back from the source of truth) so the
		// client can sync local state without a follow-up module-settings fetch.
		const [mcpAccessEnabled, autoExposeNewWorkflows] = await Promise.all([
			this.mcpSettingsService.getEnabled(),
			this.mcpSettingsService.getAutoExposeNewWorkflows(),
		]);

		return { mcpAccessEnabled, autoExposeNewWorkflows };
	}

	@GlobalScope('mcpApiKey:create')
	@Get('/api-key')
	async getApiKeyForMcpServer(req: AuthenticatedRequest) {
		return await this.mcpServerApiKeyService.getOrCreateApiKey(req.user);
	}

	@GlobalScope('mcpApiKey:rotate')
	@Post('/api-key/rotate')
	async rotateApiKeyForMcpServer(req: AuthenticatedRequest) {
		return await this.mcpServerApiKeyService.rotateMcpServerApiKey(req.user);
	}

	@GlobalScope('mcp:manage')
	@Get('/oauth/allowed-redirect-uris')
	async getAllowedRedirectUris() {
		const uris = await this.mcpSettingsService.getAllowedRedirectUris();
		return { uris };
	}

	@GlobalScope('mcp:manage')
	@Patch('/oauth/allowed-redirect-uris')
	async updateAllowedRedirectUris(
		_req: AuthenticatedRequest,
		_res: Response,
		@Body dto: UpdateAllowedRedirectUrisDto,
	) {
		await this.mcpSettingsService.setAllowedRedirectUris(dto.uris);
		return { success: true };
	}

	@Get('/workflows', { middlewares: listQueryMiddleware })
	async getMcpEligibleWorkflows(req: ListQuery.Request, res: Response) {
		const options: ListQuery.Options = {
			...req.listQueryOptions,
			filter: {
				...req.listQueryOptions?.filter,
				isArchived: false,
				availableInMCP: false,
			},
		};

		const { workflows, count } = await this.workflowService.getMany(req.user, options, {
			// Only return workflows the user can edit
			requiredScopes: ['workflow:update'],
		});

		res.json({ count, data: workflows });
	}

	// Ideally we would use ProjectScope here but it only works if projectId is a URL parameter
	@Patch('/workflows/toggle-access')
	async toggleWorkflowsMCPAccess(
		req: AuthenticatedRequest,
		_res: Response,
		@Body dto: UpdateWorkflowsAvailabilityDto,
	) {
		const { changedWorkflows, ...result } = await this.mcpSettingsService.bulkSetAvailableInMCP(
			req.user,
			dto,
		);

		void this.mcpSettingsService.broadcastWorkflowMCPAvailabilityChanged(changedWorkflows);

		return result;
	}

	private async refreshMcpModuleSettings() {
		try {
			await this.moduleRegistry.refreshModuleSettings('mcp');
		} catch (error) {
			this.logger.warn('Failed to sync MCP settings to module registry', {
				cause: error instanceof Error ? error.message : String(error),
			});
		}
	}
}
