import { Router } from "express";
import multer from "multer";
import createDOMPurify from "dompurify";
import { JSDOM } from "jsdom";
import { createAssetImageMetadataSchema } from "@rudderhq/shared";
import { assetService, logActivity } from "../services/index.js";
import { isAllowedContentType, MAX_ATTACHMENT_BYTES } from "../attachment-types.js";
import { assertCompanyAccess, getActorInfo } from "./authz.js";
const SVG_CONTENT_TYPE = "image/svg+xml";
const ALLOWED_COMPANY_LOGO_CONTENT_TYPES = new Set([
    "image/png",
    "image/jpeg",
    "image/jpg",
    "image/webp",
    "image/gif",
    SVG_CONTENT_TYPE,
]);
function sanitizeSvgBuffer(input) {
    const raw = input.toString("utf8").trim();
    if (!raw)
        return null;
    const baseDom = new JSDOM("");
    const domPurify = createDOMPurify(baseDom.window);
    domPurify.addHook("uponSanitizeAttribute", (_node, data) => {
        const attrName = data.attrName.toLowerCase();
        const attrValue = (data.attrValue ?? "").trim();
        if (attrName.startsWith("on")) {
            data.keepAttr = false;
            return;
        }
        if ((attrName === "href" || attrName === "xlink:href") && attrValue && !attrValue.startsWith("#")) {
            data.keepAttr = false;
        }
    });
    let parsedDom = null;
    try {
        const sanitized = domPurify.sanitize(raw, {
            USE_PROFILES: { svg: true, svgFilters: true, html: false },
            FORBID_TAGS: ["script", "foreignObject"],
            FORBID_CONTENTS: ["script", "foreignObject"],
            RETURN_TRUSTED_TYPE: false,
        });
        parsedDom = new JSDOM(sanitized, { contentType: SVG_CONTENT_TYPE });
        const document = parsedDom.window.document;
        const root = document.documentElement;
        if (!root || root.tagName.toLowerCase() !== "svg")
            return null;
        for (const el of Array.from(root.querySelectorAll("script, foreignObject"))) {
            el.remove();
        }
        for (const el of Array.from(root.querySelectorAll("*"))) {
            for (const attr of Array.from(el.attributes)) {
                const attrName = attr.name.toLowerCase();
                const attrValue = attr.value.trim();
                if (attrName.startsWith("on")) {
                    el.removeAttribute(attr.name);
                    continue;
                }
                if ((attrName === "href" || attrName === "xlink:href") && attrValue && !attrValue.startsWith("#")) {
                    el.removeAttribute(attr.name);
                }
            }
        }
        const output = root.outerHTML.trim();
        if (!output || !/^<svg[\s>]/i.test(output))
            return null;
        return Buffer.from(output, "utf8");
    }
    catch {
        return null;
    }
    finally {
        parsedDom?.window.close();
        baseDom.window.close();
    }
}
export function assetRoutes(db, storage) {
    const router = Router();
    const svc = assetService(db);
    const assetUpload = multer({
        storage: multer.memoryStorage(),
        limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 },
    });
    const companyLogoUpload = multer({
        storage: multer.memoryStorage(),
        limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 },
    });
    async function runSingleFileUpload(upload, req, res) {
        await new Promise((resolve, reject) => {
            upload.single("file")(req, res, (err) => {
                if (err)
                    reject(err);
                else
                    resolve();
            });
        });
    }
    router.post("/orgs/:orgId/assets/images", async (req, res) => {
        const orgId = req.params.orgId;
        assertCompanyAccess(req, orgId);
        try {
            await runSingleFileUpload(assetUpload, req, res);
        }
        catch (err) {
            if (err instanceof multer.MulterError) {
                if (err.code === "LIMIT_FILE_SIZE") {
                    res.status(422).json({ error: `File exceeds ${MAX_ATTACHMENT_BYTES} bytes` });
                    return;
                }
                res.status(400).json({ error: err.message });
                return;
            }
            throw err;
        }
        const file = req.file;
        if (!file) {
            res.status(400).json({ error: "Missing file field 'file'" });
            return;
        }
        const parsedMeta = createAssetImageMetadataSchema.safeParse(req.body ?? {});
        if (!parsedMeta.success) {
            res.status(400).json({ error: "Invalid image metadata", details: parsedMeta.error.issues });
            return;
        }
        const namespaceSuffix = parsedMeta.data.namespace ?? "general";
        const contentType = (file.mimetype || "").toLowerCase();
        if (contentType !== SVG_CONTENT_TYPE && !isAllowedContentType(contentType)) {
            res.status(422).json({ error: `Unsupported file type: ${contentType || "unknown"}` });
            return;
        }
        let fileBody = file.buffer;
        if (contentType === SVG_CONTENT_TYPE) {
            const sanitized = sanitizeSvgBuffer(file.buffer);
            if (!sanitized || sanitized.length <= 0) {
                res.status(422).json({ error: "SVG could not be sanitized" });
                return;
            }
            fileBody = sanitized;
        }
        if (fileBody.length <= 0) {
            res.status(422).json({ error: "Image is empty" });
            return;
        }
        const actor = getActorInfo(req);
        const stored = await storage.putFile({
            orgId,
            namespace: `assets/${namespaceSuffix}`,
            originalFilename: file.originalname || null,
            contentType,
            body: fileBody,
        });
        const asset = await svc.create(orgId, {
            provider: stored.provider,
            objectKey: stored.objectKey,
            contentType: stored.contentType,
            byteSize: stored.byteSize,
            sha256: stored.sha256,
            originalFilename: stored.originalFilename,
            createdByAgentId: actor.agentId,
            createdByUserId: actor.actorType === "user" ? actor.actorId : null,
        });
        await logActivity(db, {
            orgId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "asset.created",
            entityType: "asset",
            entityId: asset.id,
            details: {
                originalFilename: asset.originalFilename,
                contentType: asset.contentType,
                byteSize: asset.byteSize,
            },
        });
        res.status(201).json({
            assetId: asset.id,
            orgId: asset.orgId,
            provider: asset.provider,
            objectKey: asset.objectKey,
            contentType: asset.contentType,
            byteSize: asset.byteSize,
            sha256: asset.sha256,
            originalFilename: asset.originalFilename,
            createdByAgentId: asset.createdByAgentId,
            createdByUserId: asset.createdByUserId,
            createdAt: asset.createdAt,
            updatedAt: asset.updatedAt,
            contentPath: `/api/assets/${asset.id}/content`,
        });
    });
    router.post("/orgs/:orgId/logo", async (req, res) => {
        const orgId = req.params.orgId;
        assertCompanyAccess(req, orgId);
        try {
            await runSingleFileUpload(companyLogoUpload, req, res);
        }
        catch (err) {
            if (err instanceof multer.MulterError) {
                if (err.code === "LIMIT_FILE_SIZE") {
                    res.status(422).json({ error: `Image exceeds ${MAX_ATTACHMENT_BYTES} bytes` });
                    return;
                }
                res.status(400).json({ error: err.message });
                return;
            }
            throw err;
        }
        const file = req.file;
        if (!file) {
            res.status(400).json({ error: "Missing file field 'file'" });
            return;
        }
        const contentType = (file.mimetype || "").toLowerCase();
        if (!ALLOWED_COMPANY_LOGO_CONTENT_TYPES.has(contentType)) {
            res.status(422).json({ error: `Unsupported image type: ${contentType || "unknown"}` });
            return;
        }
        let fileBody = file.buffer;
        if (contentType === SVG_CONTENT_TYPE) {
            const sanitized = sanitizeSvgBuffer(file.buffer);
            if (!sanitized || sanitized.length <= 0) {
                res.status(422).json({ error: "SVG could not be sanitized" });
                return;
            }
            fileBody = sanitized;
        }
        if (fileBody.length <= 0) {
            res.status(422).json({ error: "Image is empty" });
            return;
        }
        const actor = getActorInfo(req);
        const stored = await storage.putFile({
            orgId,
            namespace: "assets/orgs",
            originalFilename: file.originalname || null,
            contentType,
            body: fileBody,
        });
        const asset = await svc.create(orgId, {
            provider: stored.provider,
            objectKey: stored.objectKey,
            contentType: stored.contentType,
            byteSize: stored.byteSize,
            sha256: stored.sha256,
            originalFilename: stored.originalFilename,
            createdByAgentId: actor.agentId,
            createdByUserId: actor.actorType === "user" ? actor.actorId : null,
        });
        await logActivity(db, {
            orgId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "asset.created",
            entityType: "asset",
            entityId: asset.id,
            details: {
                originalFilename: asset.originalFilename,
                contentType: asset.contentType,
                byteSize: asset.byteSize,
                namespace: "assets/orgs",
            },
        });
        res.status(201).json({
            assetId: asset.id,
            orgId: asset.orgId,
            provider: asset.provider,
            objectKey: asset.objectKey,
            contentType: asset.contentType,
            byteSize: asset.byteSize,
            sha256: asset.sha256,
            originalFilename: asset.originalFilename,
            createdByAgentId: asset.createdByAgentId,
            createdByUserId: asset.createdByUserId,
            createdAt: asset.createdAt,
            updatedAt: asset.updatedAt,
            contentPath: `/api/assets/${asset.id}/content`,
        });
    });
    router.get("/assets/:assetId/content", async (req, res, next) => {
        const assetId = req.params.assetId;
        const asset = await svc.getById(assetId);
        if (!asset) {
            res.status(404).json({ error: "Asset not found" });
            return;
        }
        assertCompanyAccess(req, asset.orgId);
        const object = await storage.getObject(asset.orgId, asset.objectKey);
        const responseContentType = asset.contentType || object.contentType || "application/octet-stream";
        res.setHeader("Content-Type", responseContentType);
        res.setHeader("Content-Length", String(asset.byteSize || object.contentLength || 0));
        res.setHeader("Cache-Control", "private, max-age=60");
        res.setHeader("X-Content-Type-Options", "nosniff");
        if (responseContentType === SVG_CONTENT_TYPE) {
            res.setHeader("Content-Security-Policy", "sandbox; default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'");
        }
        const filename = asset.originalFilename ?? "asset";
        res.setHeader("Content-Disposition", `inline; filename=\"${filename.replaceAll("\"", "")}\"`);
        object.stream.on("error", (err) => {
            next(err);
        });
        object.stream.pipe(res);
    });
    return router;
}
//# sourceMappingURL=assets.js.map