const test = require("node:test");
const assert = require("node:assert/strict");
const http = require("node:http");
const WebSocket = require("ws");

const {
  buildChatMessages,
  isLoopbackUrl,
  NemotronSidecarClient,
  normalizeOpenAiBaseUrl,
  streamLmStudioChat,
} = require("../src/utils/localProviders");

test("normalizes LM Studio OpenAI-compatible base URLs without selecting models automatically", () => {
  assert.equal(
    normalizeOpenAiBaseUrl("http://127.0.0.1:1234"),
    "http://127.0.0.1:1234/v1",
  );
  assert.equal(
    normalizeOpenAiBaseUrl("http://127.0.0.1:1234/v1/"),
    "http://127.0.0.1:1234/v1",
  );
});

test("detects loopback URLs but does not reject non-local URLs", () => {
  assert.equal(isLoopbackUrl("http://localhost:1234/v1"), true);
  assert.equal(isLoopbackUrl("http://127.0.0.1:1234/v1"), true);
  assert.equal(isLoopbackUrl("http://[::1]:1234/v1"), true);
  assert.equal(isLoopbackUrl("http://192.168.1.40:1234/v1"), false);
  assert.equal(isLoopbackUrl("https://example.com/v1"), false);
});

test("builds OpenAI-compatible image messages for local screenshot analysis", () => {
  const messages = buildChatMessages({
    systemPrompt: "Be useful.",
    history: [{ role: "assistant", content: "Previous answer" }],
    userText: "Analyze this screen",
    imageBase64: "abc123",
  });

  assert.deepEqual(messages[0], { role: "system", content: "Be useful." });
  assert.equal(messages[1].role, "assistant");
  assert.equal(messages[2].role, "user");
  assert.equal(messages[2].content[0].type, "text");
  assert.equal(messages[2].content[1].type, "image_url");
  assert.equal(
    messages[2].content[1].image_url.url,
    "data:image/jpeg;base64,abc123",
  );
});

test("requires a manually configured LM Studio model id", async () => {
  await assert.rejects(
    () =>
      streamLmStudioChat({
        baseUrl: "http://127.0.0.1:1234/v1",
        model: "",
        messages: [{ role: "user", content: "hello" }],
      }),
    /model id is required/,
  );
});

test("streams LM Studio chat completion tokens from an OpenAI-compatible endpoint", async () => {
  let receivedBody = null;
  const server = http.createServer((req, res) => {
    assert.equal(req.method, "POST");
    assert.equal(req.url, "/v1/chat/completions");

    let raw = "";
    req.setEncoding("utf8");
    req.on("data", (chunk) => {
      raw += chunk;
    });
    req.on("end", () => {
      receivedBody = JSON.parse(raw);
      res.writeHead(200, {
        "Content-Type": "text/event-stream",
      });
      res.write('data: {"choices":[{"delta":{"content":"hel"}}]}\n\n');
      res.write('data: {"choices":[{"delta":{"content":"lo"}}]}\n\n');
      res.end("data: [DONE]\n\n");
    });
  });

  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
  const { port } = server.address();
  const accumulations = [];

  const fullText = await streamLmStudioChat({
    baseUrl: `http://127.0.0.1:${port}/v1`,
    model: "manual-gemma4",
    messages: [{ role: "user", content: "hello" }],
    onToken: (_token, accumulated) => accumulations.push(accumulated),
  });

  assert.equal(fullText, "hello");
  assert.deepEqual(accumulations, ["hel", "hello"]);
  assert.equal(receivedBody.model, "manual-gemma4");
  assert.equal(receivedBody.stream, true);
  assert.deepEqual(receivedBody.messages, [{ role: "user", content: "hello" }]);

  await new Promise((resolve) => server.close(resolve));
});

test("Nemotron sidecar client sends start JSON, binary PCM, and emits final transcripts", async () => {
  const server = new WebSocket.Server({ host: "127.0.0.1", port: 0 });
  await new Promise((resolve) => server.once("listening", resolve));

  const { port } = server.address();
  const received = [];

  server.on("connection", (socket) => {
    socket.on("message", (data, isBinary) => {
      if (isBinary) {
        received.push({ isBinary, data: Buffer.from(data) });
        socket.send(JSON.stringify({ type: "final", text: "hello world" }));
        return;
      }

      received.push({ isBinary, data: JSON.parse(data.toString("utf8")) });
      socket.send(JSON.stringify({ type: "ready" }));
    });
  });

  const client = new NemotronSidecarClient({
    url: `ws://127.0.0.1:${port}/v1/asr/stream`,
    language: "en-US",
  });

  const finalPromise = new Promise((resolve) => client.once("final", resolve));
  await client.connect();
  client.sendAudio(Buffer.from([1, 2, 3, 4]));

  assert.equal(await finalPromise, "hello world");
  assert.deepEqual(received[0], {
    isBinary: false,
    data: {
      type: "start",
      sampleRate: 16000,
      channels: 1,
      encoding: "pcm_s16le",
      language: "en-US",
    },
  });
  assert.equal(received[1].isBinary, true);
  assert.deepEqual([...received[1].data], [1, 2, 3, 4]);

  client.close();
  await new Promise((resolve) => server.close(resolve));
});
