import { Router, type Request, type Response } from "express";
import multer from "multer";
import type { Db } from "@paperclipai/db";
import type { StorageService } from "../storage/types.js";
import { fileTypeFromBuffer } from "file-type";
import { contentJobStore } from "../services/content-job-store.js";
import { contentJobRunner } from "../services/content-job-runner.js";
import { converterCapabilitiesService } from "../services/converter-capabilities.js";
import { assertCompanyAccess } from "./authz.js";
import { MAX_ATTACHMENT_BYTES } from "../attachment-types.js";

// Extension → expected MIME map for magic-byte mismatch detection
const EXTENSION_MIME_MAP: Record<string, string> = {
  png: "image/png",
  jpg: "image/jpeg",
  jpeg: "image/jpeg",
  gif: "image/gif",
  webp: "image/webp",
  svg: "image/svg+xml",
  mp3: "audio/mpeg",
  mp4: "video/mp4",
  wav: "audio/wav",
  ogg: "audio/ogg",
  csv: "text/csv",
  json: "application/json",
  xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
};

// These formats have no magic bytes — null from fileTypeFromBuffer is expected
const TEXT_BASED_EXTENSIONS = new Set(["svg", "csv", "json", "txt", "html", "md", "xml"]);

const fileUpload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 },
});

async function runSingleFileUpload(
  upload: ReturnType<typeof multer>,
  req: Request,
  res: Response,
): Promise<void> {
  await new Promise<void>((resolve, reject) => {
    upload.single("file")(req, res, (err: unknown) => {
      if (err) reject(err);
      else resolve();
    });
  });
}

export function convertRoutes(db: Db, storage: StorageService) {
  const router = Router();

  // POST /companies/:companyId/convert — multipart upload + MIME validation + job dispatch
  router.post("/companies/:companyId/convert", async (req, res) => {
    const companyId = req.params.companyId!;
    assertCompanyAccess(req, companyId);

    // Handle multer upload
    try {
      await runSingleFileUpload(fileUpload, 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 as Request & {
        file?: {
          mimetype: string;
          buffer: Buffer;
          originalname: string;
          size: number;
        };
      }
    ).file;

    if (!file) {
      res.status(400).json({ error: "Missing file field 'file'" });
      return;
    }

    const targetFormat =
      typeof req.body.targetFormat === "string" && req.body.targetFormat.trim() !== ""
        ? (req.body.targetFormat as string).trim()
        : null;

    if (!targetFormat) {
      res.status(400).json({ error: "targetFormat is required" });
      return;
    }

    // ── Magic-byte MIME validation (CONV-09) ──────────────────────────────────
    const originalFilename: string = file.originalname ?? "upload";
    const ext = originalFilename.split(".").pop()?.toLowerCase() ?? "";

    // Only validate if the extension has a known MIME and is NOT text-based
    // (text-based files return null from fileTypeFromBuffer — allowed through)
    if (ext && EXTENSION_MIME_MAP[ext] && !TEXT_BASED_EXTENSIONS.has(ext)) {
      const detected = await fileTypeFromBuffer(file.buffer);
      if (detected) {
        const claimedMime = EXTENSION_MIME_MAP[ext]!;
        // Normalise audio/mpeg vs audio/mp3 comparison
        const detectedNorm = detected.mime.toLowerCase();
        const claimedNorm = claimedMime.toLowerCase();
        if (detectedNorm !== claimedNorm) {
          res.status(422).json({
            error: `File MIME mismatch: file claims to be ${claimedMime} but magic bytes indicate ${detected.mime}`,
            actualMime: detected.mime,
            claimedMime,
          });
          return;
        }
      }
    }

    // Determine source MIME — prefer file-type detection over multer claim for known binary types
    let sourceMime = file.mimetype || "application/octet-stream";
    if (ext && EXTENSION_MIME_MAP[ext]) {
      sourceMime = EXTENSION_MIME_MAP[ext]!;
    }

    // ── Create and dispatch content job ──────────────────────────────────────
    const store = contentJobStore(db);
    const job = await store.create(companyId, {
      jobType: "convert",
      input: {
        fileBase64: file.buffer.toString("base64"),
        sourceMime,
        targetFormat,
        originalFilename,
      },
      sourceTaskId: typeof req.body.sourceTaskId === "string" ? req.body.sourceTaskId : null,
    });

    void contentJobRunner.dispatch(db, storage, job!);

    res.status(202).json({
      jobId: job!.id,
      status: job!.status,
    });
  });

  // GET /system/converters — capability map (CONV-08)
  router.get("/system/converters", async (_req, res) => {
    const caps = await converterCapabilitiesService().get();
    res.json(caps);
  });

  return router;
}
