import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";

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

import { createLlmClient } from "../core/llm-client.ts";
import { resetEnvCacheForTesting } from "../core/env.ts";
import { resolvePromptBundle } from "../internal/prompt-runtime.js";
import {
  resolveSearchVisionModel,
  runImageKeywordSelection,
} from "./keyword-selection.ts";

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

vi.mock("../internal/prompt-runtime.js", () => ({
  getOptionalPayloadObject: vi.fn((bundle, key) => bundle[key]),
  getRequiredPayloadString: vi.fn((payload, key) => payload[key]),
  renderPromptTemplate: vi.fn((template) => template),
  resolvePromptBundle: vi.fn(),
}));

const createLlmClientMock = vi.mocked(createLlmClient);
const resolvePromptBundleMock = vi.mocked(resolvePromptBundle);
const originalEnv = { ...process.env };
let tempDir = "";

function mockVisionResponse(content: string) {
  const completionsCreate = vi.fn().mockResolvedValue({
    choices: [{ message: { content } }],
  });
  createLlmClientMock.mockResolvedValue({
    client: {
      chat: {
        completions: {
          create: completionsCreate,
        },
      },
    },
    config: {
      provider: "gemini",
      baseURL: "https://gemini.example.test",
      apiKey: "redacted",
      model: "stage-search-model",
    },
  } as unknown as Awaited<ReturnType<typeof createLlmClient>>);
  return completionsCreate;
}

describe("keyword-selection search vision model", () => {
  beforeEach(async () => {
    vi.clearAllMocks();
    tempDir = await mkdtemp(path.join(os.tmpdir(), "keyword-selection-"));
    process.env = {
      ...originalEnv,
      NODE_ENV: "test",
      VITEST: "true",
      SUPABASE_URL: "https://example.supabase.co",
      SUPABASE_SERVICE_ROLE_KEY: "service-role-key",
      SEARCH_VISION_LLM_MODEL: "stage-search-model",
      SEARCH_VISION_MODEL: "legacy-search-model",
      LLM_MODEL: "global-model",
    };
    resetEnvCacheForTesting();
    resolvePromptBundleMock.mockResolvedValue({
      promptKey: "automation.search.keyword_fusion.bundle",
      system: "system prompt",
      user_template: "user prompt",
      step_templates: {
        step1_image_analysis_prompt: "image prompt",
        step2_title_analysis_prompt: "title prompt",
        step3_fusion_prompt: "fusion prompt",
      },
    } as Awaited<ReturnType<typeof resolvePromptBundle>>);
  });

  afterEach(async () => {
    process.env = { ...originalEnv };
    resetEnvCacheForTesting();
    if (tempDir) {
      await rm(tempDir, { recursive: true, force: true });
      tempDir = "";
    }
  });

  it("resolves the search vision stage model independently from the global model", () => {
    expect(resolveSearchVisionModel()).toBe("stage-search-model");

    process.env.SEARCH_VISION_LLM_MODEL = "";
    resetEnvCacheForTesting();
    expect(resolveSearchVisionModel()).toBe("legacy-search-model");

    process.env.SEARCH_VISION_MODEL = "";
    resetEnvCacheForTesting();
    expect(resolveSearchVisionModel()).toBe("global-model");

    process.env.LLM_MODEL = "";
    resetEnvCacheForTesting();
    expect(resolveSearchVisionModel()).toBe("gemini-2.5-pro-preview-p");
  });

  it("uses the resolved search vision model for prompt scope, multimodal request, and debug artifact", async () => {
    const sourceImagePath = path.join(tempDir, "source.jpg");
    const screenshotPath = path.join(tempDir, "screenshot.jpg");
    const inputPath = path.join(tempDir, "image-search-keyword-report.json");
    const keywordProcessPath = path.join(tempDir, "keyword-process.json");
    await writeFile(sourceImagePath, Buffer.from("source image"));
    await writeFile(screenshotPath, Buffer.from("screenshot image"));
    await writeFile(inputPath, JSON.stringify({
      material_id: "mat-001",
      source_image_path: sourceImagePath,
      image_search: {
        screenshot_path: screenshotPath,
        top_titles: ["304不锈钢楼梯扶手 玻璃栏杆配件"],
      },
    }), "utf8");
    await writeFile(keywordProcessPath, JSON.stringify({
      ai_keyword_analysis: {
        suggested_keyword: "不锈钢楼梯扶手",
        product_type: "楼梯扶手",
        structure_summary: "完整扶手结构",
        component_roles: { 扶手: "主体" },
      },
    }), "utf8");
    const completionsCreate = mockVisionResponse(JSON.stringify({
      main_keyword: "不锈钢楼梯扶手",
      image_basis: "图片显示完整扶手结构",
      title_analysis: "标题与扶手主体一致",
      decision_logic: "选择不锈钢楼梯扶手",
    }));

    const result = await runImageKeywordSelection({
      inputPath,
      keywordProcessPath,
      outputRoot: tempDir,
      createRunDir: false,
    });

    expect(createLlmClientMock).toHaveBeenCalledWith({ stage: "search_vision", maxRetries: 0 });
    expect(completionsCreate).toHaveBeenCalledWith(expect.objectContaining({
      model: "stage-search-model",
    }));
    expect(resolvePromptBundleMock).toHaveBeenCalledWith(
      "automation.search.keyword_fusion.bundle",
      expect.objectContaining({
        provider: expect.any(String),
        model: "stage-search-model",
      }),
    );
    const debugArchive = JSON.parse(await readFile(result.debugPath, "utf8")) as { model_used: string };
    expect(debugArchive.model_used).toBe("stage-search-model");
  });
});
