import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { afterEach, describe, expect, it, vi } from "vitest";

import {
  createEdgeTtsMiddleware,
  type EdgeTtsMiddlewareOptions,
  type EdgeTtsSynthesizer
} from "./edge-tts-service";

const servers: ReturnType<typeof createServer>[] = [];

afterEach(async () => {
  await Promise.all(
    servers.splice(0).map((server) => new Promise<void>((resolve) => server.close(() => resolve())))
  );
});

async function startServer(options: EdgeTtsMiddlewareOptions) {
  const middleware = createEdgeTtsMiddleware(options);
  const server = createServer((request, response) => {
    void middleware(request, response, () => {
      response.statusCode = 404;
      response.end();
    });
  });
  servers.push(server);
  await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
  const { port } = server.address() as AddressInfo;
  return `http://127.0.0.1:${port}`;
}

function postSpeech(baseUrl: string, body: unknown, headers = true) {
  return fetch(`${baseUrl}/api/tts/edge`, {
    method: "POST",
    ...(headers ? { headers: { "Content-Type": "application/json" } } : {}),
    body: typeof body === "string" ? body : JSON.stringify(body)
  });
}

describe("Edge TTS adapter", () => {
  it("returns synthesized MP3 audio with the server-owned voice", async () => {
    const synthesize = vi.fn<EdgeTtsSynthesizer>(async () => Uint8Array.from([73, 68, 51]));
    const baseUrl = await startServer({ synthesize, defaultVoice: "zh-CN-XiaoxiaoNeural" });

    const response = await postSpeech(baseUrl, { text: "  朗读调度结果  " });

    expect(response.status).toBe(200);
    expect(response.headers.get("content-type")).toBe("audio/mpeg");
    expect(new Uint8Array(await response.arrayBuffer())).toEqual(Uint8Array.from([73, 68, 51]));
    expect(synthesize).toHaveBeenCalledWith("朗读调度结果", "zh-CN-XiaoxiaoNeural");
  });

  it("rejects invalid, unsafe and oversized requests before synthesis", async () => {
    const synthesize = vi.fn<EdgeTtsSynthesizer>(async () => Uint8Array.from([1]));
    const baseUrl = await startServer({ synthesize });

    expect((await postSpeech(baseUrl, "not-json")).status).toBe(400);
    expect((await postSpeech(baseUrl, { text: "测试" }, false)).status).toBe(415);
    expect((await postSpeech(baseUrl, { text: "测试", voice: "unsafe" })).status).toBe(400);
    expect((await postSpeech(baseUrl, { text: "排".repeat(521) })).status).toBe(413);
    expect(synthesize).not.toHaveBeenCalled();
  });

  it("limits requests by client and releases the window", async () => {
    let currentTime = 1_000;
    const synthesize = vi.fn<EdgeTtsSynthesizer>(async () => Uint8Array.from([1]));
    const baseUrl = await startServer({
      synthesize,
      maxRequestsPerWindow: 1,
      rateWindowMs: 1_000,
      now: () => currentTime
    });

    expect((await postSpeech(baseUrl, { text: "第一次" })).status).toBe(200);
    expect((await postSpeech(baseUrl, { text: "第二次" })).status).toBe(429);
    currentTime += 1_000;
    expect((await postSpeech(baseUrl, { text: "新窗口" })).status).toBe(200);
  });

  it("caps concurrent synthesis work", async () => {
    let releaseFirst: ((audio: Uint8Array) => void) | undefined;
    const firstAudio = new Promise<Uint8Array>((resolve) => {
      releaseFirst = resolve;
    });
    const synthesize = vi.fn<EdgeTtsSynthesizer>(() => firstAudio);
    const baseUrl = await startServer({ synthesize, maxConcurrent: 1 });

    const firstRequest = postSpeech(baseUrl, { text: "第一段" });
    await vi.waitFor(() => expect(synthesize).toHaveBeenCalledTimes(1));
    expect((await postSpeech(baseUrl, { text: "第二段" })).status).toBe(429);
    releaseFirst?.(Uint8Array.from([1]));
    expect((await firstRequest).status).toBe(200);
  });

  it("returns a gateway timeout without releasing the occupied slot early", async () => {
    const synthesize = vi.fn<EdgeTtsSynthesizer>(() => new Promise(() => undefined));
    const baseUrl = await startServer({ synthesize, timeoutMs: 5 });

    expect((await postSpeech(baseUrl, { text: "超时测试" })).status).toBe(504);
  });
});
