import assert from "node:assert/strict";
import test from "node:test";
import { createUploadApi } from "../../lib/server/upload-api.ts";
import { UploadError } from "../../lib/server/uploads.ts";

const session = {
  id: "upload-1",
  r2UploadId: "secret-r2-id",
  objectKey: "sources/upload-1/book.pdf",
  fileName: "book.pdf",
  mimeType: "application/pdf",
  format: "pdf",
  totalBytes: 10,
  partSize: 8 * 1024 * 1024,
  partCount: 1,
  uploadedParts: [],
  status: "created",
  expiresAt: "2026-08-19T00:00:00.000Z",
  createdAt: "2026-08-12T00:00:00.000Z",
  updatedAt: "2026-08-12T00:00:00.000Z",
};

function setup(overrides = {}) {
  const coordinator = {
    create: async () => structuredClone(session),
    status: async () => structuredClone(session),
    uploadPart: async (_id, partNumber, value) => ({
      partNumber,
      etag: `etag-${value.byteLength}`,
    }),
    complete: async () => ({ ...session, status: "complete" }),
    abort: async () => ({ ...session, status: "aborted" }),
    ...overrides,
  };
  return createUploadApi({
    coordinator,
    registerCompletedBook: async (completed) => ({
      id: completed.id,
      title: "book",
      status: "processing",
    }),
  });
}

test("creates a session without exposing the R2 upload id or object key", async () => {
  const api = setup();
  const response = await api.create(
    new Request("https://example.test/api/uploads", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        fileName: "book.pdf",
        mimeType: "application/pdf",
        totalBytes: 10,
      }),
    }),
  );
  const body = await response.json();

  assert.equal(response.status, 201);
  assert.equal(body.upload.id, "upload-1");
  assert.equal(body.upload.partCount, 1);
  assert.equal("r2UploadId" in body.upload, false);
  assert.equal("objectKey" in body.upload, false);
});

test("accepts an octet-stream part and returns its resumable etag", async () => {
  const api = setup();
  const response = await api.uploadPart(
    new Request("https://example.test/api/uploads/upload-1/parts/1", {
      method: "PUT",
      headers: { "content-type": "application/octet-stream" },
      body: new Uint8Array([1, 2, 3]),
    }),
    "upload-1",
    "1",
  );

  assert.equal(response.status, 200);
  assert.deepEqual(await response.json(), {
    part: { partNumber: 1, etag: "etag-3" },
  });
});

test("completes storage and idempotently registers a processable book", async () => {
  const response = await setup().complete("upload-1");
  assert.equal(response.status, 201);
  assert.deepEqual(await response.json(), {
    book: { id: "upload-1", title: "book", status: "processing" },
  });
});

test("maps validation, missing, expired, and conflict errors", async () => {
  const cases = [
    ["file_too_large", 413],
    ["upload_not_found", 404],
    ["upload_expired", 410],
    ["missing_parts", 409],
    ["signature_mismatch", 415],
  ];
  for (const [code, expectedStatus] of cases) {
    const api = setup({
      status: async () => {
        throw new UploadError(code, `error-${code}`);
      },
    });
    const response = await api.status("upload-1");
    assert.equal(response.status, expectedStatus, code);
    assert.deepEqual(await response.json(), {
      error: { code, message: `error-${code}` },
    });
  }
});

test("rejects malformed JSON, a non-binary part, and invalid part numbers", async () => {
  const api = setup();
  const malformed = await api.create(
    new Request("https://example.test/api/uploads", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: "{",
    }),
  );
  assert.equal(malformed.status, 400);

  const wrongType = await api.uploadPart(
    new Request("https://example.test", {
      method: "PUT",
      headers: { "content-type": "text/plain" },
      body: "abc",
    }),
    "upload-1",
    "1",
  );
  assert.equal(wrongType.status, 415);

  const wrongNumber = await api.uploadPart(
    new Request("https://example.test", {
      method: "PUT",
      headers: { "content-type": "application/octet-stream" },
      body: "abc",
    }),
    "upload-1",
    "1.5",
  );
  assert.equal(wrongNumber.status, 400);
});
