// 嵌入模型可配置＋換代（Arcrun#59）—— 2026-08-03
//
// 為什麼要有這個測試（別刪）：
//   舊版把模型寫死成 `@cf/baai/bge-base-en-v1.5`，那是**英文模型**，拿來嵌中文等於嵌一堆
//   看不懂的 token。5 組中文測資實測（margin = min(相關) − max(無關)，≤0 代表排序錯）：
//     bge-base-en-v1.5   768   排序正確 2/5   平均 margin -0.0413  ← 舊，五組錯三組
//     embeddinggemma-300m 768   4/5   +0.1275
//     **bge-m3           1024   5/5   +0.1410**  ← 新（品質最好、而且最快 959ms）
//     qwen3-embedding-0.6b 1024  4/5   +0.1381
//   最刺眼的一組：問「知識庫問答為什麼要標出處？」→「會議室預約規則」0.7789 竟然高於
//   真正相關的 0.7306 ＝ leo 2026-07-18 回報「問 RAG 卻引用會議室規範」的直接數字。
//
// 本檔守三件事：
//   ① 預設模型是 m3（有人手滑改回英文模型會紅）
//   ② env.EMBED_MODEL 真的能覆寫（#59 要的「可配置」）
//   ③ **查詢端與寫入端用同一顆模型**——兩邊不同步是最惡毒的 bug：
//      不會報錯、只是分數全是垃圾，而且從外面完全看不出來。
import { describe, it, expect } from 'vitest';
import { embedOnWrite, semanticSearch } from '../src/embed';
import type { Bindings, Entry } from '../src/types';

function mkEnv(over: Partial<Bindings> = {}) {
  const calls: { model: string; text: string[] }[] = [];
  const env = {
    AI: {
      run: async (model: string, input: { text: string[] }) => {
        calls.push({ model, text: input.text });
        return { data: input.text.map(() => [0.1, 0.2, 0.3]) };
      },
    },
    VECTORIZE: {
      upsert: async () => undefined,
      query: async () => ({ matches: [] }),
    },
    DB: {
      prepare: () => ({ bind: () => ({ run: async () => ({}), all: async () => ({ results: [] }) }) }),
    },
    ...over,
  } as unknown as Bindings;
  return { env, calls };
}

const entry = {
  id: 'e_1',
  content: '出處標註讓使用者能回頭核對答案來源。',
  entry_type: 'block',
  owner_id: 'demo',
  metadata_json: JSON.stringify({ embed: true }),
} as unknown as Entry;

describe('嵌入模型（Arcrun#59）', () => {
  it('預設是 bge-m3——不得退回英文模型（中文會排錯）', async () => {
    const { env, calls } = mkEnv();
    await embedOnWrite(env, entry);
    expect(calls).toHaveLength(1);
    expect(calls[0].model).toBe('@cf/baai/bge-m3');
    expect(calls[0].model).not.toContain('-en-'); // 英文模型一律不准當預設
  });

  it('env.EMBED_MODEL 可覆寫（#59 的「模型應可配置」）', async () => {
    const { env, calls } = mkEnv({ EMBED_MODEL: '@cf/google/embeddinggemma-300m' });
    await embedOnWrite(env, entry);
    expect(calls[0].model).toBe('@cf/google/embeddinggemma-300m');
  });

  it('空字串／空白的 EMBED_MODEL 視為沒設，回退預設（不會把空字串當模型名送出去）', async () => {
    for (const bad of ['', '   ']) {
      const { env, calls } = mkEnv({ EMBED_MODEL: bad });
      await embedOnWrite(env, entry);
      expect(calls[0].model).toBe('@cf/baai/bge-m3');
    }
  });

  it('🔴 查詢端與寫入端必須是同一顆模型（不同步＝分數全垃圾且不會報錯）', async () => {
    const { env, calls } = mkEnv({ EMBED_MODEL: '@cf/baai/bge-m3' });
    await embedOnWrite(env, entry);          // 寫入端
    await semanticSearch(env, '為什麼要標出處？'); // 查詢端
    expect(calls.length).toBeGreaterThanOrEqual(2);
    const models = new Set(calls.map((c) => c.model));
    expect(models.size).toBe(1);
  });
});
