import type { Scope } from '@n8n/permissions';
import {
	CHAT_TOOL_NODE_TYPE,
	type ChunkType,
	DATA_TABLE_TOOL_NODE_TYPE,
	type INode,
	INodeSchema,
	WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE,
} from 'n8n-workflow';
import { z } from 'zod';

import { Z } from './zod-class';

/**
 * Supported AI model providers
 */
export const chatHubLLMProviderSchema = z.enum([
	'openai',
	'anthropic',
	'google',
	'azureOpenAi',
	'azureEntraId',
	'ollama',
	'awsBedrock',
	'vercelAiGateway',
	'xAiGrok',
	'groq',
	'openRouter',
	'deepSeek',
	'cohere',
	'mistralCloud',
]);
export type ChatHubLLMProvider = z.infer<typeof chatHubLLMProviderSchema>;

export interface ChatHubAgentKnowledgeItem {
	id: string;
	type: 'embedding';
	provider: ChatHubLLMProvider;
	fileName: string;
	mimeType: string;
}

/**
 * Schema for icon or emoji representation
 */
export const agentIconOrEmojiSchema = z.discriminatedUnion('type', [
	z.object({
		type: z.literal('icon'),
		value: z.string(),
	}),
	z.object({
		type: z.literal('emoji'),
		value: z.string(),
	}),
]);
export type AgentIconOrEmoji = z.infer<typeof agentIconOrEmojiSchema>;

export const chatHubProviderSchema = z.enum([
	...chatHubLLMProviderSchema.options,
	'n8n',
	'custom-agent',
] as const);
export type ChatHubProvider = z.infer<typeof chatHubProviderSchema>;

/**
 * Map of providers to their credential types
 * Only LLM providers (openai, anthropic, google) have credentials
 */
export const PROVIDER_CREDENTIAL_TYPE_MAP: Record<
	Exclude<ChatHubProvider, 'n8n' | 'custom-agent'>,
	string
> = {
	openai: 'openAiApi',
	anthropic: 'anthropicApi',
	google: 'googlePalmApi',
	ollama: 'ollamaApi',
	azureOpenAi: 'azureOpenAiApi',
	azureEntraId: 'azureEntraCognitiveServicesOAuth2Api',
	awsBedrock: 'aws',
	vercelAiGateway: 'vercelAiGatewayApi',
	xAiGrok: 'xAiApi',
	groq: 'groqApi',
	openRouter: 'openRouterApi',
	deepSeek: 'deepSeekApi',
	cohere: 'cohereApi',
	mistralCloud: 'mistralCloudApi',
};

/**
 * Chat Hub conversation model configuration
 */
const openAIModelSchema = z.object({
	provider: z.literal('openai'),
	model: z.string(),
});

const anthropicModelSchema = z.object({
	provider: z.literal('anthropic'),
	model: z.string(),
});

const googleModelSchema = z.object({
	provider: z.literal('google'),
	model: z.string(),
});

const azureOpenAIModelSchema = z.object({
	provider: z.literal('azureOpenAi'),
	model: z.string(),
});

const azureEntraIdModelSchema = z.object({
	provider: z.literal('azureEntraId'),
	model: z.string(),
});

const ollamaModelSchema = z.object({
	provider: z.literal('ollama'),
	model: z.string(),
});

const awsBedrockModelSchema = z.object({
	provider: z.literal('awsBedrock'),
	model: z.string(),
});

const vercelAiGatewaySchema = z.object({
	provider: z.literal('vercelAiGateway'),
	model: z.string(),
});

const xAiGrokModelSchema = z.object({
	provider: z.literal('xAiGrok'),
	model: z.string(),
});

const groqModelSchema = z.object({
	provider: z.literal('groq'),
	model: z.string(),
});

const openRouterModelSchema = z.object({
	provider: z.literal('openRouter'),
	model: z.string(),
});

const deepSeekModelSchema = z.object({
	provider: z.literal('deepSeek'),
	model: z.string(),
});

const cohereModelSchema = z.object({
	provider: z.literal('cohere'),
	model: z.string(),
});

const mistralCloudModelSchema = z.object({
	provider: z.literal('mistralCloud'),
	model: z.string(),
});

const n8nModelSchema = z.object({
	provider: z.literal('n8n'),
	workflowId: z.string(),
});

const chatAgentSchema = z.object({
	provider: z.literal('custom-agent'),
	agentId: z.string(),
});

export const chatHubConversationModelSchema = z.discriminatedUnion('provider', [
	openAIModelSchema,
	anthropicModelSchema,
	googleModelSchema,
	azureOpenAIModelSchema,
	azureEntraIdModelSchema,
	ollamaModelSchema,
	awsBedrockModelSchema,
	vercelAiGatewaySchema,
	xAiGrokModelSchema,
	groqModelSchema,
	openRouterModelSchema,
	deepSeekModelSchema,
	cohereModelSchema,
	mistralCloudModelSchema,
	n8nModelSchema,
	chatAgentSchema,
]);

export type ChatHubOpenAIModel = z.infer<typeof openAIModelSchema>;
export type ChatHubAnthropicModel = z.infer<typeof anthropicModelSchema>;
export type ChatHubGoogleModel = z.infer<typeof googleModelSchema>;
export type ChatHubAzureOpenAIModel = z.infer<typeof azureOpenAIModelSchema>;
export type ChatHubAzureEntraIdModel = z.infer<typeof azureEntraIdModelSchema>;
export type ChatHubOllamaModel = z.infer<typeof ollamaModelSchema>;
export type ChatHubAwsBedrockModel = z.infer<typeof awsBedrockModelSchema>;
export type ChatHubVercelAiGatewayModel = z.infer<typeof vercelAiGatewaySchema>;
export type ChatHubXAiGrokModel = z.infer<typeof xAiGrokModelSchema>;
export type ChatHubGroqModel = z.infer<typeof groqModelSchema>;
export type ChatHubOpenRouterModel = z.infer<typeof openRouterModelSchema>;
export type ChatHubDeepSeekModel = z.infer<typeof deepSeekModelSchema>;
export type ChatHubCohereModel = z.infer<typeof cohereModelSchema>;
export type ChatHubMistralCloudModel = z.infer<typeof mistralCloudModelSchema>;
export type ChatHubBaseLLMModel =
	| ChatHubOpenAIModel
	| ChatHubAnthropicModel
	| ChatHubGoogleModel
	| ChatHubAzureOpenAIModel
	| ChatHubAzureEntraIdModel
	| ChatHubOllamaModel
	| ChatHubAwsBedrockModel
	| ChatHubVercelAiGatewayModel
	| ChatHubXAiGrokModel
	| ChatHubGroqModel
	| ChatHubOpenRouterModel
	| ChatHubDeepSeekModel
	| ChatHubCohereModel
	| ChatHubMistralCloudModel;

export type ChatHubN8nModel = z.infer<typeof n8nModelSchema>;
export type ChatHubCustomAgentModel = z.infer<typeof chatAgentSchema>;
export type ChatHubConversationModel = z.infer<typeof chatHubConversationModelSchema>;

/**
 * Request schema for fetching available chat models
 * Maps provider names to credential IDs (null if no credential available)
 */
export const chatModelsRequestSchema = z.object({
	credentials: z.record(chatHubProviderSchema, z.string().nullable()),
});

export type ChatModelsRequest = z.infer<typeof chatModelsRequestSchema>;

export interface ChatModelMetadataDto {
	allowFileUploads: boolean;
	allowedFilesMimeTypes: string;
	priority?: number; // Order on the model picker list, higher means first, default 0
	capabilities: {
		functionCalling: boolean;
	};
	available: boolean;
	scopes?: Scope[];
}

export interface ChatModelDto {
	model: ChatHubConversationModel;
	name: string;
	description: string | null;
	icon: AgentIconOrEmoji | null;
	updatedAt: string | null;
	createdAt: string | null;
	metadata: ChatModelMetadataDto;
	groupName: string | null;
	groupIcon: AgentIconOrEmoji | null;
	suggestedPrompts?: Array<{ text: string; icon?: AgentIconOrEmoji }>;
}

/**
 * Response type for fetching available chat models
 */
export type ChatModelsResponse = Record<
	ChatHubProvider,
	{
		models: ChatModelDto[];
		error?: string;
	}
>;

export const emptyChatModelsResponse: ChatModelsResponse = {
	openai: { models: [] },
	anthropic: { models: [] },
	google: { models: [] },
	azureOpenAi: { models: [] },
	azureEntraId: { models: [] },
	ollama: { models: [] },
	awsBedrock: { models: [] },
	vercelAiGateway: { models: [] },
	xAiGrok: { models: [] },
	groq: { models: [] },
	openRouter: { models: [] },
	deepSeek: { models: [] },
	cohere: { models: [] },
	mistralCloud: { models: [] },
	n8n: { models: [] },
	// eslint-disable-next-line @typescript-eslint/naming-convention
	'custom-agent': { models: [] },
};

/**
 * Chat attachment schema for incoming requests.
 * Requires base64 data and fileName.
 * MimeType, fileType, fileExtension, and fileSize are populated server-side.
 */
export const chatAttachmentSchema = z.object({
	data: z.string(),
	mimeType: z.string(),
	fileName: z.string(),
});

export const isValidTimeZone = (tz: string): boolean => {
	try {
		// Throws if invalid timezone
		new Intl.DateTimeFormat('en-US', { timeZone: tz });
		return true;
	} catch {
		return false;
	}
};

export const StrictTimeZoneSchema = z
	.string()
	.min(1)
	.max(50)
	.regex(/^[A-Za-z0-9_/+-]+$/)
	.refine(isValidTimeZone, {
		message: 'Unknown or invalid time zone',
	});

export const TimeZoneSchema = StrictTimeZoneSchema.optional().catch(undefined);

export type ChatAttachment = z.infer<typeof chatAttachmentSchema>;

export class ChatHubSendMessageRequest extends Z.class({
	messageId: z.string().uuid(),
	sessionId: z.string().uuid(),
	message: z.string(),
	model: chatHubConversationModelSchema,
	previousMessageId: z.string().uuid().nullable(),
	credentials: z.record(
		z.object({
			id: z.string(),
			name: z.string(),
		}),
	),
	attachments: z.array(chatAttachmentSchema),
	agentName: z.string().optional(),
	timeZone: TimeZoneSchema,
}) {}

export class ChatHubRegenerateMessageRequest extends Z.class({
	model: chatHubConversationModelSchema,
	credentials: z.record(
		z.object({
			id: z.string(),
			name: z.string(),
		}),
	),
	timeZone: TimeZoneSchema,
}) {}

export class ChatHubEditMessageRequest extends Z.class({
	message: z.string(),
	messageId: z.string().uuid(),
	model: chatHubConversationModelSchema,
	credentials: z.record(
		z.object({
			id: z.string(),
			name: z.string(),
		}),
	),
	newAttachments: z.array(chatAttachmentSchema),
	keepAttachmentIndices: z.array(z.number()),
	timeZone: TimeZoneSchema,
}) {}

export class ChatHubUpdateConversationRequest extends Z.class({
	title: z.string().optional(),
	credentialId: z.string().max(36).optional(),
	agent: z
		.object({
			model: chatHubConversationModelSchema,
			name: z.string(),
		})
		.optional(),
	toolIds: z.array(z.string().uuid()).optional(),
}) {}

export type ChatHubMessageType = 'human' | 'ai' | 'system' | 'tool' | 'generic';
export type ChatHubMessageStatus = 'success' | 'error' | 'running' | 'cancelled' | 'waiting';

export type ChatSessionId = string; // UUID
export type ChatMessageId = string; // UUID

export interface ChatHubSessionDto {
	id: ChatSessionId;
	title: string;
	ownerId: string;
	lastMessageAt: string | null;
	credentialId: string | null;
	provider: ChatHubProvider | null;
	model: string | null;
	workflowId: string | null;
	agentId: string | null;
	agentName: string;
	agentIcon: AgentIconOrEmoji | null;
	createdAt: string;
	updatedAt: string;
	toolIds: string[];
}

export type ChatMessageContentChunk =
	| { type: 'text'; content: string }
	| { type: 'hidden'; content: string }
	| {
			type: 'artifact-create';
			content: string;
			command: ChatArtifactCreateCommand;
			isIncomplete: boolean;
	  }
	| {
			type: 'artifact-edit';
			content: string;
			command: ChatArtifactEditCommand;
			isIncomplete: boolean;
	  }
	| {
			type: 'with-buttons';
			content: string;
			buttons: ChatHubMessageButton[];
			blockUserInput: boolean;
	  };

export interface ChatHubMessageDto {
	id: ChatMessageId;
	sessionId: ChatSessionId;
	type: ChatHubMessageType;
	name: string;
	content: ChatMessageContentChunk[];
	provider: ChatHubProvider | null;
	model: string | null;
	workflowId: string | null;
	agentId: string | null;
	executionId: number | null;
	status: ChatHubMessageStatus;
	createdAt: string;
	updatedAt: string;

	previousMessageId: ChatMessageId | null;
	retryOfMessageId: ChatMessageId | null;
	revisionOfMessageId: ChatMessageId | null;

	attachments: Array<{ fileName?: string; mimeType?: string }>;
}

export class ChatHubConversationsRequest extends Z.class({
	limit: z.coerce.number().int().min(1).max(100),
	cursor: z.string().uuid().optional(),
}) {}

export interface ChatHubConversationsResponse {
	data: ChatHubSessionDto[];
	nextCursor: string | null;
	hasMore: boolean;
}

export interface ChatHubConversationDto {
	messages: Record<ChatMessageId, ChatHubMessageDto>;
}

export interface ChatHubConversationResponse {
	session: ChatHubSessionDto;
	conversation: ChatHubConversationDto;
}

export const suggestedPromptSchema = z.object({
	text: z.string().min(1).max(256),
	icon: agentIconOrEmojiSchema.optional(),
});

export const suggestedPromptsSchema = z.array(suggestedPromptSchema).max(6);

export type SuggestedPrompt = z.infer<typeof suggestedPromptSchema>;

export interface ChatHubAgentDto {
	id: string;
	name: string;
	description: string | null;
	icon: AgentIconOrEmoji | null;
	suggestedPrompts: SuggestedPrompt[];
	systemPrompt: string;
	ownerId: string;
	credentialId: string | null;
	provider: ChatHubLLMProvider;
	model: string;
	files: ChatHubAgentKnowledgeItem[];
	toolIds: string[];
	createdAt: string;
	updatedAt: string;
}

export class ChatHubCreateAgentRequest extends Z.class({
	name: z.string().min(1).max(128),
	description: z.string().max(512).optional(),
	icon: agentIconOrEmojiSchema,
	suggestedPrompts: suggestedPromptsSchema.optional(),
	systemPrompt: z.string().min(1),
	credentialId: z.string(),
	provider: chatHubLLMProviderSchema,
	model: z.string().max(64),
	toolIds: z.array(z.string().uuid()),
}) {}

export class ChatHubUpdateAgentRequest extends Z.class({
	name: z.string().min(1).max(128).optional(),
	description: z.string().max(512).optional(),
	icon: agentIconOrEmojiSchema.optional(),
	suggestedPrompts: suggestedPromptsSchema.optional(),
	systemPrompt: z.string().min(1).optional(),
	credentialId: z.string().optional(),
	provider: chatHubLLMProviderSchema.optional(),
	model: z.string().max(64).optional(),
	toolIds: z.array(z.string().uuid()).optional(),
}) {}

export interface MessageChunk {
	type: ChunkType;
	content?: string;
	metadata: {
		timestamp: number;
		messageId: ChatMessageId;
		previousMessageId: ChatMessageId | null;
		retryOfMessageId: ChatMessageId | null;
		executionId: number | null;
	};
}

const chatProviderSettingsSchema = z.object({
	provider: chatHubLLMProviderSchema,
	enabled: z.boolean().optional(),
	credentialId: z.string().nullable(),
	// Empty list = all models allowed
	allowedModels: z.array(
		z.object({
			displayName: z.string(),
			model: z.string(),
			isManual: z.boolean().optional(),
		}),
	),
	createdAt: z.string(),
	updatedAt: z.string().nullable(),
});

export type ChatProviderSettingsDto = z.infer<typeof chatProviderSettingsSchema>;

export class UpdateChatSettingsRequest extends Z.class({
	payload: chatProviderSettingsSchema,
}) {}

export interface ChatHubModuleSettings {
	enabled: boolean;
	providers: Record<ChatHubLLMProvider, ChatProviderSettingsDto>;
}

/**
 * Response returned immediately when sending a message via WebSocket streaming.
 * Message IDs are not included as they come via WebSocket events (chatHubStreamBegin).
 */
export interface ChatSendMessageResponse {
	/** Status indicating streaming has started */
	status: 'streaming';
}

/**
 * Request query parameters for reconnecting to a chat stream
 */
export class ChatReconnectRequest extends Z.class({
	lastSequence: z.coerce.number().int().min(0).optional(),
}) {}

/**
 * Response containing pending chunks for reconnection replay
 */
export interface ChatReconnectResponse {
	/** Whether there is an active stream for this session */
	hasActiveStream: boolean;
	/** Current message ID being streamed, if any */
	currentMessageId: ChatMessageId | null;
	/** Pending chunks that were missed during disconnection */
	pendingChunks: Array<{
		sequenceNumber: number;
		content: string;
	}>;
	/** Last sequence number received by client (for gap detection) */
	lastSequenceNumber: number;
}

/**
 * Artifact generated during chat interaction
 */
export interface ChatArtifact {
	title: string;
	/**
	 * Document type (html, md, csv, js etc.)
	 */
	type: string;
	content: string;
}

export interface ChatArtifactCreateCommand {
	title: string;
	type: string;
	content: string;
}

export interface ChatArtifactEditCommand {
	title: string;
	oldString: string;
	newString: string;
	replaceAll: boolean;
}

/**
 * Button shown in a chat message
 */
export const chatHubMessageButtonSchema = z.object({
	text: z.string(),
	link: z.string(),
	type: z.enum(['primary', 'secondary']),
});

export type ChatHubMessageButton = z.infer<typeof chatHubMessageButtonSchema>;

/**
 * Structured message with buttons, sent from
 * Chat node in "Send and Wait for Response" mode for HITL approvals
 */
export const chatHubMessageWithButtonsSchema = z.object({
	type: z.literal('with-buttons'),
	text: z.string(),
	blockUserInput: z.boolean(),
	buttons: z.array(chatHubMessageButtonSchema).min(1),
});

export type ChatHubMessageWithButtons = z.infer<typeof chatHubMessageWithButtonsSchema>;

/**
 * DTO for a configured chat hub tool
 */
export interface ChatHubToolDto {
	definition: INode;
	enabled: boolean;
}

/** Tool types blocked for ALL users in the Chat Hub. */
export const ALWAYS_BLOCKED_CHAT_HUB_TOOL_TYPES: string[] = [CHAT_TOOL_NODE_TYPE];

/** Additional tool types blocked for chat-only users (global:chatUser). */
export const CHAT_USER_BLOCKED_CHAT_HUB_TOOL_TYPES: string[] = [
	WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE,
	DATA_TABLE_TOOL_NODE_TYPE,
];

/**
 * Request schema for creating a chat hub tool
 */
export class ChatHubCreateToolRequest extends Z.class({
	definition: INodeSchema,
}) {}

/**
 * Request schema for updating a chat hub tool
 */
export class ChatHubUpdateToolRequest extends Z.class({
	definition: INodeSchema.optional(),
	enabled: z.boolean().optional(),
}) {}
