import assert from "node:assert/strict";
import test from "node:test";
import { createBookSourceApi } from "../../lib/server/source-api.ts";

function object(body, options = {}) {
  return {
    body,
    size: options.size ?? body.byteLength,
    httpEtag: options.httpEtag ?? '"etag"',
    httpMetadata: { contentType: options.contentType ?? "application/pdf" },
    range: options.range,
  };
}

test("streams a source file with download-safe metadata", async () => {
  const bytes = new TextEncoder().encode("%PDF-test");
  const api = createBookSourceApi({
    findSource: async () => ({ objectKey: "sources/b1/书.pdf", fileName: "书.pdf", mimeType: "application/pdf" }),
    getObject: async () => object(bytes),
  });
  const response = await api.get(new Request("https://example.test"), "b1");

  assert.equal(response.status, 200);
  assert.equal(response.headers.get("content-type"), "application/pdf");
  assert.equal(response.headers.get("content-length"), String(bytes.byteLength));
  assert.match(response.headers.get("content-disposition"), /filename\*=UTF-8''%E4%B9%A6\.pdf/);
  assert.equal(response.headers.get("accept-ranges"), "bytes");
  assert.equal(await response.text(), "%PDF-test");
});

test("supports a single byte range for PDF.js without exposing an R2 key", async () => {
  const bytes = new TextEncoder().encode("2345");
  let requestedRange;
  const api = createBookSourceApi({
    findSource: async () => ({ objectKey: "secret-key", fileName: "book.pdf", mimeType: "application/pdf" }),
    getObject: async (_key, range) => {
      requestedRange = range;
      return object(bytes, { size: 10, range: { offset: 2, length: 4 } });
    },
  });
  const response = await api.get(new Request("https://example.test", { headers: { range: "bytes=2-5" } }), "b1");

  assert.deepEqual(requestedRange, { offset: 2, length: 4 });
  assert.equal(response.status, 206);
  assert.equal(response.headers.get("content-range"), "bytes 2-5/10");
  assert.doesNotMatch(await response.text(), /secret-key/);
});

test("returns 404 for missing books or objects and 416 for malformed ranges", async () => {
  const missingBook = createBookSourceApi({ findSource: async () => null, getObject: async () => null });
  assert.equal((await missingBook.get(new Request("https://example.test"), "missing")).status, 404);

  const missingObject = createBookSourceApi({
    findSource: async () => ({ objectKey: "missing", fileName: "book.pdf", mimeType: "application/pdf" }),
    getObject: async () => null,
  });
  assert.equal((await missingObject.get(new Request("https://example.test"), "b1")).status, 404);

  const invalidRange = createBookSourceApi({
    findSource: async () => ({ objectKey: "key", fileName: "book.pdf", mimeType: "application/pdf" }),
    getObject: async () => object(new Uint8Array(10)),
  });
  assert.equal((await invalidRange.get(new Request("https://example.test", { headers: { range: "bytes=5-2" } }), "b1")).status, 416);
  assert.equal((await invalidRange.get(new Request("https://example.test", { headers: { range: "bytes=1-2,4-5" } }), "b1")).status, 416);
});
