import { expect, test } from "bun:test";
import { createResumableUploadManager, memoryResumableSessionStore } from "../src/index.ts";
import type { StorageDriver } from "../src/index.ts";

function driver() {
  const objects = new Map<string, Uint8Array>();
  const storage: StorageDriver = {
    async put(key, data) {
      objects.set(key, data.slice());
    },
    async get(key) {
      const body = objects.get(key);
      return body ? { body, contentType: "application/octet-stream", size: body.length } : null;
    },
    async delete(key) {
      objects.delete(key);
    },
    publicUrl: () => null,
  };
  return { storage, objects };
}

test("resumable uploads accept out-of-order idempotent chunks and assemble once", async () => {
  const { storage, objects } = driver();
  const manager = createResumableUploadManager({
    driver: storage,
    chunkSize: 3,
    maxBytes: 20,
    accept: ["text/plain"],
  });
  const session = await manager.create({ name: "hello.txt", type: "text/plain", size: 8 });
  expect(session.totalChunks).toBe(3);
  expect((await manager.uploadChunk(session.id, 1, new TextEncoder().encode("lo "))).complete).toBe(
    false,
  );
  await manager.uploadChunk(session.id, 0, new TextEncoder().encode("hel"));
  await manager.uploadChunk(session.id, 0, new TextEncoder().encode("hel"));
  const result = await manager.uploadChunk(session.id, 2, new TextEncoder().encode("!!"));
  expect(result.complete).toBe(true);
  expect(new TextDecoder().decode(objects.get(result.file!.key))).toBe("hello !!");
  expect(await manager.status(session.id)).toBeNull();
});

test("resumable uploads enforce checksums, conflicts, limits, expiry, and cancellation", async () => {
  let now = 0;
  const { storage } = driver();
  const sessions = memoryResumableSessionStore();
  const manager = createResumableUploadManager({
    driver: storage,
    sessions,
    chunkSize: 2,
    maxBytes: 4,
    maxSessions: 1,
    ttlMs: 10,
    now: () => now,
  });
  const session = await manager.create({ name: "data.bin", size: 4 });
  await expect(manager.create({ name: "other.bin", size: 1 })).rejects.toThrow("too many");
  await expect(manager.uploadChunk(session.id, 0, new Uint8Array([1, 2]), "bad")).rejects.toThrow(
    "checksum",
  );
  await manager.uploadChunk(session.id, 0, new Uint8Array([1, 2]));
  await expect(manager.uploadChunk(session.id, 0, new Uint8Array([2, 1]))).rejects.toThrow(
    "different content",
  );
  expect(await manager.cancel(session.id)).toBe(true);
  const expiring = await manager.create({ name: "expire.bin", size: 2 });
  now = 11;
  expect(await manager.prune()).toBe(1);
  await expect(manager.uploadChunk(expiring.id, 0, new Uint8Array([1, 2]))).rejects.toThrow(
    "expired",
  );
});
