import { Service } from '@n8n/di';
import type { Thread, Author } from 'chat';

import { AgentCredentialIntegrationConfig } from '@n8n/api-types';
import type { ChatInstance } from './chat-integration.service';
import type { SuspendComponent } from './component-mapper';
import type {
	IntegrationAction,
	IntegrationActionResult,
	IntegrationContextQuery,
	IntegrationMessageContext,
	IntegrationToolConnectionDescriptor,
} from './integration-tools';

/** Per-connection context handed to AgentChatIntegration hooks. */
export interface AgentChatIntegrationContext {
	agentId: string;
	projectId: string;
	credentialId: string;
	credential: Record<string, unknown>;
	/** Returns the inbound webhook URL this n8n instance exposes for the given platform. */
	webhookUrlFor: (platform: string) => string;
}

/** Response shape returned by `handleUnauthenticatedWebhook`. */
export interface UnauthenticatedWebhookResponse {
	status: number;
	body: unknown;
}

export interface AgentChatIntegrationBuilderGuidance {
	capabilities: string[];
	useIntegrationWhen: string[];
	useNodeToolWhen: string[];
}

/**
 * A chat platform (Slack, Telegram, …) that an agent can be connected to.
 *
 * Encapsulates everything platform-specific in one place: adapter construction,
 * credential extraction, capability metadata used by the rich_interaction tool,
 * component normalization before rendering, and optional lifecycle hooks.
 *
 * The concrete subclasses live under `./platforms/`.
 */
export abstract class AgentChatIntegration {
	/** Platform identifier (`'slack'`, `'telegram'`, …). */
	abstract readonly type: string;

	/** Credential types accepted by the frontend selector. */
	abstract readonly credentialTypes: string[];

	// ---------------------------------------------------------------------------
	// FE display metadata — shown in the trigger-picker and integration cards.
	// Localizable copy (help text, connected confirmation) lives in the FE i18n
	// catalog keyed by `type`; only stable, brand-level metadata lives here.
	// ---------------------------------------------------------------------------

	/** Brand-name label shown in UI (not localized — e.g. "Slack", "Linear"). */
	abstract readonly displayLabel: string;

	/** Lucide icon name (from the shared icon set) for the integration card. */
	abstract readonly displayIcon: string;

	/**
	 * Builder-facing guidance returned by `list_integration_types`.
	 * This helps the builder choose between connecting the agent to a chat
	 * integration and adding a regular node/workflow tool for the same product.
	 */
	readonly builderGuidance?: AgentChatIntegrationBuilderGuidance;

	/**
	 * Component types this platform supports in rich_interaction cards.
	 * Omit to signal that the platform has no rich_interaction surface — the
	 * tool won't be injected into agents targeting this platform.
	 */
	readonly supportedComponents?: string[];

	/** Read-only context queries exposed through the generated integration context tool. */
	readonly contextQueries: IntegrationContextQuery[] = [
		'get_current_message_context',
		'get_current_subject',
	];

	/** Side-effecting actions exposed through the generated integration action tool. */
	readonly actions: IntegrationAction[] = ['respond'];

	/**
	 * True if this platform has a small callback_data limit (Telegram: 64 bytes).
	 * When true, buttons encode a short key that the bridge resolves via the
	 * CallbackStore instead of carrying the full payload.
	 */
	readonly needsShortCallbackData: boolean = false;

	/**
	 * True if the bridge should buffer streaming output and post it as a single
	 * message instead of streaming text deltas via post-and-edit.
	 */
	readonly disableStreaming: boolean = false;

	/**
	 * True if this integration must run on the leader main only.
	 *
	 * Polling-based platforms (e.g. Telegram in polling mode) require this so a
	 * single instance owns the long-poll loop — otherwise updates race between
	 * mains and either duplicate or get lost. Webhook-based platforms return
	 * false so any main can answer inbound webhooks (which the load balancer
	 * routes round-robin across all mains).
	 */
	requiresLeader(): boolean {
		return false;
	}

	/** Build the Chat SDK adapter for this platform. */
	abstract createAdapter(ctx: AgentChatIntegrationContext): Promise<unknown>;

	/**
	 * Handle a webhook request that arrives before an integration is connected
	 * (i.e. before credentials are configured). The canonical case is Slack's
	 * `url_verification` challenge — sent when the user creates a Slack app
	 * from the manifest, before they have pasted bot token / signing secret
	 * into n8n. Without this hook, the standard handler returns 404 and the
	 * user has to manually re-verify URLs after configuring the credential.
	 *
	 * Implementations inspect the parsed JSON body; return a response to send
	 * back, or undefined to fall through to the standard 404.
	 *
	 * Security note: this hook bypasses signature verification, so it must
	 * only echo non-sensitive data (e.g. a challenge token sent by the caller
	 * in the request itself).
	 */
	handleUnauthenticatedWebhook?(body: unknown): UnauthenticatedWebhookResponse | undefined;

	/**
	 * Optional hook run BEFORE the adapter is built. Use it to reject the
	 * connect early — e.g. a webhook-based platform checking that the
	 * credential isn't already claimed elsewhere. Throwing aborts the connect.
	 */
	onBeforeConnect?(ctx: AgentChatIntegrationContext): Promise<void>;

	/** Optional hook run AFTER `chat.initialize()`. Throwing triggers cleanup. */
	onAfterConnect?(ctx: AgentChatIntegrationContext): Promise<void>;

	/**
	 * Optional hook run BEFORE `chat.shutdown()` — use it to release any
	 * external state owned by this integration (e.g. Telegram `deleteWebhook`
	 * to free the bot for other applications). Runs only when the disconnect
	 * is user-initiated; peer mains reacting to a multi-main PubSub broadcast,
	 * graceful shutdowns, and leader-stepdown teardown all skip this hook so
	 * the cluster-wide state isn't released by every main in turn.
	 *
	 * Errors are logged by the caller and swallowed — local teardown always
	 * proceeds so a transient remote failure can't leak in-process resources.
	 */
	onBeforeDisconnect?(ctx: AgentChatIntegrationContext): Promise<void>;

	/**
	 * Optional per-platform component normalization (applied before toCard).
	 * Convert unsupported types into close-enough equivalents — e.g. Telegram
	 * turns select options into individual buttons.
	 */
	normalizeComponents?(components: SuspendComponent[]): SuspendComponent[];

	/**
	 * Optional per-platform thread ID formatting.
	 * Used to convert between the Chat SDK thread and our format.
	 */
	formatThreadId?: {
		fromSdk: (thread: Thread<unknown, unknown>) => string;
		toSdk: (threadId: string) => string;
	};

	/**
	 * Optional per-user authorisation check called on every inbound mention,
	 * subscribed message, and action before the bridge subscribes / executes.
	 * Default (no implementation): allow. Telegram uses this to enforce the
	 * Private-mode allowlist.
	 */
	isUserAllowed?(author: Author, settings: AgentCredentialIntegrationConfig | undefined): boolean;

	/**
	 * Execute a context query that this platform owns (e.g. Linear `get_issue`,
	 * Slack `search_users`). The central executor handles only the cross-platform
	 * message-context queries before delegating here.
	 *
	 * Return shape mirrors {@link IntegrationActionResult} — `{ ok: true, ... }`
	 * on success or `{ ok: false, error: { code, message } }` on failure.
	 */
	executeContextQuery?(params: PlatformContextQueryParams): Promise<unknown>;

	/**
	 * Execute a platform-specific action (e.g. Linear `create_issue`, Slack
	 * `add_reaction`). The central executor handles the cross-platform actions
	 * (`respond`, `send_dm`, `send_channel_message`) before delegating here.
	 *
	 * Return `undefined` to signal the action isn't owned by this platform — the
	 * caller then returns an `UNSUPPORTED_ACTION` error.
	 */
	executeAction?(params: PlatformActionParams): Promise<IntegrationActionResult | undefined>;
}

/** Per-platform context-query execution params. */
export interface PlatformContextQueryParams {
	chat: ChatInstance;
	descriptor: IntegrationToolConnectionDescriptor;
	query: IntegrationContextQuery;
	input: Record<string, unknown>;
}

/** Per-platform action-execution params. */
export interface PlatformActionParams {
	chat: ChatInstance;
	descriptor: IntegrationToolConnectionDescriptor;
	action: IntegrationAction;
	input: Record<string, unknown>;
	currentMessageContext?: IntegrationMessageContext;
}

/**
 * Singleton registry of AgentChatIntegration implementations.
 *
 * Platforms register themselves during module init (`agents.module.ts`).
 * Consumers (ChatIntegrationService, ComponentMapper, createRichInteractionTool,
 * AgentChatBridge) look up integrations by type.
 */
@Service()
export class ChatIntegrationRegistry {
	private readonly integrations = new Map<string, AgentChatIntegration>();

	register(integration: AgentChatIntegration): void {
		this.integrations.set(integration.type, integration);
	}

	get(type: string): AgentChatIntegration | undefined {
		return this.integrations.get(type);
	}

	require(type: string): AgentChatIntegration {
		const integration = this.integrations.get(type);
		if (!integration) throw new Error(`Unknown integration type: ${type}`);
		return integration;
	}

	list(): AgentChatIntegration[] {
		return [...this.integrations.values()];
	}
}
