/**
 * Extended adversarial integration tests for the SnapOtter image API.
 *
 * Covers: zero-byte uploads across tools, corrupted headers / wrong magic
 * bytes, unicode filenames, extreme dimensions through various tools, batch
 * edge cases, pipeline edge cases, and concurrent stress scenarios.
 *
 * Complements adversarial.test.ts (32 tests), edge-cases.test.ts (24 tests),
 * and concurrent.test.ts.
 */

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { apiToolPath, TOOLS } from "@snapotter/shared";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { fixtures, readFixture } from "../../fixtures/index.js";
import {
  buildTestApp,
  createMultipartPayload,
  loginAsAdmin,
  type TestApp,
} from "../test-server.js";

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
const PNG_200x150 = readFixture(fixtures.image.base.png200);
const PNG_1x1 = readFixture(fixtures.image.edge.px1);
const JPG_100x100 = readFixture(fixtures.image.base.jpg100);
const STRESS_LARGE = readFixture(fixtures.image.stressLarge);

// ---------------------------------------------------------------------------
// Shared state
// ---------------------------------------------------------------------------
let testApp: TestApp;
let app: TestApp["app"];
let adminToken: string;

beforeAll(async () => {
  testApp = await buildTestApp();
  app = testApp.app;
  adminToken = await loginAsAdmin(app);
}, 30_000);

afterAll(async () => {
  await testApp.cleanup();
}, 10_000);

// Real catalog tools resolve to their section-prefixed path; unknown ids
// (used by the non-existent/injection negative tests below) fall back to a
// raw path that matches no route and 404s, instead of throwing in apiToolPath.
const toolUrl = (toolId: string): string =>
  TOOLS.some((t) => t.id === toolId) ? apiToolPath(toolId) : `/api/v1/tools/${toolId}`;

/** Helper to POST a multipart payload to a tool endpoint. */
function postTool(
  toolId: string,
  fields: Array<{
    name: string;
    filename?: string;
    contentType?: string;
    content: Buffer | string;
  }>,
) {
  const { body, contentType } = createMultipartPayload(fields);
  return app.inject({
    method: "POST",
    url: toolUrl(toolId),
    headers: {
      "content-type": contentType,
      authorization: `Bearer ${adminToken}`,
    },
    body,
  });
}

/** Helper to POST a batch request. */
function postBatch(
  toolId: string,
  fields: Array<{
    name: string;
    filename?: string;
    contentType?: string;
    content: Buffer | string;
  }>,
) {
  const { body, contentType } = createMultipartPayload(fields);
  return app.inject({
    method: "POST",
    url: `${toolUrl(toolId)}/batch`,
    headers: {
      "content-type": contentType,
      authorization: `Bearer ${adminToken}`,
    },
    body,
  });
}

/** Helper to POST a pipeline execution request. */
function executePipeline(
  image: Buffer,
  filename: string,
  pipeline: {
    steps: Array<{ toolId: string; settings?: Record<string, unknown> }>;
  },
) {
  const { body, contentType } = createMultipartPayload([
    { name: "file", filename, content: image, contentType: "image/png" },
    { name: "pipeline", content: JSON.stringify(pipeline) },
  ]);
  return app.inject({
    method: "POST",
    url: "/api/v1/pipeline/execute",
    headers: {
      "content-type": contentType,
      authorization: `Bearer ${adminToken}`,
    },
    body,
  });
}

/** Helper to build an inject config for a tool request. */
function buildToolRequest(
  toolId: string,
  image: Buffer,
  filename: string,
  settings: Record<string, unknown>,
) {
  const { body, contentType } = createMultipartPayload([
    { name: "file", filename, content: image, contentType: "image/png" },
    { name: "settings", content: JSON.stringify(settings) },
  ]);
  return {
    method: "POST" as const,
    url: apiToolPath(toolId),
    headers: {
      "content-type": contentType,
      authorization: `Bearer ${adminToken}`,
    },
    body,
  };
}

// ═══════════════════════════════════════════════════════════════════════════
// ZERO-BYTE FILES ACROSS MULTIPLE TOOLS
// ═══════════════════════════════════════════════════════════════════════════
describe("Zero-byte file uploads across tools", () => {
  const zeroBuffer = Buffer.alloc(0);

  it("rejects a 0-byte file to /api/v1/tools/image/resize with 400", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "empty.png",
        content: zeroBuffer,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toBeDefined();
  });

  it("rejects a 0-byte file to /api/v1/tools/image/compress with 400", async () => {
    const res = await postTool("compress", [
      {
        name: "file",
        filename: "empty.jpg",
        content: zeroBuffer,
        contentType: "image/jpeg",
      },
      { name: "settings", content: JSON.stringify({ quality: 80 }) },
    ]);

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toBeDefined();
  });

  it("rejects a 0-byte file to /api/v1/tools/image/convert with 400", async () => {
    const res = await postTool("convert", [
      {
        name: "file",
        filename: "empty.png",
        content: zeroBuffer,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ format: "jpg" }) },
    ]);

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toBeDefined();
  });

  it("rejects a 0-byte file to /api/v1/tools/image/rotate with 400", async () => {
    const res = await postTool("rotate", [
      {
        name: "file",
        filename: "empty.webp",
        content: zeroBuffer,
        contentType: "image/webp",
      },
      { name: "settings", content: JSON.stringify({ angle: 90 }) },
    ]);

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toBeDefined();
  });

  it("rejects a 0-byte file to /api/v1/tools/image/crop with 400", async () => {
    const res = await postTool("crop", [
      {
        name: "file",
        filename: "empty.png",
        content: zeroBuffer,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({
          left: 0,
          top: 0,
          width: 10,
          height: 10,
        }),
      },
    ]);

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toBeDefined();
  });

  it("rejects a 0-byte file to /api/v1/tools/image/border with 400", async () => {
    const res = await postTool("border", [
      {
        name: "file",
        filename: "empty.png",
        content: zeroBuffer,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ borderWidth: 10 }) },
    ]);

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toBeDefined();
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// CORRUPTED HEADERS / WRONG MAGIC BYTES
// ═══════════════════════════════════════════════════════════════════════════
describe("Corrupted headers and wrong magic bytes", () => {
  it("handles PNG magic bytes followed by garbage gracefully", async () => {
    // PNG signature: 89 50 4E 47 0D 0A 1A 0A
    const corruptedPng = Buffer.concat([
      Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
      Buffer.from(
        "THIS IS GARBAGE CONTENT AFTER A VALID PNG HEADER " +
          "AAAA BBBB CCCC DDDD EEEE FFFF 0000 1111 2222 3333",
      ),
    ]);

    const res = await postTool("resize", [
      {
        name: "file",
        filename: "corrupt-png.png",
        content: corruptedPng,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    // Should fail gracefully with a JSON error, NOT crash the server
    expect([400, 422]).toContain(res.statusCode);
    const json = JSON.parse(res.body);
    expect(json.error).toBeDefined();
  });

  it("handles JPEG SOI marker followed by truncated data gracefully", async () => {
    // JPEG starts with FF D8 (SOI), typically followed by FF E0 (APP0)
    const truncatedJpeg = Buffer.from([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46]);

    const res = await postTool("compress", [
      {
        name: "file",
        filename: "truncated.jpg",
        content: truncatedJpeg,
        contentType: "image/jpeg",
      },
      { name: "settings", content: JSON.stringify({ quality: 50 }) },
    ]);

    // Must not crash; either 200 (partial decode), 400, or 422
    expect([200, 400, 422]).toContain(res.statusCode);
  });

  it("handles a .jpg file containing actual PNG data (format mismatch)", async () => {
    // Sharp auto-detects via magic bytes, so this should process fine
    const res = await postTool("compress", [
      {
        name: "file",
        filename: "actually-png.jpg",
        content: PNG_200x150,
        contentType: "image/jpeg",
      },
      { name: "settings", content: JSON.stringify({ quality: 75 }) },
    ]);

    // Sharp detects the real format via magic bytes regardless of extension
    expect(res.statusCode).toBe(200);
  });

  it("rejects a BMP header followed by garbage", async () => {
    // BMP magic bytes: 42 4D
    const fakeBmp = Buffer.concat([Buffer.from([0x42, 0x4d]), Buffer.alloc(100, 0xff)]);

    const res = await postTool("resize", [
      {
        name: "file",
        filename: "fake.bmp",
        content: fakeBmp,
        contentType: "image/bmp",
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    // BMP is not a supported format OR the garbage content fails validation
    expect([400, 422]).toContain(res.statusCode);
  });

  it("rejects a WebP RIFF header followed by garbage", async () => {
    // WebP starts with RIFF....WEBP
    const fakeWebp = Buffer.concat([
      Buffer.from("RIFF"),
      Buffer.from([0x00, 0x00, 0x00, 0x00]),
      Buffer.from("WEBP"),
      Buffer.alloc(50, 0xab),
    ]);

    const res = await postTool("resize", [
      {
        name: "file",
        filename: "corrupt.webp",
        content: fakeWebp,
        contentType: "image/webp",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect([400, 422]).toContain(res.statusCode);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// UNICODE FILENAMES (EXTENDED)
// ═══════════════════════════════════════════════════════════════════════════
describe("Unicode filenames — extended adversarial", () => {
  it("handles filename with emoji: photo_\u{1F389}.png", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "photo_\u{1F389}.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.downloadUrl).toBeDefined();
  });

  it("handles filename with CJK characters: 写真.png", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "写真.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("handles filename with spaces and special chars: my photo (final).png", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "my photo (final).png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("sanitizes path traversal: ../../../etc/passwd.png", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "../../../etc/passwd.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.downloadUrl).not.toContain("..");
    expect(json.downloadUrl).not.toContain("etc/passwd");
  });

  it("handles filename with mixed RTL and LTR characters", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "ملف_photo_الصورة.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("handles filename with null bytes stripped", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "test\x00hidden.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    // Should succeed — null bytes are stripped by filename sanitization
    expect([200, 400]).toContain(res.statusCode);
  });

  it("handles filename with only an extension", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: ".png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("handles filename with double extensions", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "exploit.php.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    // The download URL retains "exploit.php" in the base name but the final
    // extension is still a safe image format (.png). The key security check
    // is that the URL ends with a recognized image extension.
    expect(json.downloadUrl).toMatch(/\.(png|jpg|jpeg|webp|avif|tiff|gif)$/);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// EXTREME DIMENSIONS THROUGH VARIOUS TOOLS
// ═══════════════════════════════════════════════════════════════════════════
describe("1x1 pixel image through additional tools", () => {
  it("upscales a 1x1 pixel image to 100x100 via resize", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "tiny.png",
        content: PNG_1x1,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({ width: 100, height: 100 }),
      },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("applies crop to a 1x1 pixel image (1x1 crop region)", async () => {
    const res = await postTool("crop", [
      {
        name: "file",
        filename: "tiny.png",
        content: PNG_1x1,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({ left: 0, top: 0, width: 1, height: 1 }),
      },
    ]);

    // Should succeed or fail gracefully
    expect([200, 422]).toContain(res.statusCode);
  });

  it("applies border to a 1x1 pixel image", async () => {
    const res = await postTool("border", [
      {
        name: "file",
        filename: "tiny.png",
        content: PNG_1x1,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({
          borderWidth: 20,
          borderColor: "#FF0000",
        }),
      },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("applies watermark-text to a 1x1 pixel image", async () => {
    const res = await postTool("watermark-text", [
      {
        name: "file",
        filename: "tiny.png",
        content: PNG_1x1,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({
          text: "WATERMARK",
          fontSize: 12,
          opacity: 50,
        }),
      },
    ]);

    // The watermark will be much larger than the 1x1 image — may succeed
    // or fail depending on how Sharp handles the composite. Must not crash.
    expect([200, 400, 422]).toContain(res.statusCode);
  });

  it("applies sharpening to a 1x1 pixel image", async () => {
    const res = await postTool("sharpening", [
      {
        name: "file",
        filename: "tiny.png",
        content: PNG_1x1,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({ method: "adaptive" }),
      },
    ]);

    // Sharpening a 1x1 image is a no-op but should not crash
    expect([200, 422]).toContain(res.statusCode);
  });

  it("applies text-overlay to a 1x1 pixel image", async () => {
    const res = await postTool("text-overlay", [
      {
        name: "file",
        filename: "tiny.png",
        content: PNG_1x1,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({
          text: "Hello",
          fontSize: 24,
        }),
      },
    ]);

    // Text overlay on 1x1 image — the SVG overlay will dwarf the image
    expect([200, 422]).toContain(res.statusCode);
  });

  it("converts a 1x1 pixel image from PNG to WebP", async () => {
    const res = await postTool("convert", [
      {
        name: "file",
        filename: "tiny.png",
        content: PNG_1x1,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ format: "webp" }) },
    ]);

    expect([200, 400]).toContain(res.statusCode);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// BATCH EDGE CASES (EXTENDED)
// ═══════════════════════════════════════════════════════════════════════════
describe("Batch edge cases — extended", () => {
  it("rejects batch with 0 images uploaded to resize", async () => {
    const res = await postBatch("resize", [
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toMatch(/no file/i);
  });

  it("handles batch with duplicate filenames", async () => {
    const res = await postBatch("resize", [
      {
        name: "file",
        filename: "duplicate.png",
        contentType: "image/png",
        content: PNG_200x150,
      },
      {
        name: "file",
        filename: "duplicate.png",
        contentType: "image/png",
        content: PNG_200x150,
      },
      {
        name: "file",
        filename: "duplicate.png",
        contentType: "image/png",
        content: PNG_200x150,
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    // Should succeed and deduplicate filenames in the ZIP
    expect(res.statusCode).toBe(200);
    expect(res.headers["content-type"]).toBe("application/zip");
    const fileResults = JSON.parse(decodeURIComponent(res.headers["x-file-results"] as string));
    // All three entries should have unique names
    const names = Object.values(fileResults);
    expect(new Set(names).size).toBe(3);
  });

  it("handles batch with mix of valid and invalid files", async () => {
    const garbage = Buffer.from("this is not an image at all");

    const res = await postBatch("resize", [
      {
        name: "file",
        filename: "valid.png",
        contentType: "image/png",
        content: PNG_200x150,
      },
      {
        name: "file",
        filename: "invalid.png",
        contentType: "image/png",
        content: garbage,
      },
      {
        name: "file",
        filename: "also-valid.jpg",
        contentType: "image/jpeg",
        content: JPG_100x100,
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    // Batch should process valid files and skip invalid ones.
    // Since not all files failed, it should return 200 with a ZIP.
    expect(res.statusCode).toBe(200);
    expect(res.headers["content-type"]).toBe("application/zip");
  });

  it("rejects batch where ALL files are invalid", async () => {
    const garbage1 = Buffer.from("not an image 1");
    const garbage2 = Buffer.from("not an image 2");

    const res = await postBatch("resize", [
      {
        name: "file",
        filename: "bad1.png",
        contentType: "image/png",
        content: garbage1,
      },
      {
        name: "file",
        filename: "bad2.png",
        contentType: "image/png",
        content: garbage2,
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    // All files failed — should return 422
    expect(res.statusCode).toBe(422);
    const json = JSON.parse(res.body);
    expect(json.error).toMatch(/all files failed/i);
    expect(json.errors).toBeDefined();
    expect(json.errors.length).toBe(2);
  });

  it("handles batch with zero-byte files (skipped as empty)", async () => {
    // Batch processing silently skips zero-byte parts (buffer.length === 0)
    const res = await postBatch("resize", [
      {
        name: "file",
        filename: "empty.png",
        contentType: "image/png",
        content: Buffer.alloc(0),
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    // The zero-byte file is skipped, resulting in 0 valid files
    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toMatch(/no file/i);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// PIPELINE EDGE CASES (EXTENDED)
// ═══════════════════════════════════════════════════════════════════════════
describe("Pipeline edge cases — extended", () => {
  it("rejects pipeline with 0 steps", async () => {
    const res = await executePipeline(PNG_200x150, "test.png", {
      steps: [],
    });

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toMatch(/pipeline/i);
  });

  it("handles pipeline with conflicting resize then crop larger than result", async () => {
    // Resize to 50x50, then crop region 200x200 — should fail at crop step
    const res = await executePipeline(PNG_200x150, "test.png", {
      steps: [
        { toolId: "resize", settings: { width: 50, height: 50 } },
        {
          toolId: "crop",
          settings: { left: 0, top: 0, width: 200, height: 200 },
        },
      ],
    });

    // Crop exceeds resized dimensions — should fail gracefully (422)
    // or succeed if sharp auto-clips. Must not crash.
    expect([200, 422]).toContain(res.statusCode);
  });

  it("rejects pipeline step with unknown tool name", async () => {
    const res = await executePipeline(PNG_200x150, "test.png", {
      steps: [{ toolId: "resize", settings: { width: 100 } }, { toolId: "imaginary-tool-xyz" }],
    });

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toMatch(/not found/i);
  });

  it("handles pipeline with resize then border then compress chain", async () => {
    const res = await executePipeline(PNG_200x150, "test.png", {
      steps: [
        { toolId: "resize", settings: { width: 80 } },
        {
          toolId: "border",
          settings: { borderWidth: 5, borderColor: "#FF0000" },
        },
        { toolId: "compress", settings: { quality: 50 } },
      ],
    });

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.stepsCompleted).toBe(3);
  });

  it("rejects pipeline with empty settings object for a required-field tool", async () => {
    // watermark-text requires 'text' (min length 1)
    const res = await executePipeline(PNG_200x150, "test.png", {
      steps: [{ toolId: "watermark-text", settings: {} }],
    });

    // Zod validation should reject the missing required 'text' field
    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toMatch(/step 1/i);
  });

  it("handles pipeline that outputs to a different format mid-chain", async () => {
    // Convert to webp, then resize — should work since Sharp handles webp
    const res = await executePipeline(PNG_200x150, "test.png", {
      steps: [
        { toolId: "convert", settings: { format: "webp" } },
        { toolId: "resize", settings: { width: 50 } },
      ],
    });

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.stepsCompleted).toBe(2);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// CONCURRENT STRESS — EXTENDED
// ═══════════════════════════════════════════════════════════════════════════
describe("Concurrent stress — 10 simultaneous resize requests", () => {
  it("fires 10 simultaneous resize requests with the same image — all return 200", async () => {
    const results = await Promise.all(
      Array.from({ length: 10 }, (_, i) =>
        app.inject(
          buildToolRequest("resize", PNG_200x150, `stress-${i}.png`, {
            width: 50,
          }),
        ),
      ),
    );

    for (const res of results) {
      expect(res.statusCode).toBe(200);
      const json = JSON.parse(res.body);
      expect(json.jobId).toBeDefined();
      expect(json.downloadUrl).toBeDefined();
    }

    // All 10 must produce unique job IDs
    const jobIds = results.map((r) => JSON.parse(r.body).jobId);
    expect(new Set(jobIds).size).toBe(10);
  }, 120_000);
});

describe("Concurrent stress — 5 different tools simultaneously", () => {
  it("fires resize, crop, rotate, compress, and border simultaneously — all return 200", async () => {
    const [resizeRes, cropRes, rotateRes, compressRes, borderRes] = await Promise.all([
      app.inject(
        buildToolRequest("resize", PNG_200x150, "conc-resize.png", {
          width: 100,
        }),
      ),
      app.inject(
        buildToolRequest("crop", PNG_200x150, "conc-crop.png", {
          left: 0,
          top: 0,
          width: 100,
          height: 100,
        }),
      ),
      app.inject(
        buildToolRequest("rotate", PNG_200x150, "conc-rotate.png", {
          angle: 90,
        }),
      ),
      app.inject(
        buildToolRequest("compress", PNG_200x150, "conc-compress.png", {
          quality: 60,
        }),
      ),
      app.inject(
        buildToolRequest("border", PNG_200x150, "conc-border.png", {
          borderWidth: 10,
          borderColor: "#0000FF",
        }),
      ),
    ]);

    expect(resizeRes.statusCode).toBe(200);
    expect(cropRes.statusCode).toBe(200);
    expect(rotateRes.statusCode).toBe(200);
    expect(compressRes.statusCode).toBe(200);
    expect(borderRes.statusCode).toBe(200);

    // All must have unique job IDs
    const ids = [resizeRes, cropRes, rotateRes, compressRes, borderRes].map(
      (r) => JSON.parse(r.body).jobId,
    );
    expect(new Set(ids).size).toBe(5);
  }, 120_000);
});

// ═══════════════════════════════════════════════════════════════════════════
// CONCURRENT MIX OF ADVERSARIAL AND VALID REQUESTS
// ═══════════════════════════════════════════════════════════════════════════
describe("Concurrent adversarial and valid requests", () => {
  it("processes valid requests correctly even when invalid ones are fired simultaneously", async () => {
    const garbage = Buffer.from(
      Array.from({ length: 1024 }, () => Math.floor(Math.random() * 256)),
    );
    const emptyBuf = Buffer.alloc(0);

    const [valid1, valid2, invalid1, invalid2, valid3] = await Promise.all([
      app.inject(
        buildToolRequest("resize", PNG_200x150, "valid-1.png", {
          width: 80,
        }),
      ),
      app.inject(
        buildToolRequest("rotate", JPG_100x100, "valid-2.jpg", {
          angle: 180,
        }),
      ),
      // Garbage data
      app.inject(buildToolRequest("resize", garbage, "garbage.png", { width: 50 })),
      // Zero-byte file — manually construct since buildToolRequest uses non-empty image
      (() => {
        const { body, contentType } = createMultipartPayload([
          {
            name: "file",
            filename: "empty.png",
            content: emptyBuf,
            contentType: "image/png",
          },
          {
            name: "settings",
            content: JSON.stringify({ width: 50 }),
          },
        ]);
        return app.inject({
          method: "POST",
          url: "/api/v1/tools/image/resize",
          headers: {
            "content-type": contentType,
            authorization: `Bearer ${adminToken}`,
          },
          body,
        });
      })(),
      app.inject(
        buildToolRequest("compress", PNG_200x150, "valid-3.png", {
          quality: 70,
        }),
      ),
    ]);

    // Valid requests must succeed
    expect(valid1.statusCode).toBe(200);
    expect(valid2.statusCode).toBe(200);
    expect(valid3.statusCode).toBe(200);

    // Invalid requests must fail gracefully
    expect([400, 422]).toContain(invalid1.statusCode);
    expect(invalid2.statusCode).toBe(400);

    // Valid results must have valid job IDs
    expect(JSON.parse(valid1.body).jobId).toBeDefined();
    expect(JSON.parse(valid2.body).jobId).toBeDefined();
    expect(JSON.parse(valid3.body).jobId).toBeDefined();
  }, 120_000);
});

// ═══════════════════════════════════════════════════════════════════════════
// INJECTION IN VARIOUS SETTINGS FIELDS
// ═══════════════════════════════════════════════════════════════════════════
describe("Injection attacks in settings fields", () => {
  it("handles command injection attempt in border color field", async () => {
    const res = await postTool("border", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({
          borderWidth: 10,
          borderColor: "$(rm -rf /)",
        }),
      },
    ]);

    // Zod hex color regex should reject this
    expect(res.statusCode).toBe(400);
  });

  it("handles prototype pollution attempt in settings", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({
          width: 100,
          __proto__: { admin: true },
          constructor: { prototype: { isAdmin: true } },
        }),
      },
    ]);

    // Zod strips unknown keys — the extra fields should be ignored
    expect(res.statusCode).toBe(200);
  });

  it("handles extremely long string in watermark text", async () => {
    const longText = "A".repeat(501); // exceeds 500 char max

    const res = await postTool("watermark-text", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({
          text: longText,
          fontSize: 12,
        }),
      },
    ]);

    // z.string().max(500) should reject this
    expect(res.statusCode).toBe(400);
  });

  it("handles NaN and Infinity in numeric settings", async () => {
    // JSON.stringify(NaN) becomes null, JSON.stringify(Infinity) also becomes null
    // So we pass them as strings which should fail Zod number validation
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: '{"width": "NaN"}',
      },
    ]);

    // Zod should reject string "NaN" for a z.number() field
    expect(res.statusCode).toBe(400);
  });

  it("handles boolean where number expected", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({ width: true }),
      },
    ]);

    expect(res.statusCode).toBe(400);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// CONTENT-TYPE MISMATCHES
// ═══════════════════════════════════════════════════════════════════════════
describe("Content-type header mismatches", () => {
  it("processes image when content-type is application/octet-stream", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "application/octet-stream",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    // Sharp detects format from magic bytes, not content-type
    expect(res.statusCode).toBe(200);
  });

  it("processes image when content-type is completely wrong", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "text/plain",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    // Sharp should still detect the actual format
    expect(res.statusCode).toBe(200);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// RAPID-FIRE SEQUENTIAL REQUESTS TO DIFFERENT TOOLS
// ═══════════════════════════════════════════════════════════════════════════
describe("Rapid sequential requests across tools", () => {
  it("handles 8 sequential requests to alternating tools without errors", async () => {
    const tools = [
      { id: "resize", settings: { width: 80 } },
      { id: "rotate", settings: { angle: 90 } },
      { id: "compress", settings: { quality: 70 } },
      { id: "border", settings: { borderWidth: 5 } },
      { id: "resize", settings: { width: 60 } },
      { id: "rotate", settings: { angle: 180 } },
      { id: "compress", settings: { quality: 50 } },
      { id: "border", settings: { borderWidth: 10 } },
    ];

    for (let i = 0; i < tools.length; i++) {
      const tool = tools[i];
      const res = await app.inject(
        buildToolRequest(tool.id, PNG_200x150, `rapid-${i}.png`, tool.settings),
      );
      expect(res.statusCode).toBe(200);
    }
  }, 60_000);
});

// ═══════════════════════════════════════════════════════════════════════════
// SETTINGS BOUNDARY VALUES
// ═══════════════════════════════════════════════════════════════════════════
describe("Settings boundary values", () => {
  it("accepts compress quality at minimum boundary (1)", async () => {
    const res = await postTool("compress", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ quality: 1 }) },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("accepts compress quality at maximum boundary (100)", async () => {
    const res = await postTool("compress", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ quality: 100 }) },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("rejects compress quality below minimum (0)", async () => {
    const res = await postTool("compress", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ quality: 0 }) },
    ]);

    expect(res.statusCode).toBe(400);
  });

  it("rejects compress quality above maximum (101)", async () => {
    const res = await postTool("compress", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ quality: 101 }) },
    ]);

    expect(res.statusCode).toBe(400);
  });

  it("accepts rotate at 0 degrees", async () => {
    const res = await postTool("rotate", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ angle: 0 }) },
    ]);

    // 0-degree rotation is a valid no-op
    expect([200, 400]).toContain(res.statusCode);
  });

  it("accepts rotate at 359 degrees", async () => {
    const res = await postTool("rotate", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ angle: 359 }) },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("handles border width at maximum (2000)", async () => {
    const res = await postTool("border", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({ borderWidth: 2000 }),
      },
    ]);

    // May succeed or fail at processing, but must not crash
    expect([200, 400, 422]).toContain(res.statusCode);
  });

  it("rejects border width above maximum (2001)", async () => {
    const res = await postTool("border", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({ borderWidth: 2001 }),
      },
    ]);

    expect(res.statusCode).toBe(400);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// MULTI-FORMAT ZERO-BYTE BATCH
// ═══════════════════════════════════════════════════════════════════════════
describe("Batch with only zero-byte files", () => {
  it("rejects batch where all files are zero-byte", async () => {
    const res = await postBatch("resize", [
      {
        name: "file",
        filename: "empty1.png",
        contentType: "image/png",
        content: Buffer.alloc(0),
      },
      {
        name: "file",
        filename: "empty2.png",
        contentType: "image/png",
        content: Buffer.alloc(0),
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    // Zero-byte files are skipped during parsing, so 0 valid files
    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toMatch(/no file/i);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// PIPELINE WITH 1x1 PIXEL IMAGE
// ═══════════════════════════════════════════════════════════════════════════
describe("Pipeline with 1x1 pixel image", () => {
  it("processes 1x1 image through resize + border + compress pipeline", async () => {
    const res = await executePipeline(PNG_1x1, "tiny.png", {
      steps: [
        { toolId: "resize", settings: { width: 50, height: 50 } },
        {
          toolId: "border",
          settings: { borderWidth: 5, borderColor: "#00FF00" },
        },
        { toolId: "compress", settings: { quality: 80 } },
      ],
    });

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.stepsCompleted).toBe(3);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// NON-EXISTENT TOOL ENDPOINTS
// ═══════════════════════════════════════════════════════════════════════════
describe("Non-existent tool and route handling", () => {
  it("returns 404 for completely non-existent tool route", async () => {
    const res = await postTool("this-tool-does-not-exist-at-all", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(404);
  });

  it("returns 404 for tool endpoint with SQL injection in URL", async () => {
    const res = await postTool("resize'; DROP TABLE users; --", [
      {
        name: "file",
        filename: "test.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 100 }) },
    ]);

    expect(res.statusCode).toBe(404);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// MEMORY PRESSURE — LARGE FILE SEQUENTIAL UPLOADS
// ═══════════════════════════════════════════════════════════════════════════
describe("Memory pressure — large file (6.7MB) sequential uploads", () => {
  it("processes 5 sequential resize requests with stress-large.jpg — all succeed", async () => {
    for (let i = 0; i < 5; i++) {
      const res = await postTool("resize", [
        {
          name: "file",
          filename: `stress-${i}.jpg`,
          content: STRESS_LARGE,
          contentType: "image/jpeg",
        },
        { name: "settings", content: JSON.stringify({ width: 200 }) },
      ]);

      expect(res.statusCode).toBe(200);
      const json = JSON.parse(res.body);
      expect(json.jobId).toBeDefined();
      expect(json.processedSize).toBeLessThan(json.originalSize);
    }
  }, 120_000);

  it("processes stress-large.jpg through compress", async () => {
    const res = await postTool("compress", [
      {
        name: "file",
        filename: "stress-compress.jpg",
        content: STRESS_LARGE,
        contentType: "image/jpeg",
      },
      { name: "settings", content: JSON.stringify({ quality: 40 }) },
    ]);

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.processedSize).toBeLessThan(json.originalSize);
  }, 60_000);

  it("processes stress-large.jpg through convert (JPEG to WebP)", async () => {
    const res = await postTool("convert", [
      {
        name: "file",
        filename: "stress-convert.jpg",
        content: STRESS_LARGE,
        contentType: "image/jpeg",
      },
      { name: "settings", content: JSON.stringify({ format: "webp" }) },
    ]);

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.downloadUrl).toBeDefined();
  }, 60_000);

  it("processes stress-large.jpg through info", async () => {
    const res = await postTool("info", [
      {
        name: "file",
        filename: "stress-info.jpg",
        content: STRESS_LARGE,
        contentType: "image/jpeg",
      },
    ]);

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.width).toBeGreaterThan(0);
    expect(json.height).toBeGreaterThan(0);
    expect(json.format).toBe("jpeg");
  }, 60_000);

  it("processes stress-large.jpg through rotate", async () => {
    const res = await postTool("rotate", [
      {
        name: "file",
        filename: "stress-rotate.jpg",
        content: STRESS_LARGE,
        contentType: "image/jpeg",
      },
      { name: "settings", content: JSON.stringify({ angle: 90 }) },
    ]);

    expect(res.statusCode).toBe(200);
  }, 60_000);
});

// ═══════════════════════════════════════════════════════════════════════════
// PIPELINE LIMIT — MANY STEPS
// ═══════════════════════════════════════════════════════════════════════════
describe("Pipeline with many repeated steps", () => {
  it("handles pipeline with 10 resize steps (repeated operation)", async () => {
    const steps = Array.from({ length: 10 }, () => ({
      toolId: "resize",
      settings: { percentage: 95 },
    }));

    const res = await executePipeline(PNG_200x150, "multi-resize.png", { steps });

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.stepsCompleted).toBe(10);
  }, 120_000);

  it("handles pipeline with alternating resize and compress (circular-like)", async () => {
    const steps = [
      { toolId: "resize", settings: { width: 180 } },
      { toolId: "compress", settings: { quality: 90 } },
      { toolId: "resize", settings: { width: 160 } },
      { toolId: "compress", settings: { quality: 80 } },
      { toolId: "resize", settings: { width: 140 } },
      { toolId: "compress", settings: { quality: 70 } },
    ];

    const res = await executePipeline(PNG_200x150, "circular.png", { steps });

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.stepsCompleted).toBe(6);
    // Each resize reduces size, so final should be smaller than original
    expect(json.processedSize).toBeLessThan(json.originalSize);
  }, 60_000);

  it("handles pipeline with duplicate consecutive steps (same tool, same settings)", async () => {
    const steps = Array.from({ length: 5 }, () => ({
      toolId: "compress",
      settings: { quality: 50 },
    }));

    const res = await executePipeline(PNG_200x150, "dupe-steps.png", { steps });

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.stepsCompleted).toBe(5);
  }, 60_000);

  it("rejects pipeline with only non-existent tool IDs", async () => {
    const res = await executePipeline(PNG_200x150, "bad-pipeline.png", {
      steps: [{ toolId: "fake-tool-a" }, { toolId: "fake-tool-b" }],
    });

    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toMatch(/not found/i);
  });

  it("handles pipeline where middle step fails — returns partial step info", async () => {
    // Resize to 10x10, then crop 200x200 (exceeds image), then border
    const res = await executePipeline(PNG_200x150, "mid-fail.png", {
      steps: [
        { toolId: "resize", settings: { width: 10, height: 10 } },
        { toolId: "crop", settings: { left: 0, top: 0, width: 200, height: 200 } },
        { toolId: "border", settings: { borderWidth: 5 } },
      ],
    });

    // Should fail at the crop step (step 2)
    expect([200, 422]).toContain(res.statusCode);
    if (res.statusCode === 422) {
      const json = JSON.parse(res.body);
      expect(json.completedSteps).toBeDefined();
      expect(json.completedSteps.length).toBe(1); // only step 1 completed
    }
  }, 30_000);
});

// ═══════════════════════════════════════════════════════════════════════════
// BATCH LIMITS — BOUNDARY TESTS
// ═══════════════════════════════════════════════════════════════════════════
describe("Batch limits — boundary tests", () => {
  it("handles batch with exactly 1 image (minimum valid batch)", async () => {
    const res = await postBatch("resize", [
      {
        name: "file",
        filename: "single-batch.png",
        contentType: "image/png",
        content: PNG_200x150,
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    expect(res.statusCode).toBe(200);
    expect(res.headers["content-type"]).toBe("application/zip");
    const fileResults = JSON.parse(decodeURIComponent(res.headers["x-file-results"] as string));
    expect(Object.keys(fileResults).length).toBe(1);
  });

  it("handles batch at MAX_BATCH_SIZE limit (10 images)", async () => {
    const fields = Array.from({ length: 10 }, (_, i) => ({
      name: "file",
      filename: `batch-max-${i}.png`,
      contentType: "image/png",
      content: PNG_200x150,
    }));
    fields.push({
      name: "settings",
      filename: undefined as unknown as string,
      contentType: undefined as unknown as string,
      content: JSON.stringify({ width: 30 }) as unknown as Buffer,
    });

    const res = await postBatch("resize", fields);

    expect(res.statusCode).toBe(200);
    expect(res.headers["content-type"]).toBe("application/zip");
    const fileResults = JSON.parse(decodeURIComponent(res.headers["x-file-results"] as string));
    expect(Object.keys(fileResults).length).toBe(10);
  }, 120_000);

  it("rejects batch exceeding MAX_BATCH_SIZE (11 images, limit is 10)", async () => {
    const fields = Array.from({ length: 11 }, (_, i) => ({
      name: "file",
      filename: `batch-over-${i}.png`,
      contentType: "image/png",
      content: PNG_200x150,
    }));
    fields.push({
      name: "settings",
      filename: undefined as unknown as string,
      contentType: undefined as unknown as string,
      content: JSON.stringify({ width: 30 }) as unknown as Buffer,
    });

    const res = await postBatch("resize", fields);

    // @fastify/multipart enforces a files limit at the parser level (set to
    // MAX_BATCH_SIZE in upload.ts), so the request fails at multipart parsing
    // before the application-level batch size check runs.
    expect(res.statusCode).toBe(400);
    const json = JSON.parse(res.body);
    expect(json.error).toBeDefined();
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// UNICODE FILENAMES — ADDITIONAL EDGE CASES
// ═══════════════════════════════════════════════════════════════════════════
describe("Unicode filenames — specific requested patterns", () => {
  it("handles filename with picture frame emoji: \u{1F5BC}️.jpg", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "\u{1F5BC}️.jpg",
        content: JPG_100x100,
        contentType: "image/jpeg",
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.downloadUrl).toBeDefined();
  });

  it("handles filename with Japanese katakana: テスト.png", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "テスト.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("handles filename with ampersand and equals: file&name=special.jpg", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "file&name=special.jpg",
        content: JPG_100x100,
        contentType: "image/jpeg",
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.downloadUrl).toBeDefined();
  });

  it("handles filename with URL-encoded characters: %20photo%2F.png", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "%20photo%2F.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    expect(res.statusCode).toBe(200);
  });

  it("handles filename with backslashes: path\\to\\image.png", async () => {
    const res = await postTool("resize", [
      {
        name: "file",
        filename: "path\\to\\image.png",
        content: PNG_200x150,
        contentType: "image/png",
      },
      { name: "settings", content: JSON.stringify({ width: 50 }) },
    ]);

    // Backslashes should be stripped or sanitized
    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.downloadUrl).not.toContain("\\");
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// EXTREME DIMENSIONS — 1x1 IMAGE CROP LARGER THAN IMAGE
// ═══════════════════════════════════════════════════════════════════════════
describe("1x1 pixel image — crop larger than image", () => {
  it("rejects crop of 100x100 on a 1x1 image", async () => {
    const res = await postTool("crop", [
      {
        name: "file",
        filename: "tiny.png",
        content: PNG_1x1,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({ left: 0, top: 0, width: 100, height: 100 }),
      },
    ]);

    // Crop region extends beyond image — should fail
    expect([400, 422]).toContain(res.statusCode);
  });

  it("rejects crop with offset beyond 1x1 bounds", async () => {
    const res = await postTool("crop", [
      {
        name: "file",
        filename: "tiny.png",
        content: PNG_1x1,
        contentType: "image/png",
      },
      {
        name: "settings",
        content: JSON.stringify({ left: 5, top: 5, width: 1, height: 1 }),
      },
    ]);

    expect([400, 422]).toContain(res.statusCode);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// PIPELINE WITH LARGE FILE
// ═══════════════════════════════════════════════════════════════════════════
describe("Pipeline with large file (6.7MB)", () => {
  it("processes stress-large.jpg through resize + compress pipeline", async () => {
    const res = await executePipeline(STRESS_LARGE, "stress-pipeline.jpg", {
      steps: [
        { toolId: "resize", settings: { width: 400 } },
        { toolId: "compress", settings: { quality: 50 } },
      ],
    });

    expect(res.statusCode).toBe(200);
    const json = JSON.parse(res.body);
    expect(json.stepsCompleted).toBe(2);
    expect(json.processedSize).toBeLessThan(json.originalSize);
  }, 120_000);
});

// ═══════════════════════════════════════════════════════════════════════════
// BATCH WITH LARGE FILES
// ═══════════════════════════════════════════════════════════════════════════
describe("Batch with large files", () => {
  it("processes 2 large files in batch resize", async () => {
    const res = await postBatch("resize", [
      {
        name: "file",
        filename: "stress-1.jpg",
        contentType: "image/jpeg",
        content: STRESS_LARGE,
      },
      {
        name: "file",
        filename: "stress-2.jpg",
        contentType: "image/jpeg",
        content: STRESS_LARGE,
      },
      { name: "settings", content: JSON.stringify({ width: 200 }) },
    ]);

    expect(res.statusCode).toBe(200);
    expect(res.headers["content-type"]).toBe("application/zip");
  }, 120_000);
});
