import type { Database } from "@api/db";
import type { ConversationRecord } from "@api/db/mutations/conversation";
import { conversation } from "@api/db/schema";
import { createModelRaw, createModelRawForWebsite, generateText } from "@api/lib/ai";
import { ingestAiCreditUsage } from "@api/lib/ai-credits/polar-meter";
import {
	recordOpenRouterByokFailure,
	recordOpenRouterByokSuccess,
	type OpenRouterBillingSource,
	type WebsiteOpenRouterContext,
} from "@api/lib/openrouter-byok/resolver";
import { emitConversationTranslationUpdate } from "@api/utils/conversation-realtime";
import {
	getPrimaryLanguageTag,
	normalizeLanguageTag,
	shouldTranslateBetweenLanguages,
} from "@cossistant/core";
import type { TimelinePartTranslation } from "@cossistant/types/api/timeline-item";
import { and, eq } from "drizzle-orm";

export const AUTO_TRANSLATE_MODEL_ID = "google/gemini-2.5-flash-lite";
const AUTO_TRANSLATE_TIMEOUT_MS = 4000;

type DetectionConfidence = "high" | "medium" | "low";

export type LanguageDetectionResult = {
	language: string | null;
	confidence: DetectionConfidence;
	source: "script" | "stopword" | "hint" | "unknown";
};

export type TranslationResult =
	| {
			status: "translated";
			text: string;
			sourceLanguage: string;
			targetLanguage: string;
			modelId: string;
			billingSource?: OpenRouterBillingSource;
	  }
	| {
			status: "not_needed" | "skipped";
			reason:
				| "same_language"
				| "missing_language"
				| "too_short"
				| "non_linguistic"
				| "empty";
			sourceLanguage: string | null;
			targetLanguage: string | null;
	  }
	| {
			status: "failed";
			reason: "timeout" | "provider_error" | "empty_result";
			sourceLanguage: string | null;
			targetLanguage: string | null;
			error?: unknown;
	  };

export type PreparedInboundTranslation = {
	visitorLanguage: string | null;
	translationPart: TimelinePartTranslation | null;
	translationResult: TranslationResult;
};

export type PreparedOutboundTranslation = {
	sourceLanguage: string | null;
	translationPart: TimelinePartTranslation | null;
	translationResult: TranslationResult;
};

export type TranslationFinalizeResult =
	| {
			status: "activated";
			visitorLanguage: string | null;
			translationActivatedAt: string | null;
			translationChargedAt: string | null;
			visitorTitle: string | null;
			visitorTitleLanguage: string | null;
	  }
	| {
			status: "language_updated";
			visitorLanguage: string;
	  }
	| {
			status: "noop";
	  };

const STOPWORDS: Record<string, string[]> = {
	en: [
		"the",
		"and",
		"you",
		"your",
		"please",
		"help",
		"hello",
		"thanks",
		"thank",
		"can",
		"need",
		"my",
	],
	es: [
		"hola",
		"gracias",
		"por",
		"para",
		"que",
		"necesito",
		"ayuda",
		"puedo",
		"mi",
		"estoy",
		"el",
		"la",
	],
	fr: [
		"bonjour",
		"merci",
		"pour",
		"avec",
		"besoin",
		"aide",
		"mon",
		"est",
		"une",
		"le",
		"la",
		"et",
	],
	de: [
		"danke",
		"bitte",
		"hilfe",
		"mein",
		"ich",
		"nicht",
		"kann",
		"der",
		"die",
		"das",
		"und",
	],
	pt: [
		"ola",
		"olá",
		"obrigado",
		"obrigada",
		"preciso",
		"ajuda",
		"para",
		"com",
		"minha",
		"meu",
		"estou",
	],
	it: [
		"ciao",
		"grazie",
		"per",
		"aiuto",
		"sono",
		"non",
		"mio",
		"mia",
		"il",
		"la",
		"che",
	],
	nl: [
		"hallo",
		"dank",
		"help",
		"voor",
		"mijn",
		"ik",
		"niet",
		"kan",
		"de",
		"het",
		"een",
	],
};

const SCRIPT_PATTERNS: Array<{ language: string; pattern: RegExp }> = [
	{ language: "th", pattern: /[\u0E00-\u0E7F]/u },
	{ language: "ru", pattern: /[\u0400-\u04FF]/u },
	{ language: "ar", pattern: /[\u0600-\u06FF]/u },
	{ language: "he", pattern: /[\u0590-\u05FF]/u },
	{ language: "ko", pattern: /[\uAC00-\uD7AF]/u },
	{ language: "ja", pattern: /[\u3040-\u30FF]/u },
	{ language: "hi", pattern: /[\u0900-\u097F]/u },
	{ language: "zh", pattern: /[\u4E00-\u9FFF]/u },
];

function tokenizeWords(text: string): string[] {
	return text.toLowerCase().match(/\p{Letter}+/gu) ?? [];
}

function isNonLinguistic(text: string): boolean {
	const letters = text.match(/\p{Letter}/gu) ?? [];
	return letters.length === 0;
}

function isTooShortForAutoTranslate(text: string): boolean {
	return text.trim().length < 3;
}

function scoreLatinLanguage(
	text: string
): { language: string; score: number } | null {
	const words = tokenizeWords(text);
	if (words.length === 0) {
		return null;
	}

	let best: { language: string; score: number } | null = null;

	for (const [language, stopwords] of Object.entries(STOPWORDS)) {
		const score = words.reduce(
			(total, word) => total + (stopwords.includes(word) ? 1 : 0),
			0
		);

		if (!best || score > best.score) {
			best = { language, score };
		}
	}

	return best;
}

export function detectMessageLanguage(params: {
	text: string;
	hintLanguage?: string | null;
}): LanguageDetectionResult {
	const text = params.text.trim();
	const normalizedHint = normalizeLanguageTag(params.hintLanguage);

	if (!text) {
		return {
			language: normalizedHint,
			confidence: normalizedHint ? "low" : "low",
			source: normalizedHint ? "hint" : "unknown",
		};
	}

	for (const script of SCRIPT_PATTERNS) {
		if (script.pattern.test(text)) {
			return {
				language: script.language,
				confidence: "high",
				source: "script",
			};
		}
	}

	const latin = scoreLatinLanguage(text);
	if (latin && latin.score >= 2) {
		return {
			language: latin.language,
			confidence: latin.score >= 3 ? "high" : "medium",
			source: "stopword",
		};
	}

	if (latin && latin.score === 1 && normalizedHint) {
		return {
			language: normalizedHint,
			confidence: "low",
			source: "hint",
		};
	}

	if (normalizedHint) {
		return {
			language: normalizedHint,
			confidence: "low",
			source: "hint",
		};
	}

	return {
		language: null,
		confidence: "low",
		source: "unknown",
	};
}

function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
	return new Promise<T>((resolve, reject) => {
		const timer = setTimeout(() => {
			reject(new Error("translation_timeout"));
		}, timeoutMs);

		promise.then(
			(value) => {
				clearTimeout(timer);
				resolve(value);
			},
			(error) => {
				clearTimeout(timer);
				reject(error);
			}
		);
	});
}

export async function maybeTranslateText(params: {
	text: string;
	sourceLanguage?: string | null;
	targetLanguage?: string | null;
	timeoutMs?: number;
	aiContext?: WebsiteOpenRouterContext;
}): Promise<TranslationResult> {
	const trimmedText = params.text.trim();
	const sourceLanguage = normalizeLanguageTag(params.sourceLanguage);
	const targetLanguage = normalizeLanguageTag(params.targetLanguage);

	if (!trimmedText) {
		return {
			status: "skipped",
			reason: "empty",
			sourceLanguage,
			targetLanguage,
		};
	}

	if (!(sourceLanguage && targetLanguage)) {
		return {
			status: "skipped",
			reason: "missing_language",
			sourceLanguage,
			targetLanguage,
		};
	}

	if (!shouldTranslateBetweenLanguages(sourceLanguage, targetLanguage)) {
		return {
			status: "not_needed",
			reason: "same_language",
			sourceLanguage,
			targetLanguage,
		};
	}

	if (isNonLinguistic(trimmedText)) {
		return {
			status: "skipped",
			reason: "non_linguistic",
			sourceLanguage,
			targetLanguage,
		};
	}

	if (isTooShortForAutoTranslate(trimmedText)) {
		return {
			status: "skipped",
			reason: "too_short",
			sourceLanguage,
			targetLanguage,
		};
	}

	let billingSource: OpenRouterBillingSource | undefined;

	try {
		const modelResolution = params.aiContext
			? await createModelRawForWebsite(AUTO_TRANSLATE_MODEL_ID, params.aiContext)
			: null;
		billingSource = modelResolution?.billingSource;

		const result = await withTimeout(
			generateText({
				model: modelResolution?.model ?? createModelRaw(AUTO_TRANSLATE_MODEL_ID),
				temperature: 0,
				system: `Translate MESSAGE into ${targetLanguage}. The message probably uses ${sourceLanguage}. If MESSAGE is already in ${targetLanguage}, return it unchanged. Return only the translated message. Preserve markdown, URLs, code blocks, placeholders, emoji, punctuation, and line breaks. Do not add quotes, labels, or explanations.`,
				prompt: trimmedText,
			}),
			params.timeoutMs ?? AUTO_TRANSLATE_TIMEOUT_MS
		);
		const translatedText = result.text.trim();

		if (!translatedText) {
			if (params.aiContext && billingSource) {
				await recordOpenRouterByokFailure({
					context: params.aiContext,
					billingSource,
					error: new Error("empty_translation_result"),
				});
			}

			return {
				status: "failed",
				reason: "empty_result",
				sourceLanguage,
				targetLanguage,
			};
		}

		if (params.aiContext && billingSource) {
			await recordOpenRouterByokSuccess({
				context: params.aiContext,
				billingSource,
			});
		}

		return {
			status: "translated",
			text: translatedText,
			sourceLanguage,
			targetLanguage,
			modelId: AUTO_TRANSLATE_MODEL_ID,
			billingSource,
		};
	} catch (error) {
		if (params.aiContext && billingSource) {
			await recordOpenRouterByokFailure({
				context: params.aiContext,
				billingSource,
				error,
			});
		}

		const reason =
			error instanceof Error && error.message === "translation_timeout"
				? "timeout"
				: "provider_error";

		return {
			status: "failed",
			reason,
			sourceLanguage,
			targetLanguage,
			error,
		};
	}
}

export function buildTranslationPart(params: {
	text: string;
	sourceLanguage: string;
	targetLanguage: string;
	audience: "team" | "visitor";
	mode: "auto" | "manual";
	modelId?: string | null;
}): TimelinePartTranslation {
	return {
		type: "translation",
		text: params.text,
		sourceLanguage: params.sourceLanguage,
		targetLanguage: params.targetLanguage,
		audience: params.audience,
		mode: params.mode,
		modelId: params.modelId ?? null,
	};
}

export function didTranslationSucceed(
	result: TranslationResult
): result is Extract<TranslationResult, { status: "translated" }> {
	return result.status === "translated";
}

export async function prepareInboundVisitorTranslation(params: {
	text: string;
	websiteDefaultLanguage: string;
	visitorLanguageHint?: string | null;
	mode: "auto" | "manual";
	autoTranslateEnabled: boolean;
	aiContext?: WebsiteOpenRouterContext;
}): Promise<PreparedInboundTranslation> {
	const detection = detectMessageLanguage({
		text: params.text,
		hintLanguage: params.visitorLanguageHint,
	});
	const visitorLanguage =
		normalizeLanguageTag(detection.language) ??
		normalizeLanguageTag(params.visitorLanguageHint);

	if (!params.autoTranslateEnabled) {
		return {
			visitorLanguage,
			translationPart: null,
			translationResult: {
				status: "skipped",
				reason: "missing_language",
				sourceLanguage: visitorLanguage,
				targetLanguage: normalizeLanguageTag(params.websiteDefaultLanguage),
			},
		};
	}

	const translationResult = await maybeTranslateText({
		text: params.text,
		sourceLanguage: visitorLanguage,
		targetLanguage: params.websiteDefaultLanguage,
		aiContext: params.aiContext,
	});

	return {
		visitorLanguage,
		translationPart: didTranslationSucceed(translationResult)
			? buildTranslationPart({
					text: translationResult.text,
					sourceLanguage: translationResult.sourceLanguage,
					targetLanguage: translationResult.targetLanguage,
					audience: "team",
					mode: params.mode,
					modelId: translationResult.modelId,
				})
			: null,
		translationResult,
	};
}

export async function prepareOutboundVisitorTranslation(params: {
	text: string;
	sourceLanguage: string;
	visitorLanguage?: string | null;
	mode: "auto" | "manual";
	aiContext?: WebsiteOpenRouterContext;
}): Promise<PreparedOutboundTranslation> {
	const detection = detectMessageLanguage({
		text: params.text,
		hintLanguage: params.sourceLanguage,
	});
	const sourceLanguage =
		normalizeLanguageTag(detection.language) ??
		normalizeLanguageTag(params.sourceLanguage);

	const translationResult = await maybeTranslateText({
		text: params.text,
		sourceLanguage,
		targetLanguage: params.visitorLanguage,
		aiContext: params.aiContext,
	});

	return {
		sourceLanguage,
		translationPart: didTranslationSucceed(translationResult)
			? buildTranslationPart({
					text: translationResult.text,
					sourceLanguage: translationResult.sourceLanguage,
					targetLanguage: translationResult.targetLanguage,
					audience: "visitor",
					mode: params.mode,
					modelId: translationResult.modelId,
				})
			: null,
		translationResult,
	};
}

export async function syncConversationVisitorTitle(params: {
	db: Database;
	conversationId: string;
	organizationId: string;
	websiteId: string;
	title: string | null;
	websiteDefaultLanguage: string;
	visitorLanguage?: string | null;
	autoTranslateEnabled: boolean;
	aiContext?: WebsiteOpenRouterContext;
}): Promise<{
	visitorTitle: string | null;
	visitorTitleLanguage: string | null;
}> {
	const normalizedTitle = params.title?.trim() ?? "";
	const normalizedVisitorLanguage = normalizeLanguageTag(
		params.visitorLanguage
	);
	const shouldTranslateTitle =
		params.autoTranslateEnabled &&
		normalizedTitle.length > 0 &&
		shouldTranslateBetweenLanguages(
			params.websiteDefaultLanguage,
			normalizedVisitorLanguage
		);

	const translationResult = shouldTranslateTitle
		? await maybeTranslateText({
				text: normalizedTitle,
				sourceLanguage: params.websiteDefaultLanguage,
				targetLanguage: normalizedVisitorLanguage,
				aiContext: params.aiContext,
			})
		: null;

	const visitorTitle =
		translationResult && didTranslationSucceed(translationResult)
			? translationResult.text
			: null;
	const visitorTitleLanguage =
		visitorTitle && translationResult
			? translationResult.targetLanguage
			: normalizedVisitorLanguage;

	const [updated] = await params.db
		.update(conversation)
		.set({
			visitorTitle,
			visitorTitleLanguage: visitorTitleLanguage ?? null,
			updatedAt: new Date().toISOString(),
		})
		.where(
			and(
				eq(conversation.id, params.conversationId),
				eq(conversation.organizationId, params.organizationId),
				eq(conversation.websiteId, params.websiteId)
			)
		)
		.returning({
			visitorTitle: conversation.visitorTitle,
			visitorTitleLanguage: conversation.visitorTitleLanguage,
		});

	return {
		visitorTitle: updated?.visitorTitle ?? visitorTitle,
		visitorTitleLanguage:
			updated?.visitorTitleLanguage ?? visitorTitleLanguage ?? null,
	};
}

export async function activateConversationTranslation(params: {
	db: Database;
	conversationId: string;
	organizationId: string;
	websiteId: string;
	visitorId: string;
	visitorLanguage?: string | null;
	alreadyActivated: boolean;
	alreadyCharged: boolean;
	chargeCredits: boolean;
	conversationTitle?: string | null;
	websiteDefaultLanguage?: string | null;
	billingSource?: OpenRouterBillingSource;
}): Promise<{
	visitorLanguage: string | null;
	translationActivatedAt: string | null;
	translationChargedAt: string | null;
	visitorTitle: string | null;
	visitorTitleLanguage: string | null;
}> {
	const now = new Date().toISOString();
	const normalizedVisitorLanguage = normalizeLanguageTag(
		params.visitorLanguage
	);
	const shouldChargeCredits =
		params.chargeCredits &&
		params.billingSource !== "customer_openrouter";

	const [updated] = await params.db
		.update(conversation)
		.set({
			visitorLanguage: normalizedVisitorLanguage ?? undefined,
			translationActivatedAt: params.alreadyActivated ? undefined : now,
			translationChargedAt:
				shouldChargeCredits && !params.alreadyCharged ? now : undefined,
			updatedAt: now,
		})
		.where(
			and(
				eq(conversation.id, params.conversationId),
				eq(conversation.organizationId, params.organizationId),
				eq(conversation.websiteId, params.websiteId)
			)
		)
		.returning({
			visitorLanguage: conversation.visitorLanguage,
			translationActivatedAt: conversation.translationActivatedAt,
			translationChargedAt: conversation.translationChargedAt,
		});

	if (shouldChargeCredits && !params.alreadyCharged) {
		try {
			await ingestAiCreditUsage({
				organizationId: params.organizationId,
				credits: 1,
				workflowRunId: `translation:${params.conversationId}`,
				modelId: AUTO_TRANSLATE_MODEL_ID,
				mode: "normal",
				baseCredits: 1,
				modelCredits: 0,
				toolCredits: 0,
				billableToolCount: 0,
				excludedToolCount: 0,
				totalToolCount: 0,
			});
		} catch (error) {
			console.warn("[translation] failed to ingest translation credit usage", {
				conversationId: params.conversationId,
				error,
			});
		}
	}

	const titleTranslation = params.websiteDefaultLanguage
		? await syncConversationVisitorTitle({
				db: params.db,
				conversationId: params.conversationId,
				organizationId: params.organizationId,
				websiteId: params.websiteId,
				title: params.conversationTitle ?? null,
				websiteDefaultLanguage: params.websiteDefaultLanguage,
				visitorLanguage: updated?.visitorLanguage ?? normalizedVisitorLanguage,
				autoTranslateEnabled: true,
				aiContext: {
					db: params.db,
					organizationId: params.organizationId,
					websiteId: params.websiteId,
				},
			})
		: { visitorTitle: null, visitorTitleLanguage: null };

	return {
		visitorLanguage:
			updated?.visitorLanguage ?? normalizedVisitorLanguage ?? null,
		translationActivatedAt:
			updated?.translationActivatedAt ?? (params.alreadyActivated ? null : now),
		translationChargedAt:
			updated?.translationChargedAt ??
			(shouldChargeCredits && !params.alreadyCharged ? now : null),
		visitorTitle: titleTranslation.visitorTitle,
		visitorTitleLanguage: titleTranslation.visitorTitleLanguage,
	};
}

export async function updateConversationVisitorLanguage(params: {
	db: Database;
	conversationId: string;
	organizationId: string;
	websiteId: string;
	visitorLanguage: string;
}): Promise<string> {
	const normalized =
		normalizeLanguageTag(params.visitorLanguage) ?? params.visitorLanguage;
	const [updated] = await params.db
		.update(conversation)
		.set({
			visitorLanguage: normalized,
			updatedAt: new Date().toISOString(),
		})
		.where(
			and(
				eq(conversation.id, params.conversationId),
				eq(conversation.organizationId, params.organizationId),
				eq(conversation.websiteId, params.websiteId)
			)
		)
		.returning({
			visitorLanguage: conversation.visitorLanguage,
		});

	return updated?.visitorLanguage ?? normalized;
}

export function shouldMaskTypingPreview(params: {
	preview: string;
	websiteDefaultLanguage: string;
	visitorLanguageHint?: string | null;
}): boolean {
	const trimmedPreview = params.preview.trim();
	if (!trimmedPreview) {
		return false;
	}

	const previewDetection = detectMessageLanguage({
		text: trimmedPreview,
		hintLanguage: params.visitorLanguageHint,
	});
	if (previewDetection.confidence === "low") {
		return false;
	}

	const previewLanguage =
		normalizeLanguageTag(previewDetection.language) ??
		normalizeLanguageTag(params.visitorLanguageHint);

	return Boolean(
		previewLanguage &&
			shouldTranslateBetweenLanguages(
				previewLanguage,
				params.websiteDefaultLanguage
			)
	);
}

export function isAutomaticTranslationEnabled(params: {
	planAllowsAutoTranslate: boolean;
	websiteAutoTranslateEnabled?: boolean | null;
}): boolean {
	return (
		params.planAllowsAutoTranslate &&
		(params.websiteAutoTranslateEnabled ?? true) === true
	);
}

export async function finalizeConversationTranslation(params: {
	db: Database;
	conversation: ConversationRecord;
	websiteDefaultLanguage: string;
	visitorLanguage?: string | null;
	hasTranslationPart: boolean;
	chargeCredits: boolean;
	billingSource?: OpenRouterBillingSource;
	aiAgentId?: string | null;
	emitRealtime?: boolean;
}): Promise<TranslationFinalizeResult> {
	const normalizedVisitorLanguage = normalizeLanguageTag(
		params.visitorLanguage
	);
	const shouldEmitRealtime = params.emitRealtime ?? true;

	if (params.hasTranslationPart) {
		const translationState = await activateConversationTranslation({
			db: params.db,
			conversationId: params.conversation.id,
			organizationId: params.conversation.organizationId,
			websiteId: params.conversation.websiteId,
			visitorId: params.conversation.visitorId,
			visitorLanguage: normalizedVisitorLanguage,
			alreadyActivated: Boolean(params.conversation.translationActivatedAt),
			alreadyCharged: Boolean(params.conversation.translationChargedAt),
			chargeCredits: params.chargeCredits,
			conversationTitle: params.conversation.title,
			websiteDefaultLanguage: params.websiteDefaultLanguage,
			billingSource: params.billingSource,
		});

		if (shouldEmitRealtime) {
			await emitConversationTranslationUpdate({
				conversation: params.conversation,
				updates: {
					visitorLanguage: translationState.visitorLanguage,
					translationActivatedAt: translationState.translationActivatedAt,
					translationChargedAt: translationState.translationChargedAt,
					visitorTitle: translationState.visitorTitle,
					visitorTitleLanguage: translationState.visitorTitleLanguage,
				},
				aiAgentId: params.aiAgentId ?? null,
			});
		}

		return {
			status: "activated",
			visitorLanguage: translationState.visitorLanguage,
			translationActivatedAt: translationState.translationActivatedAt,
			translationChargedAt: translationState.translationChargedAt,
			visitorTitle: translationState.visitorTitle,
			visitorTitleLanguage: translationState.visitorTitleLanguage,
		};
	}

	if (
		normalizedVisitorLanguage &&
		normalizedVisitorLanguage !== params.conversation.visitorLanguage
	) {
		const visitorLanguage = await updateConversationVisitorLanguage({
			db: params.db,
			conversationId: params.conversation.id,
			organizationId: params.conversation.organizationId,
			websiteId: params.conversation.websiteId,
			visitorLanguage: normalizedVisitorLanguage,
		});

		if (shouldEmitRealtime) {
			await emitConversationTranslationUpdate({
				conversation: params.conversation,
				updates: {
					visitorLanguage,
				},
				aiAgentId: params.aiAgentId ?? null,
			});
		}

		return {
			status: "language_updated",
			visitorLanguage,
		};
	}

	return {
		status: "noop",
	};
}

export function shouldAttemptAutoTranslation(params: {
	text: string;
	sourceLanguage?: string | null;
	targetLanguage?: string | null;
}): boolean {
	if (!params.text.trim()) {
		return false;
	}

	if (isNonLinguistic(params.text)) {
		return false;
	}

	if (getPrimaryLanguageTag(params.sourceLanguage) === null) {
		return false;
	}

	return shouldTranslateBetweenLanguages(
		params.sourceLanguage,
		params.targetLanguage
	);
}
