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

import { resetEnvCacheForTesting } from "./env.ts";
import {
  createLlmClient,
  resolveLlmRuntimeConfig,
} from "./llm-client.ts";

const getActiveLlmConfigMock = vi.hoisted(() => vi.fn());
const listActiveLlmProviderConfigsMock = vi.hoisted(() => vi.fn());
const listLlmStageRoutesMock = vi.hoisted(() => vi.fn());

vi.mock("../internal/db.ts", () => ({
  getActiveLlmConfig: getActiveLlmConfigMock,
  listActiveLlmProviderConfigs: listActiveLlmProviderConfigsMock,
  listLlmStageRoutes: listLlmStageRoutesMock,
}));

const originalEnv = { ...process.env };
const minimalValidEnv: NodeJS.ProcessEnv = {
  NODE_ENV: "test",
  VITEST: "true",
  SUPABASE_URL: "https://example.supabase.co",
  SUPABASE_SERVICE_ROLE_KEY: "service-role-key",
};

afterEach(() => {
  process.env = { ...originalEnv };
  resetEnvCacheForTesting();
  vi.clearAllMocks();
  vi.unstubAllGlobals();
});

describe("resolveLlmRuntimeConfig", () => {
  it("uses stage provider env credentials without global LLM config", async () => {
    process.env = {
      ...originalEnv,
      ...minimalValidEnv,
      STANDARDIZATION_LLM_PROVIDER: "deepseek",
      STANDARDIZATION_LLM_MODEL: "deepseek-v4-pro",
      DEEPSEEK_LLM_BASE_URL: "https://api.deepseek.com",
      DEEPSEEK_LLM_API_KEY: "deepseek-key",
    };

    const config = await resolveLlmRuntimeConfig("standardization");

    expect(config).toEqual({
      provider: "deepseek",
      baseURL: "https://api.deepseek.com",
      apiKey: "deepseek-key",
      model: "deepseek-v4-pro",
    });
    expect(getActiveLlmConfigMock).not.toHaveBeenCalled();
  });

  it("resolves fixed search batch to GLM provider credentials", async () => {
    process.env = {
      ...originalEnv,
      ...minimalValidEnv,
      FIXED_SEARCH_LLM_PROVIDER: "glm",
      FIXED_SEARCH_LLM_MODEL: "glm-4-plus",
      GLM_LLM_BASE_URL: "https://open.bigmodel.cn/api/paas/v4",
      GLM_LLM_API_KEY: "glm-key",
    };

    const config = await resolveLlmRuntimeConfig("fixed_search_batch");

    expect(config.provider).toBe("glm");
    expect(config.baseURL).toBe("https://open.bigmodel.cn/api/paas/v4");
    expect(config.apiKey).toBe("glm-key");
    expect(config.model).toBe("glm-4-plus");
  });

  it("resolves document extraction to GLM provider credentials", async () => {
    process.env = {
      ...originalEnv,
      ...minimalValidEnv,
      DOCUMENT_EXTRACTION_LLM_PROVIDER: "glm",
      DOCUMENT_EXTRACTION_LLM_MODEL: "glm-5.1",
      GLM_LLM_BASE_URL: "https://open.bigmodel.cn/api/paas/v4",
      GLM_LLM_API_KEY: "glm-key",
    };

    const config = await resolveLlmRuntimeConfig("document_extraction");

    expect(config).toEqual({
      provider: "glm",
      baseURL: "https://open.bigmodel.cn/api/paas/v4",
      apiKey: "glm-key",
      model: "glm-5.1",
    });
  });

  it("resolves document OCR to MiMo provider credentials", async () => {
    process.env = {
      ...originalEnv,
      ...minimalValidEnv,
      DOCUMENT_OCR_LLM_PROVIDER: "mimo",
      DOCUMENT_OCR_LLM_MODEL: "mimo-v2.5",
      MIMO_LLM_BASE_URL: "https://api.xiaomimimo.com/v1",
      MIMO_LLM_API_KEY: "mimo-key",
    };

    const config = await resolveLlmRuntimeConfig("document_ocr");

    expect(config).toEqual({
      provider: "mimo",
      baseURL: "https://api.xiaomimimo.com/v1",
      apiKey: "mimo-key",
      model: "mimo-v2.5",
    });
  });

  it("calls MiMo chat completions with the api-key header", async () => {
    process.env = {
      ...originalEnv,
      ...minimalValidEnv,
      DOCUMENT_OCR_LLM_PROVIDER: "mimo",
      DOCUMENT_OCR_LLM_MODEL: "mimo-v2.5",
      MIMO_LLM_BASE_URL: "https://api.xiaomimimo.com/v1",
      MIMO_LLM_API_KEY: "mimo-key",
    };
    const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({
      choices: [{ message: { content: "OCR text" } }],
    }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    }));
    vi.stubGlobal("fetch", fetchMock);

    const { client } = await createLlmClient({ stage: "document_ocr", maxRetries: 0 });
    const response = await client.chat.completions.create({
      model: "mimo-v2.5",
      messages: [{ role: "user", content: "hello" }],
      temperature: 0.1,
      max_completion_tokens: 2048,
    } as never);

    expect(response.choices[0]?.message?.content).toBe("OCR text");
    expect(fetchMock).toHaveBeenCalledWith(
      "https://api.xiaomimimo.com/v1/chat/completions",
      expect.objectContaining({
        method: "POST",
        headers: expect.objectContaining({
          "api-key": "mimo-key",
          "Content-Type": "application/json",
        }),
        body: JSON.stringify({
          model: "mimo-v2.5",
          messages: [{ role: "user", content: "hello" }],
          temperature: 0.1,
          max_completion_tokens: 2048,
        }),
      }),
    );
  });

  it("uses new DB provider configs and stage routes when env route is absent", async () => {
    process.env = {
      ...originalEnv,
      ...minimalValidEnv,
    };
    listActiveLlmProviderConfigsMock.mockResolvedValue([
      {
        provider_key: "deepseek",
        base_url: "https://api.deepseek.com",
        api_key: "db-deepseek-key",
        is_active: true,
      },
    ]);
    listLlmStageRoutesMock.mockResolvedValue([
      {
        stage: "standardization",
        provider_key: "deepseek",
        model: "deepseek-v4-pro",
      },
    ]);

    const config = await resolveLlmRuntimeConfig("standardization");

    expect(config).toMatchObject({
      provider: "deepseek",
      baseURL: "https://api.deepseek.com",
      apiKey: "db-deepseek-key",
      model: "deepseek-v4-pro",
    });
    expect(getActiveLlmConfigMock).not.toHaveBeenCalled();
  });

  it("fails document extraction without explicit provider or legacy global fallback", async () => {
    process.env = {
      ...originalEnv,
      ...minimalValidEnv,
      DOCUMENT_EXTRACTION_LLM_MODEL: "glm-5.1",
    };
    listActiveLlmProviderConfigsMock.mockResolvedValue([]);
    listLlmStageRoutesMock.mockResolvedValue([]);

    await expect(resolveLlmRuntimeConfig("document_extraction")).rejects.toThrow(
      /DOCUMENT_EXTRACTION_LLM_PROVIDER|文档提取|LLM_BASE_URL/,
    );
  });

  it("keeps legacy global env as final fallback", async () => {
    process.env = {
      ...originalEnv,
      ...minimalValidEnv,
      LLM_PROVIDER: "glm",
      LLM_BASE_URL: "https://legacy.example.test/v1/",
      LLM_API_KEY: "legacy-key",
      LLM_MODEL: "legacy-model",
      DOCUMENT_EXTRACTION_LLM_MODEL: "glm-5.1",
    };
    listActiveLlmProviderConfigsMock.mockResolvedValue([]);
    listLlmStageRoutesMock.mockResolvedValue([]);

    const config = await resolveLlmRuntimeConfig("document_extraction");

    expect(config).toEqual({
      provider: "glm",
      baseURL: "https://legacy.example.test/v1",
      apiKey: "legacy-key",
      model: "glm-5.1",
    });
  });
});
