import { afterEach, describe, expect, it, vi } from "vitest";
import { buildOpenRouterSpeechProvider } from "./speech-provider.js";

const { assertOkOrThrowHttpErrorMock, postJsonRequestMock, resolveProviderHttpRequestConfigMock } =
  vi.hoisted(() => ({
    assertOkOrThrowHttpErrorMock: vi.fn(async () => {}),
    postJsonRequestMock: vi.fn(),
    resolveProviderHttpRequestConfigMock: vi.fn((params: Record<string, unknown>) => ({
      baseUrl: params.baseUrl ?? params.defaultBaseUrl ?? "https://openrouter.ai/api/v1",
      allowPrivateNetwork: false,
      headers: new Headers(params.defaultHeaders as HeadersInit | undefined),
      dispatcherPolicy: undefined,
    })),
  }));

vi.mock("openclaw/plugin-sdk/provider-http", () => ({
  assertOkOrThrowHttpError: assertOkOrThrowHttpErrorMock,
  postJsonRequest: postJsonRequestMock,
  resolveProviderHttpRequestConfig: resolveProviderHttpRequestConfigMock,
}));

describe("openrouter speech provider", () => {
  afterEach(() => {
    assertOkOrThrowHttpErrorMock.mockClear();
    postJsonRequestMock.mockReset();
    resolveProviderHttpRequestConfigMock.mockClear();
    vi.unstubAllEnvs();
  });

  it("normalizes provider-owned speech config", () => {
    const provider = buildOpenRouterSpeechProvider();
    const resolved = provider.resolveConfig?.({
      cfg: {} as never,
      timeoutMs: 30_000,
      rawConfig: {
        providers: {
          openrouter: {
            apiKey: "sk-test",
            baseUrl: "https://openrouter.ai/v1/",
            modelId: "google/gemini-3.1-flash-tts-preview",
            voiceId: "Kore",
            speed: 1.1,
            responseFormat: " MP3 ",
            provider: {
              options: {
                openai: {
                  instructions: "Speak warmly.",
                },
              },
            },
          },
        },
      },
    });

    expect(resolved).toEqual({
      apiKey: "sk-test",
      baseUrl: "https://openrouter.ai/api/v1",
      model: "google/gemini-3.1-flash-tts-preview",
      voice: "Kore",
      speed: 1.1,
      responseFormat: "mp3",
      provider: {
        options: {
          openai: {
            instructions: "Speak warmly.",
          },
        },
      },
    });
  });

  it("synthesizes OpenAI-compatible speech through OpenRouter", async () => {
    const release = vi.fn(async () => {});
    postJsonRequestMock.mockResolvedValue({
      response: new Response(new Uint8Array([1, 2, 3]), { status: 200 }),
      release,
    });

    const provider = buildOpenRouterSpeechProvider();
    const result = await provider.synthesize({
      text: "hello",
      cfg: {
        models: {
          providers: {
            openrouter: {
              apiKey: "sk-openrouter",
              baseUrl: "https://openrouter.ai/v1/",
            },
          },
        },
      } as never,
      providerConfig: {
        model: "openai/gpt-4o-mini-tts-2025-12-15",
        voice: "nova",
        speed: 1.2,
      },
      target: "voice-note",
      timeoutMs: 12_345,
    });

    expect(resolveProviderHttpRequestConfigMock).toHaveBeenCalledWith(
      expect.objectContaining({
        provider: "openrouter",
        capability: "audio",
        baseUrl: "https://openrouter.ai/api/v1",
        defaultHeaders: expect.objectContaining({
          "Content-Type": "application/json",
        }),
      }),
    );
    expect(postJsonRequestMock).toHaveBeenCalledWith(
      expect.objectContaining({
        url: "https://openrouter.ai/api/v1/audio/speech",
        timeoutMs: 12_345,
        body: {
          model: "openai/gpt-4o-mini-tts-2025-12-15",
          input: "hello",
          voice: "nova",
          response_format: "mp3",
          speed: 1.2,
        },
      }),
    );
    expect(result.audioBuffer).toEqual(Buffer.from([1, 2, 3]));
    expect(result.outputFormat).toBe("mp3");
    expect(result.fileExtension).toBe(".mp3");
    expect(result.voiceCompatible).toBe(true);
    expect(release).toHaveBeenCalledOnce();
  });

  it("defaults to a live-proven OpenRouter TTS model", () => {
    const provider = buildOpenRouterSpeechProvider();

    expect(
      provider.resolveConfig?.({ cfg: {} as never, rawConfig: {}, timeoutMs: 30_000 }),
    ).toMatchObject({
      model: "hexgrad/kokoro-82m",
      voice: "af_alloy",
    });
  });

  it("uses OPENROUTER_API_KEY when provider config omits apiKey", () => {
    vi.stubEnv("OPENROUTER_API_KEY", "sk-env");
    const provider = buildOpenRouterSpeechProvider();

    expect(
      provider.isConfigured({
        cfg: {} as never,
        providerConfig: {},
        timeoutMs: 30_000,
      }),
    ).toBe(true);
  });
});
