import { getLogger } from "../../../../logger.js";
import { getBlobStorageProvider, toBlobUri } from "./index.js";
import { buildStorageKey } from "./storage-keys.js";
import { FileSecurityError } from "./file-security-errors.js";
import { normalizeInlineFileBytes } from "./file-content-security.js";
import { makeSanitizedSourceUrl } from "./file-url-security.js";
import { downloadExternalFile } from "./external-file-downloader.js";
import { createHash } from "node:crypto";
import { getExtensionFromMimeType } from "@inkeep/agents-core/constants/allowed-file-formats";

//#region src/domains/run/services/blob-storage/file-upload.ts
const logger = getLogger("file-upload");
const FILE_UPLOAD_CONCURRENCY = 3;
const isRemoteHttpAttachmentUrl = (rawUrl) => {
	try {
		const { protocol } = new URL(rawUrl);
		return protocol === "http:" || protocol === "https:";
	} catch {
		return false;
	}
};
async function uploadFilePart(part, ctx, index) {
	const storage = getBlobStorageProvider();
	const file = part.file;
	let data;
	let mimeType;
	if ("bytes" in file && file.bytes) {
		const normalized = await normalizeInlineFileBytes(file);
		data = normalized.data;
		mimeType = normalized.mimeType;
	} else if ("uri" in file && file.uri) {
		const downloaded = await downloadExternalFile(file.uri, { expectedMimeType: file.mimeType });
		data = downloaded.data;
		mimeType = downloaded.mimeType;
	} else {
		logger.warn({ index }, "FilePart has neither bytes nor uri, skipping upload");
		return part;
	}
	const contentHash = createHash("sha256").update(data).digest("hex");
	const ext = getExtensionFromMimeType(mimeType);
	const key = buildStorageKey({
		category: "media",
		tenantId: ctx.tenantId,
		projectId: ctx.projectId,
		conversationId: ctx.conversationId,
		messageId: ctx.messageId,
		contentHash,
		ext
	});
	await storage.upload({
		key,
		data,
		contentType: mimeType
	});
	logger.debug({
		key,
		mimeType,
		size: data.length
	}, "Uploaded file to blob storage");
	const remoteAttachmentSourceUrl = "uri" in file && file.uri && isRemoteHttpAttachmentUrl(file.uri) ? file.uri : void 0;
	return {
		kind: "file",
		file: {
			uri: toBlobUri(key),
			mimeType
		},
		...part.metadata || remoteAttachmentSourceUrl ? { metadata: {
			...part.metadata || {},
			...remoteAttachmentSourceUrl ? { sourceUrl: makeSanitizedSourceUrl(remoteAttachmentSourceUrl) } : {}
		} } : {}
	};
}
async function uploadPartsFiles(parts, ctx) {
	const results = parts.map((part) => part.kind === "file" ? null : part);
	const fileIndices = parts.flatMap((part, index) => part.kind === "file" ? [index] : []);
	let nextFileCursor = 0;
	const workerCount = Math.min(FILE_UPLOAD_CONCURRENCY, fileIndices.length);
	const worker = async () => {
		while (nextFileCursor < fileIndices.length) {
			const fileCursor = nextFileCursor;
			nextFileCursor += 1;
			const index = fileIndices[fileCursor];
			const part = parts[index];
			if (!part || part.kind !== "file") continue;
			try {
				results[index] = await uploadFilePart(part, ctx, index);
			} catch (error) {
				if (error instanceof FileSecurityError) throw error;
				const file = part.kind === "file" ? {
					...part.file.mimeType ? { mimeType: part.file.mimeType } : {},
					..."uri" in part.file && part.file.uri ? { uri: part.file.uri } : {},
					..."bytes" in part.file && part.file.bytes ? { bytesLength: part.file.bytes.length } : {}
				} : void 0;
				logger.error({
					error: error instanceof Error ? error.message : String(error),
					index,
					tenantId: ctx.tenantId,
					projectId: ctx.projectId,
					conversationId: ctx.conversationId,
					messageId: ctx.messageId,
					file
				}, "Failed to upload file part, dropping from persisted message to avoid storing base64 in DB");
			}
		}
	};
	await Promise.all(Array.from({ length: workerCount }, () => worker()));
	return results.filter((part) => part !== null);
}
function makeMessageContentParts(parts) {
	const result = [];
	for (const part of parts) if (part.kind === "text") result.push({
		kind: "text",
		text: part.text
	});
	else if (part.kind === "file") {
		const filePart = part;
		const file = filePart.file;
		if ("uri" in file && file.uri) result.push({
			kind: "file",
			data: file.uri,
			metadata: {
				...file.mimeType && { mimeType: file.mimeType },
				...filePart.metadata && { ...filePart.metadata }
			}
		});
		else logger.warn({}, "Skipping file part without URI — raw bytes must not be persisted to the database");
	} else result.push({
		kind: part.kind,
		data: part.data,
		metadata: part.metadata
	});
	return result;
}
function hasFileParts(parts) {
	return parts.some((p) => p.kind === "file");
}

//#endregion
export { hasFileParts, makeMessageContentParts, uploadPartsFiles };