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

const segment = {
  id: "segment-1", bookId: "book-1", chapterId: "chapter-1",
  text: "第一句。", language: "zh-CN", textVersion: 2,
};

function setup(overrides = {}) {
  const objects = new Map();
  const deleted = [];
  const registered = [];
  const repository = {
    segmentForAudio: async (id) => id === segment.id ? segment : null,
    find: async () => null,
    objectForAudio: async () => null,
    listChapter: async () => [],
    cleanupForAdmission: async () => ({ allowed: true, objectKeys: [], evictedBookIds: [] }),
    registerReady: async (input) => {
      registered.push(input);
      return { id: "audio-1", segmentId: input.segmentId, textVersion: input.textVersion,
        voice: input.voice, durationMs: input.durationMs, bytes: input.bytes,
        status: "ready", lastUsedAt: input.now, url: `/api/segments/${input.segmentId}/audio?voice=${input.voice}` };
    },
    ...overrides.repository,
  };
  const bucket = {
    put: async (key, value, options) => { objects.set(key, { bytes: new Uint8Array(await new Response(value).arrayBuffer()), options }); },
    get: async (key) => objects.get(key) ? { body: objects.get(key).bytes, size: objects.get(key).bytes.byteLength, httpEtag: '"etag"' } : null,
    delete: async (keys) => { deleted.push(...(Array.isArray(keys) ? keys : [keys])); },
  };
  return {
    api: createAudioApi({ repository, bucket, now: () => new Date("2026-08-13T02:00:00.000Z") }),
    objects, deleted, registered,
  };
}

test("describes the stored segment without accepting arbitrary TTS text", async () => {
  const { api } = setup();
  const response = await api.describe("segment-1", "zh-CN-XiaoxiaoNeural");
  assert.equal(response.status, 200);
  assert.deepEqual(await response.json(), {
    segment,
    voice: "zh-CN-XiaoxiaoNeural",
    audio: null,
  });
  assert.equal((await api.describe("missing", "voice")).status, 404);
});

test("uploads only a bounded MP3 for the current text version and records eviction", async () => {
  const { api, objects, registered, deleted } = setup({
    repository: {
      cleanupForAdmission: async () => ({ allowed: true, objectKeys: ["audio/old.mp3"], evictedBookIds: ["old-book"] }),
    },
  });
  const mp3 = new Uint8Array([0x49, 0x44, 0x33, 4, 0, 0, 0, 0]);
  const response = await api.upload(new Request("https://example.test", {
    method: "PUT",
    headers: {
      "content-type": "audio/mpeg",
      "x-text-version": "2",
      "x-voice": "zh-CN-XiaoxiaoNeural",
      "x-duration-ms": "2400",
    },
    body: mp3,
  }), "segment-1");

  assert.equal(response.status, 201);
  assert.deepEqual(deleted, ["audio/old.mp3"]);
  assert.equal(objects.size, 1);
  assert.match([...objects.keys()][0], /^audio\/book-1\/segment-1\/v2\//);
  assert.equal(registered[0].bytes, mp3.byteLength);
  assert.deepEqual((await response.json()).evictedBookIds, ["old-book"]);
});

test("rejects stale versions, non-MP3 content, empty audio, and oversized audio", async () => {
  const { api } = setup();
  const request = (body, headers = {}) => new Request("https://example.test", {
    method: "PUT",
    headers: { "content-type": "audio/mpeg", "x-text-version": "2", "x-voice": "voice", ...headers },
    body,
  });
  assert.equal((await api.upload(request(new Uint8Array([0x49, 0x44, 0x33]), { "x-text-version": "1" }), "segment-1")).status, 409);
  assert.equal((await api.upload(request(new Uint8Array([1, 2, 3])), "segment-1")).status, 415);
  assert.equal((await api.upload(request(new Uint8Array()), "segment-1")).status, 400);
  assert.equal((await api.upload(request(new Uint8Array(10), { "content-length": String(13 * 1024 * 1024) }), "segment-1")).status, 413);
});

test("streams current audio privately and lists chapter cache", async () => {
  const objectKey = "audio/book-1/current.mp3";
  const { api, objects } = setup({
    repository: {
      objectForAudio: async () => ({ objectKey, audio: { bytes: 3 } }),
      listChapter: async () => [{ segmentId: "segment-1", url: "/api/segments/segment-1/audio?voice=voice" }],
    },
  });
  objects.set(objectKey, { bytes: new Uint8Array([0x49, 0x44, 0x33]), options: {} });
  const streamed = await api.stream(new Request("https://example.test"), "segment-1", "voice");
  assert.equal(streamed.status, 200);
  assert.equal(streamed.headers.get("content-type"), "audio/mpeg");
  assert.equal(streamed.headers.get("cache-control"), "private, no-store");
  assert.deepEqual(await (await api.listChapter("book-1", "chapter-1")).json(), {
    audio: [{ segmentId: "segment-1", url: "/api/segments/segment-1/audio?voice=voice" }],
  });
});
