import { describe, it, expect } from 'vitest';
import { embedSelfTest } from '../src/embed';
import type { Bindings, Entry } from '../src/types';

// ── Minimal in-memory fakes ───────────────────────────────────────────────
// embedSelfTest issues exactly one DB statement:
//   SELECT * FROM entries WHERE is_embedded = 1 AND content <> '' [AND owner_id = ?]
//   ORDER BY updated_at DESC LIMIT 1
// The fake's `first()` filters the in-memory store accordingly and returns the
// last match (proxy for "ORDER BY updated_at DESC LIMIT 1" given store insertion order).
function mkEntry(id: string, content: string, ownerId = 'leo', is_embedded = 1): Entry {
  return {
    id, content, entry_type: 'block', owner_id: ownerId, parent_id: null, page_name: null,
    refs_json: '[]', tags_json: '[]', task_status: null, content_hash: null, is_embedded,
    confidence: null, metadata_json: JSON.stringify({ embed: true }), created_at: 1, updated_at: 1,
  };
}

function makeFakeDB(store: Entry[]) {
  const prepare = (_sql: string) => {
    let bound: unknown[] = [];
    const stmt = {
      bind(...args: unknown[]) { bound = args; return stmt; },
      async first<T>() {
        const ownerId = bound.length > 0 ? String(bound[0]) : undefined;
        const rows = store.filter(
          (e) => e.is_embedded === 1 && (e.content ?? '').trim() !== '' && (!ownerId || e.owner_id === ownerId),
        );
        return (rows.length > 0 ? rows[rows.length - 1] : null) as unknown as T;
      },
      async all<T>() { return { results: [] as T[] }; },
      async run() { return { success: true }; },
    };
    return stmt;
  };
  return { prepare } as unknown as D1Database;
}

function makeEnv(
  store: Entry[],
  opts: { withBindings?: boolean; matches?: { id: string; score: number }[] } = {},
): Bindings {
  const withBindings = opts.withBindings ?? true;
  return {
    DB: makeFakeDB(store),
    ENVIRONMENT: 'test',
    ...(withBindings
      ? {
          AI: { async run() { return { data: [[0.1, 0.2, 0.3]] }; } },
          VECTORIZE: { async query() { return { matches: opts.matches ?? [] }; } },
        }
      : {}),
  } as unknown as Bindings;
}

describe('embedSelfTest（檢修孔：卡片自我查詢，驗證 index 真的可用）', () => {
  it('module off → enabled:false, tested:false, passed:null（誠實不假綠）', async () => {
    const env = makeEnv([mkEntry('e1', 'hello')], { withBindings: false });
    const r = await embedSelfTest(env);
    expect(r.enabled).toBe(false);
    expect(r.tested).toBe(false);
    expect(r.passed).toBeNull();
    expect(typeof r.note).toBe('string');
  });

  it('沒有任何已嵌入卡片 → tested:false, passed:null（非失敗，只是還沒東西可測）', async () => {
    const env = makeEnv([]);
    const r = await embedSelfTest(env);
    expect(r.enabled).toBe(true);
    expect(r.tested).toBe(false);
    expect(r.passed).toBeNull();
  });

  it('自我查詢能搜到自己 → passed:true', async () => {
    const store = [mkEntry('e1', 'doorbell workflow content')];
    const env = makeEnv(store, { matches: [{ id: 'e1', score: 0.9 }] });
    const r = await embedSelfTest(env);
    expect(r.enabled).toBe(true);
    expect(r.tested).toBe(true);
    expect(r.passed).toBe(true);
  });

  it('自我查詢搜不到自己 → passed:false（Arcrun#11 那種「嵌了但查不到」故障模式）', async () => {
    const store = [mkEntry('e1', 'doorbell workflow content')];
    const env = makeEnv(store, { matches: [{ id: 'some-other-id', score: 0.5 }] });
    const r = await embedSelfTest(env);
    expect(r.enabled).toBe(true);
    expect(r.tested).toBe(true);
    expect(r.passed).toBe(false);
  });

  it('向量化本身失敗（AI 額度用完）→ tested:false＋note 說明故障，不 throw 也不假 passed', async () => {
    const store = [mkEntry('e1', '取樣內容', 'o1')];
    const env = {
      DB: makeFakeDB(store),
      ENVIRONMENT: 'test',
      AI: { async run() { throw new Error('3040: daily limit'); } },
      VECTORIZE: { async query() { return { matches: [] }; } },
    } as unknown as Bindings;
    const r = await embedSelfTest(env, { owner_id: 'o1' });
    expect(r.enabled).toBe(true);
    expect(r.tested).toBe(false);
    expect(r.passed).toBeNull();
    expect(r.note).toContain('沒跑成');
  });

  it('依 owner_id 隔離：別的租戶的已嵌入卡片不會被拿來測', async () => {
    const store = [mkEntry('e1', 'content', 'other-tenant')];
    const env = makeEnv(store, { matches: [] });
    const r = await embedSelfTest(env, { owner_id: 'leo' });
    expect(r.enabled).toBe(true);
    expect(r.tested).toBe(false);
    expect(r.passed).toBeNull();
  });

  it('回應絕不含卡片內容或 entry id（隱私紅線）', async () => {
    const store = [mkEntry('e1', 'this is the secret card body, must never leak')];
    const env = makeEnv(store, { matches: [{ id: 'e1', score: 0.9 }] });
    const r = await embedSelfTest(env);
    const json = JSON.stringify(r);
    expect(json).not.toContain('e1');
    expect(json).not.toContain('secret card body');
  });
});
