import { Type } from "@sinclair/typebox";
import { describe, expect, it, vi } from "vitest";
import { getModel } from "../src/models.js";
import { streamSimple } from "../src/stream.js";
import type { Tool } from "../src/types.js";

const mockState = vi.hoisted(() => ({ lastParams: undefined as unknown }));

vi.mock("openai", () => {
  class FakeOpenAI {
    chat = {
      completions: {
        create: async (params: unknown) => {
          mockState.lastParams = params;
          return {
            async *[Symbol.asyncIterator]() {
              yield {
                choices: [{ delta: {}, finish_reason: "stop" }],
                usage: {
                  prompt_tokens: 1,
                  completion_tokens: 1,
                  prompt_tokens_details: { cached_tokens: 0 },
                  completion_tokens_details: { reasoning_tokens: 0 },
                },
              };
            },
          };
        },
      },
    };
  }

  return { default: FakeOpenAI };
});

describe("openai-completions tool_choice", () => {
  it("forwards toolChoice from simple options to payload", async () => {
    const { compat: _compat, ...baseModel } = getModel(
      "openai",
      "gpt-4o-mini",
    )!;
    const model = { ...baseModel, api: "openai-completions" } as const;
    const tools: Tool[] = [
      {
        name: "ping",
        description: "Ping tool",
        parameters: Type.Object({
          ok: Type.Boolean(),
        }),
      },
    ];
    let payload: unknown;

    await streamSimple(
      model,
      {
        messages: [
          {
            role: "user",
            content: "Call ping with ok=true",
            timestamp: Date.now(),
          },
        ],
        tools,
      },
      {
        apiKey: "test",
        toolChoice: "required",
        onPayload: (params: unknown) => {
          payload = params;
        },
      } as unknown as Parameters<typeof streamSimple>[2],
    ).result();

    const params = (payload ?? mockState.lastParams) as {
      tool_choice?: string;
      tools?: unknown[];
    };
    expect(params.tool_choice).toBe("required");
    expect(Array.isArray(params.tools)).toBe(true);
    expect(params.tools?.length ?? 0).toBeGreaterThan(0);
  });

  it("omits strict when compat disables strict mode", async () => {
    const { compat: _compat, ...baseModel } = getModel(
      "openai",
      "gpt-4o-mini",
    )!;
    const model = {
      ...baseModel,
      api: "openai-completions",
      compat: { supportsStrictMode: false },
    } as const;
    const tools: Tool[] = [
      {
        name: "ping",
        description: "Ping tool",
        parameters: Type.Object({
          ok: Type.Boolean(),
        }),
      },
    ];
    let payload: unknown;

    await streamSimple(
      model,
      {
        messages: [
          {
            role: "user",
            content: "Call ping with ok=true",
            timestamp: Date.now(),
          },
        ],
        tools,
      },
      {
        apiKey: "test",
        onPayload: (params: unknown) => {
          payload = params;
        },
      } as unknown as Parameters<typeof streamSimple>[2],
    ).result();

    const params = (payload ?? mockState.lastParams) as {
      tools?: Array<{ function?: Record<string, unknown> }>;
    };
    const tool = params.tools?.[0]?.function;
    expect(tool).toBeTruthy();
    expect(tool?.strict).toBeUndefined();
    expect("strict" in (tool ?? {})).toBe(false);
  });

  it("maps groq qwen3 reasoning levels to default reasoning_effort", async () => {
    const model = getModel("groq", "qwen/qwen3-32b")!;
    let payload: unknown;

    await streamSimple(
      model,
      {
        messages: [
          {
            role: "user",
            content: "Hi",
            timestamp: Date.now(),
          },
        ],
      },
      {
        apiKey: "test",
        reasoning: "medium",
        onPayload: (params: unknown) => {
          payload = params;
        },
      },
    ).result();

    const params = (payload ?? mockState.lastParams) as {
      reasoning_effort?: string;
    };
    expect(params.reasoning_effort).toBe("default");
  });

  it("keeps normal reasoning_effort for groq models without compat mapping", async () => {
    const model = getModel("groq", "openai/gpt-oss-20b")!;
    let payload: unknown;

    await streamSimple(
      model,
      {
        messages: [
          {
            role: "user",
            content: "Hi",
            timestamp: Date.now(),
          },
        ],
      },
      {
        apiKey: "test",
        reasoning: "medium",
        onPayload: (params: unknown) => {
          payload = params;
        },
      },
    ).result();

    const params = (payload ?? mockState.lastParams) as {
      reasoning_effort?: string;
    };
    expect(params.reasoning_effort).toBe("medium");
  });
});
