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

import type {
  KeywordInputRow,
  RawCsvRow,
} from "../core/types.ts";
import { resetEnvCacheForTesting } from "../core/env.ts";
import {
  analyzeProductCombined,
  analyzeProductForKeyword,
  refreshLlmClient,
} from "./standardization-llm.ts";

const createLlmClientMock = vi.hoisted(() => vi.fn());
const completionsCreateMock = vi.hoisted(() => vi.fn());
const resolveTextPromptMock = vi.hoisted(() => vi.fn());

vi.mock("../internal/db.ts", () => ({
  getActiveLlmConfig: vi.fn(async () => null),
}));

vi.mock("../core/llm-client.ts", async (importOriginal) => {
  const actual = await importOriginal<typeof import("../core/llm-client.ts")>();
  return {
    ...actual,
    createLlmClient: createLlmClientMock,
  };
});

vi.mock("../internal/prompt-runtime.js", () => ({
  resolveTextPrompt: resolveTextPromptMock,
}));

const originalEnv = { ...process.env };

const rawRow: RawCsvRow = {
  发票种类: "增值税专票",
  名称: "不锈钢栏杆",
  规格型号: "H=1200",
  品牌: "",
  数量: "10",
  单位: "米",
  "备注/附件": "室外工程",
  收货地址: "上海",
};

const keywordRow: KeywordInputRow = {
  batch_id: "batch-1",
  material_id: "mat-1",
  invoice_type: "增值税专票",
  name: "不锈钢栏杆",
  spec: "H=1200",
  brand: "",
  quantity: "10",
  unit: "米",
  notes: "室外工程",
  delivery_address: "上海",
  material: "不锈钢",
  usage: "护栏",
  simplified_spec: "H=1200",
  standardized_brand: "",
};

function combinedPayload(overrides: Record<string, unknown> = {}) {
  return JSON.stringify({
    material: "不锈钢",
    usage: "护栏",
    simplified_spec: "H=1200",
    brand_term: "",
    keyword_parameter_terms: ["H=1200"],
    modifier_terms: [],
    alternative_terms: ["不锈钢护栏"],
    reasoning: "可采购",
    suggested_keyword: "不锈钢护栏",
    product_type: "成品",
    structure_summary: "栏杆系统",
    component_roles: "主体",
    purchase_type: "可直接采购",
    outcome: "success",
    can_search_1688: true,
    missing_required_fields: [],
    failure_reason: "",
    human_fix_hint: "",
    required_attributes_summary: "材质和高度明确",
    parameter_interpretations: [],
    ...overrides,
  });
}

function keywordPayload(overrides: Record<string, unknown> = {}) {
  return JSON.stringify({
    reasoning: "信息足够",
    suggested_keyword: "不锈钢护栏",
    product_type: "成品",
    structure_summary: "栏杆系统",
    component_roles: "主体",
    purchase_type: "可直接采购",
    outcome: "success",
    can_search_1688: true,
    missing_required_fields: [],
    failure_reason: "",
    human_fix_hint: "",
    required_attributes_summary: "材质和高度明确",
    parameter_interpretations: [],
    ...overrides,
  });
}

function completion(content: string, finishReason = "stop") {
  return {
    choices: [
      {
        finish_reason: finishReason,
        message: { content },
      },
    ],
  };
}

function setDeepSeekEnv(overrides: NodeJS.ProcessEnv = {}) {
  process.env = {
    ...originalEnv,
    NODE_ENV: "test",
    VITEST: "true",
    SUPABASE_URL: "https://example.supabase.co",
    SUPABASE_SERVICE_ROLE_KEY: "service-role-key",
    LLM_BASE_URL: "https://api.deepseek.com",
    LLM_API_KEY: "test-key",
    LLM_MODEL: "global-model",
    LLM_QUEUE_MIN_INTERVAL_MS: "1",
    LLM_RETRY_BASE_DELAY_MS: "1",
    LLM_RETRY_MAX_ATTEMPTS: "2",
    ...overrides,
  };
  resetEnvCacheForTesting();
}

beforeEach(() => {
  vi.clearAllMocks();
  refreshLlmClient();
  setDeepSeekEnv({
    STANDARDIZATION_LLM_MODEL: "deepseek-v4-pro",
    KEYWORD_SEMANTIC_LLM_MODEL: "deepseek-v4-flash",
  });
  createLlmClientMock.mockResolvedValue({
    client: {
      chat: {
        completions: {
          create: completionsCreateMock,
        },
      },
    },
    config: {
      baseURL: "https://api.deepseek.com",
      apiKey: "test-key",
      model: "global-model",
      provider: "deepseek",
    },
  });
  resolveTextPromptMock.mockResolvedValue({ text: "请完成采购物料分析。" });
  completionsCreateMock.mockResolvedValue(completion(combinedPayload()));
});

afterEach(() => {
  process.env = { ...originalEnv };
  resetEnvCacheForTesting();
  refreshLlmClient();
});

describe("standardization LLM stage model config", () => {
  it("uses STANDARDIZATION_LLM_MODEL for DeepSeek standardization requests and disables thinking by default", async () => {
    await analyzeProductCombined(rawRow);

    expect(createLlmClientMock).toHaveBeenCalledWith({ maxRetries: 0, stage: "standardization" });
    const request = completionsCreateMock.mock.calls[0]?.[0] as Record<string, unknown>;
    expect(request.model).toBe("deepseek-v4-pro");
    expect(request.response_format).toEqual({ type: "json_object" });
    expect(request.max_tokens).toBe(8192);
    expect(request.thinking).toEqual({ type: "disabled" });
    expect(request.messages).toEqual(expect.arrayContaining([
      expect.objectContaining({
        role: "system",
        content: expect.stringMatching(/json/i),
      }),
    ]));
    expect(resolveTextPromptMock).toHaveBeenCalledWith(
      "automation.standardization.combined.system",
      { provider: "deepseek", model: "deepseek-v4-pro" },
    );
  });

  it("uses KEYWORD_SEMANTIC_LLM_MODEL for keyword semantic requests", async () => {
    completionsCreateMock.mockResolvedValue(completion(keywordPayload()));

    await analyzeProductForKeyword(keywordRow);

    expect(createLlmClientMock).toHaveBeenCalledWith({ maxRetries: 0, stage: "keyword_semantic" });
    const request = completionsCreateMock.mock.calls[0]?.[0] as Record<string, unknown>;
    expect(request.model).toBe("deepseek-v4-flash");
    expect(resolveTextPromptMock).toHaveBeenCalledWith(
      "automation.standardization.keyword_semantic.system",
      { provider: "deepseek", model: "deepseek-v4-flash" },
    );
  });

  it("passes DeepSeek thinking and reasoning options when enabled", async () => {
    setDeepSeekEnv({
      STANDARDIZATION_LLM_MODEL: "deepseek-v4-pro",
      STANDARDIZATION_LLM_THINKING: "enabled",
      STANDARDIZATION_LLM_REASONING_EFFORT: "high",
      STANDARDIZATION_LLM_MAX_TOKENS: "12000",
    });

    await analyzeProductCombined(rawRow);

    const request = completionsCreateMock.mock.calls[0]?.[0] as Record<string, unknown>;
    expect(request.max_tokens).toBe(12000);
    expect(request.thinking).toEqual({ type: "enabled" });
    expect(request.reasoning_effort).toBe("high");
  });

  it("retries empty LLM content before parsing the standardization result", async () => {
    completionsCreateMock
      .mockResolvedValueOnce(completion(""))
      .mockResolvedValueOnce(completion(combinedPayload({ suggested_keyword: "二次成功" })));

    const result = await analyzeProductCombined(rawRow);

    expect(completionsCreateMock).toHaveBeenCalledTimes(2);
    expect(result.suggested_keyword).toBe("二次成功");
  });

  it("throws a clear max token hint when DeepSeek truncates the response", async () => {
    completionsCreateMock.mockResolvedValue(completion(combinedPayload(), "length"));

    await expect(analyzeProductCombined(rawRow)).rejects.toThrow(/STANDARDIZATION_LLM_MAX_TOKENS/);
  });
});
