import { describe, it, expect, vi, beforeEach } from "vitest";

// Mock ffmpeg-static BEFORE any imports
vi.mock("ffmpeg-static", () => ({ default: "/mock/ffmpeg" }));

// Mock child_process
vi.mock("node:child_process", () => ({
  execFile: vi.fn(),
  spawn: vi.fn(),
}));

// Mock fs/promises for temp file operations
vi.mock("node:fs/promises", () => ({
  writeFile: vi.fn().mockResolvedValue(undefined),
  unlink: vi.fn().mockResolvedValue(undefined),
}));

import { splitSentences, voicePipelineService } from "../services/voice-pipeline.js";
import { execFile as execFileCb } from "node:child_process";

const execFileMock = vi.mocked(execFileCb);

describe("sentence streaming", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("splitSentences", () => {
    it("splits basic sentences on period, question mark, exclamation", () => {
      const result = splitSentences("Hello world. How are you? I am fine.");
      expect(result).toEqual(["Hello world.", "How are you?", "I am fine."]);
    });

    it("keeps abbreviation-style dots intact (Dr., D.C.)", () => {
      const result = splitSentences("Dr. Smith went to D.C. He liked it.");
      // Should split only at sentence boundaries, not abbreviations
      // The sentence "Dr. Smith went to D.C." should be one unit
      expect(result).toHaveLength(2);
      expect(result[0]).toContain("Dr. Smith");
      expect(result[1]).toBe("He liked it.");
    });

    it("returns single-sentence array when text has no sentence boundaries", () => {
      const result = splitSentences("Hello world");
      expect(result).toEqual(["Hello world"]);
    });

    it("filters empty strings", () => {
      const result = splitSentences("Hello.   World.");
      expect(result.every((s) => s.length > 0)).toBe(true);
    });
  });

  describe("synthesizeSentenceStream", () => {
    it("yields one chunk per sentence", async () => {
      execFileMock.mockImplementation((_cmd: any, _args: any, _opts: any, callback: any) => {
        callback(null, Buffer.from("wav-chunk"), "");
        return {} as any;
      });

      const svc = voicePipelineService();
      const text = "Hello world. How are you? I am fine.";

      const chunks: Array<{ index: number; total: number; audio: Buffer }> = [];
      for await (const chunk of svc.synthesizeSentenceStream(text)) {
        chunks.push(chunk);
      }

      expect(chunks).toHaveLength(3);
      expect(chunks[0]).toMatchObject({ index: 0, total: 3 });
      expect(chunks[1]).toMatchObject({ index: 1, total: 3 });
      expect(chunks[2]).toMatchObject({ index: 2, total: 3 });
      chunks.forEach((c) => expect(Buffer.isBuffer(c.audio)).toBe(true));
    });

    it("yields single chunk for single-sentence text", async () => {
      execFileMock.mockImplementation((_cmd: any, _args: any, _opts: any, callback: any) => {
        callback(null, Buffer.from("wav-data"), "");
        return {} as any;
      });

      const svc = voicePipelineService();
      const chunks: Array<{ index: number; total: number; audio: Buffer }> = [];
      for await (const chunk of svc.synthesizeSentenceStream("Hello world.")) {
        chunks.push(chunk);
      }

      expect(chunks).toHaveLength(1);
      expect(chunks[0]).toMatchObject({ index: 0, total: 1 });
    });
  });

  describe("synthesizeMultiLang", () => {
    it("returns a Map with one entry per voiceId", async () => {
      execFileMock.mockImplementation((_cmd: any, _args: any, _opts: any, callback: any) => {
        callback(null, Buffer.from("audio-data"), "");
        return {} as any;
      });

      const svc = voicePipelineService();
      const text = "Hello.";
      const voiceIds = ["en_US-lessac-medium", "da_DK-talesyntese-medium"];

      const result = await svc.synthesizeMultiLang(text, voiceIds);

      expect(result).toBeInstanceOf(Map);
      expect(result.size).toBe(2);
      expect(result.has("en_US-lessac-medium")).toBe(true);
      expect(result.has("da_DK-talesyntese-medium")).toBe(true);
      voiceIds.forEach((v) => expect(Buffer.isBuffer(result.get(v))).toBe(true));
    });

    it("calls piper in parallel (Promise.all semantics)", async () => {
      const callOrder: string[] = [];
      execFileMock.mockImplementation((_cmd: any, args: any, _opts: any, callback: any) => {
        const voiceId = args[1]; // second arg after '--model'
        callOrder.push(voiceId);
        callback(null, Buffer.from("audio"), "");
        return {} as any;
      });

      const svc = voicePipelineService();
      await svc.synthesizeMultiLang("Test.", ["voice-a", "voice-b"]);

      // Both voices should have been requested
      expect(callOrder).toContain("voice-a");
      expect(callOrder).toContain("voice-b");
    });
  });
});
