import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ProviderTurnActionPolicy } from "@best-agent/protocol";

const {
  createOpenAIMock,
  defaultChatModelMock,
  defaultNativeModelMock,
  defaultOpenAiProvider,
  defaultWebSearchToolMock,
  generateTextMock,
  streamTextMock,
} = vi.hoisted(() => ({
  createOpenAIMock: vi.fn(),
  defaultChatModelMock: vi.fn(),
  defaultNativeModelMock: vi.fn(),
  defaultOpenAiProvider: Object.assign(vi.fn(), {
    chat: vi.fn(),
    tools: {
      webSearch: vi.fn(),
    },
  }),
  defaultWebSearchToolMock: vi.fn(),
  generateTextMock: vi.fn(),
  streamTextMock: vi.fn(),
}));

vi.mock("ai", async () => {
  const actual = await vi.importActual<typeof import("ai")>("ai");
  return {
    ...actual,
    generateText: generateTextMock,
    streamText: streamTextMock,
    jsonSchema: (schema: unknown) => schema,
    tool: (definition: unknown) => definition,
  };
});

const DEFAULT_DECIDE_ACTION_POLICY: ProviderTurnActionPolicy = {
  allowedActions: ["respond", "tool"],
  directResponseAllowed: true,
};

vi.mock("@ai-sdk/openai", () => ({
  createOpenAI: createOpenAIMock,
  openai: defaultOpenAiProvider,
}));

import { OpenAiSdkProvider } from "./openai-provider.js";

describe("OpenAiSdkProvider native Responses instructions mapping", () => {
  beforeEach(() => {
    createOpenAIMock.mockReset();
    defaultChatModelMock.mockReset();
    defaultNativeModelMock.mockReset();
    defaultOpenAiProvider.mockReset();
    defaultOpenAiProvider.chat.mockReset();
    defaultOpenAiProvider.tools.webSearch.mockReset();
    defaultWebSearchToolMock.mockReset();
    generateTextMock.mockReset();
    streamTextMock.mockReset();

    defaultNativeModelMock.mockImplementation((modelId: string) => ({
      modelId,
      provider: "native-openai",
    }));
    defaultChatModelMock.mockImplementation((modelId: string) => ({
      modelId,
      provider: "compatible-openai",
    }));
    defaultWebSearchToolMock.mockImplementation((options: unknown) => ({
      options,
      tool: "web_search",
    }));
    defaultOpenAiProvider.mockImplementation(defaultNativeModelMock);
    defaultOpenAiProvider.chat.mockImplementation(defaultChatModelMock);
    defaultOpenAiProvider.tools.webSearch.mockImplementation(defaultWebSearchToolMock);

    const createdProvider = Object.assign(
      vi.fn((modelId: string) => ({
        modelId,
        provider: "created-native-openai",
      })),
      {
        chat: vi.fn((modelId: string) => ({
          modelId,
          provider: "created-compatible-openai",
        })),
        tools: {
          webSearch: vi.fn((options: unknown) => ({
            options,
            tool: "created-web_search",
          })),
        },
      },
    );
    createOpenAIMock.mockReturnValue(createdProvider as never);
  });

  it("passes generate() system prompts through Responses instructions", async () => {
    generateTextMock.mockResolvedValue({
      reasoningText: undefined,
      text: "done",
      totalUsage: undefined,
    });

    const provider = new OpenAiSdkProvider({
      compatibilityMode: "native",
      kind: "openai",
      model: "gpt-5.4",
    });

    await provider.generate({
      messages: [{ content: "hi", role: "user" }],
      systemPrompt: "Follow the workspace rules.",
    });

    expect(generateTextMock).toHaveBeenCalledTimes(1);
    expect(generateTextMock).toHaveBeenCalledWith(
      expect.objectContaining({
        messages: [{ content: "hi", role: "user" }],
        providerOptions: expect.objectContaining({
          openai: expect.objectContaining({
            instructions: "Follow the workspace rules.",
            systemMessageMode: "remove",
          }),
        }),
      }),
    );
    expect(generateTextMock.mock.calls[0]?.[0]).not.toHaveProperty("system");
  });

  it("passes merged decide() prompts through Responses instructions", async () => {
    generateTextMock.mockResolvedValue({
      reasoningText: undefined,
      text: "done",
      totalUsage: undefined,
    });

    const provider = new OpenAiSdkProvider({
      compatibilityMode: "native",
      kind: "openai",
      model: "gpt-5.4",
    });

    const result = await provider.decide({
      decisionTransport: "native_tools",
      actionPolicy: DEFAULT_DECIDE_ACTION_POLICY,
      messages: [{ content: "Review the workspace", role: "user" }],
      systemPrompt: "Stay inside the current repository.",
      tools: [],
    });

    expect(result).toMatchObject({
      kind: "respond",
      provider: "openai",
      text: "done",
    });
    expect(generateTextMock).toHaveBeenCalledTimes(1);
    expect(generateTextMock).toHaveBeenCalledWith(
      expect.objectContaining({
        providerOptions: expect.objectContaining({
          openai: expect.objectContaining({
            instructions: expect.stringContaining("Stay inside the current repository."),
            systemMessageMode: "remove",
          }),
        }),
      }),
    );
    expect(generateTextMock.mock.calls[0]?.[0]).not.toHaveProperty("system");
  });

  it("passes searchWeb() system prompts through Responses instructions", async () => {
    streamTextMock.mockReturnValue({
      reasoningText: Promise.resolve(undefined),
      sources: Promise.resolve([]),
      text: Promise.resolve(
        'Search results for "OpenAI" (up to 1):\n1. OpenAI\n   URL: https://openai.com\n   Snippet: AI research lab',
      ),
      toolCalls: Promise.resolve([]),
      totalUsage: Promise.resolve(undefined),
    });

    const provider = new OpenAiSdkProvider({
      compatibilityMode: "native",
      kind: "openai-responses",
      model: "gpt-5.4",
    });

    await provider.searchWeb({
      limit: 1,
      query: "OpenAI",
    });

    expect(generateTextMock).not.toHaveBeenCalled();
    expect(streamTextMock).toHaveBeenCalledTimes(1);
    expect(streamTextMock).toHaveBeenCalledWith(
      expect.objectContaining({
        providerOptions: expect.objectContaining({
          openai: expect.objectContaining({
            instructions: expect.stringContaining(
              "Use the web_search tool to find external sources.",
            ),
            systemMessageMode: "remove",
          }),
        }),
      }),
    );
    expect(streamTextMock.mock.calls[0]?.[0]).not.toHaveProperty("system");
  });
});
