import { expect, test } from "bun:test";
import {
  createTemporaryObjectCleaner,
  ffmpegVideoTranscoder,
  memoryQuotaStore,
  multipartUpload,
  postgresQuotaStore,
} from "../src/index.ts";

test("quota stores enforce durable byte and object limits", async () => {
  const quota = memoryQuotaStore();
  expect(await quota.reserve("tenant", 60, { bytes: 100, objects: 2 })).toBeTrue();
  expect(await quota.reserve("tenant", 50, { bytes: 100, objects: 2 })).toBeFalse();
  await quota.release("tenant", 60);
  expect(await quota.get("tenant")).toMatchObject({ bytes: 0, objects: 0 });
  const calls: unknown[][] = [];
  const sql = postgresQuotaStore({
    async query<T>(_sql: string, parameters?: unknown[]) {
      calls.push(parameters ?? []);
      return { rows: [{ owner: "tenant" } as T] };
    },
  });
  expect(await sql.reserve("tenant", 10, { bytes: 20 })).toBeTrue();
  expect(calls[0]?.[0]).toBe("tenant");
});

test("multipart uploader limits concurrency, completes and aborts failures", async () => {
  const uploaded: number[] = [];
  let completed = false;
  let aborted = false;
  const client = {
    async create() {
      return "upload";
    },
    async uploadPart(_id: string, _key: string, part: number) {
      uploaded.push(part);
      return `etag-${part}`;
    },
    async complete() {
      completed = true;
    },
    async abort() {
      aborted = true;
    },
  };
  await multipartUpload(
    client,
    "video",
    new Uint8Array(11 * 1024 * 1024),
    { contentType: "video/mp4" },
    { partBytes: 5 * 1024 * 1024, concurrency: 2 },
  );
  expect(uploaded).toEqual([1, 2, 3]);
  expect(completed).toBeTrue();
  expect(aborted).toBeFalse();
});

test("temporary cleanup and video transcoding are bounded and injectable", async () => {
  let now = 10;
  const deleted: string[] = [];
  const cleaner = createTemporaryObjectCleaner(
    {
      async put() {},
      async get() {
        return null;
      },
      async delete(key) {
        deleted.push(key);
      },
      publicUrl() {
        return null;
      },
    },
    { now: () => now },
  );
  cleaner.track("tmp/a", 5);
  expect(await cleaner.cleanup()).toBe(0);
  now = 15;
  expect(await cleaner.cleanup()).toBe(1);
  expect(deleted).toEqual(["tmp/a"]);
  let command: string[] = [];
  await ffmpegVideoTranscoder({
    spawn(args) {
      command = args;
      return { exited: Promise.resolve(0) };
    },
  })("input.mov", "output.mp4", { format: "mp4", width: 1280, videoBitrateKbps: 2000 });
  expect(command).toContain("scale=1280:-2");
});
