import { describe, expect, test } from "bun:test";

import {
  buildObservationFromApiCallError,
  buildObservationFromGenerateText,
  buildObservationFromStreamText,
} from "../../../../../src/server/checker/runner/llm/observation";
import { createProviderModel } from "../../../../../src/server/checker/runner/llm/provider";

describe("LLM provider factory", () => {
  test("createProviderModel 返回 model 和 http 初始为 null", () => {
    const { http, model } = createProviderModel({
      headers: {},
      ignoreSSL: false,
      key: "test-key",
      mode: "http",
      model: "gpt-4o-mini",
      options: {},
      prompt: "test",
      provider: "openai",
      providerOptions: {},
      url: "https://api.openai.com/v1",
    });
    expect(http).toBeNull();
    expect(model).toBeDefined();
  });

  test("openai provider 使用 chat 路径", () => {
    const { model } = createProviderModel({
      headers: {},
      ignoreSSL: false,
      key: "test-key",
      mode: "http",
      model: "gpt-4o-mini",
      options: {},
      prompt: "test",
      provider: "openai",
      providerOptions: {},
      url: "https://api.openai.com/v1",
    });
    expect(model).toBeDefined();
  });

  test("openai-responses provider 使用 responses 路径", () => {
    const { model } = createProviderModel({
      headers: {},
      ignoreSSL: false,
      key: "test-key",
      mode: "http",
      model: "gpt-4o-mini",
      options: {},
      prompt: "test",
      provider: "openai-responses",
      providerOptions: {},
      url: "https://api.openai.com/v1",
    });
    expect(model).toBeDefined();
  });

  test("anthropic provider 使用 messages 路径", () => {
    const { model } = createProviderModel({
      headers: {},
      ignoreSSL: false,
      key: "test-key",
      mode: "http",
      model: "claude-3-5-haiku-20241022",
      options: {},
      prompt: "test",
      provider: "anthropic",
      providerOptions: {},
      url: "https://api.anthropic.com/v1",
    });
    expect(model).toBeDefined();
  });

  test("anthropic authToken 映射到 Authorization header", () => {
    const { model } = createProviderModel({
      authToken: "my-bearer-token",
      headers: {},
      ignoreSSL: false,
      key: "",
      mode: "http",
      model: "claude-3-5-haiku-20241022",
      options: {},
      prompt: "test",
      provider: "anthropic",
      providerOptions: {},
      url: "https://api.anthropic.com/v1",
    });
    expect(model).toBeDefined();
  });
});

describe("LLM observation - generateText", () => {
  test("构建非流式 observation", () => {
    const observation = buildObservationFromGenerateText(
      "openai",
      "gpt-4o-mini",
      "http",
      {
        finishReason: "stop",
        rawFinishReason: "stop",
        text: "OK",
        usage: { inputTokens: 12, outputTokens: 2, totalTokens: 14 },
      },
      { headers: { "content-type": "application/json" }, status: 200, statusText: "OK" },
    );

    expect(observation.provider).toBe("openai");
    expect(observation.model).toBe("gpt-4o-mini");
    expect(observation.mode).toBe("http");
    expect(observation.outputText).toBe("OK");
    expect(observation.finishReason).toBe("stop");
    expect(observation.rawFinishReason).toBe("stop");
    expect(observation.usage).toEqual({ inputTokens: 12, outputTokens: 2, totalTokens: 14 });
    expect(observation.stream).toBeNull();
    expect(observation.http?.status).toBe(200);
  });

  test("rawFinishReason 为 undefined 时转为 null", () => {
    const observation = buildObservationFromGenerateText(
      "openai",
      "gpt-4o-mini",
      "http",
      {
        finishReason: "stop",
        rawFinishReason: undefined,
        text: "OK",
        usage: { inputTokens: 5, outputTokens: 1 },
      },
      null,
    );
    expect(observation.rawFinishReason).toBeNull();
  });

  test("usage totalTokens 缺失时自动计算", () => {
    const observation = buildObservationFromGenerateText(
      "openai",
      "gpt-4o-mini",
      "http",
      {
        finishReason: "stop",
        rawFinishReason: "stop",
        text: "OK",
        usage: { inputTokens: 10, outputTokens: 3 },
      },
      null,
    );
    expect(observation.usage?.totalTokens).toBe(13);
  });
});

describe("LLM observation - APICallError", () => {
  test("带 statusCode 的 APICallError 构建 http metadata", async () => {
    const { APICallError } = await import("ai");
    const error = new APICallError({
      message: "Unauthorized",
      requestBodyValues: {},
      responseBody: '{"error":{"message":"Invalid API key"}}',
      responseHeaders: { "content-type": "application/json" },
      statusCode: 401,
      url: "https://api.openai.com/v1/chat/completions",
    });

    const observation = buildObservationFromApiCallError(error, "openai", "gpt-4o-mini", "http");
    expect(observation.http?.status).toBe(401);
    expect(observation.http?.headers).toEqual({ "content-type": "application/json" });
    expect(observation.outputText).toBeNull();
    expect(observation.finishReason).toBeNull();
    expect(observation.usage).toBeNull();
  });
});

describe("LLM observation - streamText", () => {
  test("消费 fullStream 构建流式 observation", async () => {
    const parts = [
      { textDelta: "Hello", type: "text-delta" },
      { textDelta: " world", type: "text-delta" },
      {
        finishReason: "stop",
        rawFinishReason: "stop",
        totalUsage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
        type: "finish",
        usage: { inputTokens: 10, outputTokens: 5 },
      },
    ];

    async function* fakeStream() {
      for (const part of parts) {
        yield await Promise.resolve(part);
      }
    }

    const observation = await buildObservationFromStreamText(
      "openai",
      "gpt-4o-mini",
      "stream",
      fakeStream(),
      { headers: {}, status: 200, statusText: "OK" },
      performance.now() - 100,
    );

    expect(observation.outputText).toBe("Hello world");
    expect(observation.stream?.completed).toBe(true);
    expect(observation.stream?.firstTokenMs).not.toBeNull();
    expect(observation.finishReason).toBe("stop");
    expect(observation.rawFinishReason).toBe("stop");
    expect(observation.usage?.totalTokens).toBe(15);
  });

  test("空 text-delta 不触发 firstTokenMs", async () => {
    const parts = [
      { textDelta: "", type: "text-delta" },
      { textDelta: "OK", type: "text-delta" },
      { finishReason: "stop", type: "finish", usage: { inputTokens: 5, outputTokens: 1 } },
    ];

    async function* fakeStream() {
      for (const part of parts) {
        yield await Promise.resolve(part);
      }
    }

    const observation = await buildObservationFromStreamText(
      "openai",
      "gpt-4o-mini",
      "stream",
      fakeStream(),
      null,
      performance.now(),
    );
    expect(observation.stream?.firstTokenMs).not.toBeNull();
    expect(observation.outputText).toBe("OK");
  });

  test("error part 添加到 warnings", async () => {
    const parts = [
      { error: new Error("stream broken"), type: "error" },
      { finishReason: "error", type: "finish", usage: { inputTokens: 5, outputTokens: 0 } },
    ];

    async function* fakeStream() {
      for (const part of parts) {
        yield await Promise.resolve(part);
      }
    }

    const observation = await buildObservationFromStreamText(
      "openai",
      "gpt-4o-mini",
      "stream",
      fakeStream(),
      null,
      performance.now(),
    );
    expect(observation.warnings).toContain("stream broken");
  });
});
