// arcrun-rag#46「刪掉的知識搜尋還撈得到」第 4 點：中途失敗要看得出來。
//
// DELETE /entries/:id 舊版把向量刪除包成 fire-and-forget（waitUntil(...).catch(()=>{})）——
// 呼叫端完全看不到向量清除是否成功。本測試鎖住新行為：同步 await＋回應帶 vector_deleted，
// 且無論向量刪除成功或失敗，D1 本體都要真的被刪掉（不因向量失敗而擋下本體刪除）。
//
// 測試手法同 search-deprecated-filter.test.ts：fake D1 捕捉 SQL 形狀；mock VECTORIZE 可控
// deleteByIds 成功/失敗，驗證 route 層如何把結果誠實透傳給呼叫端。
import { describe, it, expect } from 'vitest';
import { Hono } from 'hono';
import { entryRoutes } from '../src/routes/entries';
import type { Bindings } from '../src/types';

interface Captured { sql: string; params: unknown[] }

function makeCaptureDB(captured: Captured[]) {
  const prepare = (sql: string) => {
    const rec: Captured = { sql, params: [] };
    captured.push(rec);
    const stmt = {
      bind(...args: unknown[]) { rec.params = args; return stmt; },
      async all<T>() { return { results: [] as T[] }; },
      async first<T>() { return null as unknown as T; },
      async run() { return { success: true }; },
    };
    return stmt;
  };
  return { prepare } as unknown as D1Database;
}

function makeApp(captured: Captured[], extraEnv: Record<string, unknown> = {}) {
  const app = new Hono<{ Bindings: Bindings }>();
  app.route('/entries', entryRoutes);
  const env = { DB: makeCaptureDB(captured), ENVIRONMENT: 'test', ...extraEnv } as unknown as Bindings;
  return { app, env };
}

describe('arcrun-rag#46 — DELETE /entries/:id 失敗可見性', () => {
  it('embed 模組未開 → vector_deleted:null（不適用，非「清成功了」的謊）＋ D1 仍照刪', async () => {
    const captured: Captured[] = [];
    const { app, env } = makeApp(captured); // 無 VECTORIZE/AI
    const res = await app.request('/entries/e123', { method: 'DELETE' }, env);
    expect(res.status).toBe(200);
    const body = (await res.json()) as { success: boolean; vector_deleted: boolean | null };
    expect(body.success).toBe(true);
    expect(body.vector_deleted).toBe(null);
    expect(captured.some((c) => c.sql.includes('DELETE FROM entries WHERE id = ?'))).toBe(true);
  });

  it('模組開＋向量刪除成功 → vector_deleted:true（同步等到結果才回應，不是猜的）', async () => {
    const captured: Captured[] = [];
    const deletedIds: string[][] = [];
    const { app, env } = makeApp(captured, {
      VECTORIZE: {
        async deleteByIds(ids: string[]) { deletedIds.push(ids); return { count: ids.length }; },
      },
      AI: { async run() { return { data: [[0.1]] }; } },
    });
    const res = await app.request('/entries/e123', { method: 'DELETE' }, env);
    const body = (await res.json()) as { success: boolean; vector_deleted: boolean | null };
    expect(body.success).toBe(true);
    expect(body.vector_deleted).toBe(true);
    expect(deletedIds).toEqual([['e123']]); // 真的呼叫了、帶對 id，不是没做就回真
  });

  it('🔴 模組開＋向量刪除失敗 → vector_deleted:false 誠實回報，且 D1 本體仍真的刪掉', async () => {
    const captured: Captured[] = [];
    const { app, env } = makeApp(captured, {
      VECTORIZE: {
        async deleteByIds() { throw new Error('Vectorize 503（模擬故障）'); },
      },
      AI: { async run() { return { data: [[0.1]] }; } },
    });
    const res = await app.request('/entries/e123', { method: 'DELETE' }, env);
    // 舊版這裡的失敗會被 waitUntil(...).catch(()=>{}) 吞掉、caller 永遠看不到；
    // 新版：HTTP 仍是 200（D1 本體真的刪了，這件事沒有失敗），但誠實標出向量那一步失敗了。
    expect(res.status).toBe(200);
    const body = (await res.json()) as { success: boolean; vector_deleted: boolean | null };
    expect(body.success).toBe(true);
    expect(body.vector_deleted).toBe(false);
    // D1 本體不因向量失敗而被擋下——刪除的「本體一定會消失」承諾不打折扣。
    expect(captured.some((c) => c.sql.includes('DELETE FROM entries WHERE id = ?'))).toBe(true);
  });
});
