/**
 * Arcrun#100 — 「畫面上的 0，只准在真的是 0 的時候出現」
 *
 * 病灶（leo 實遇）：總圖頁寫「0 個實體・0 條關聯／知識庫還沒有任何關聯——上傳文件後 AI 會
 * 自動織網」，而他庫裡有 1854 條三元組。那句話會叫他去做一件不需要做的事。
 *
 * 本檔釘住三件事：
 *   ① kbdb-graph-plugin 的 `/triplets` 前綴掛 Bearer 閘，cypher 打它**一定要帶 token**
 *      （console-dashboard 兩支 stats 原本漏帶 → 永遠 401）。
 *   ② 三元組數量的真相源＝KBDB `/records/triplet-stats`（真 SQL COUNT、依 owner 過濾），
 *      **不是** plugin `/triplets/stats` 的 `total`——那是分頁長度（KBDB 端上限 100/500），
 *      1854 條的庫只會回 100。只修 401 不換來源＝把「0」換成「100」，一樣是假的。
 *   ③ 讀不到一律 null / 502 / empty_confirmed=false，**絕不退化成 0**。
 *
 * KBDB／graph-plugin 都打 fetchMock 假 host（wrangler.test.toml KBDB_BASE_URL=https://kbdb.test、
 * KBDB_GRAPH_URL=https://graph.test）＋disableNetConnect——絕不外連。
 */
import { SELF, env, fetchMock } from 'cloudflare:test';
import { beforeAll, afterEach, describe, it, expect } from 'vitest';
import { graphHeaders, graphBase } from '../src/routes/kbdb-proxy';
import type { Bindings } from '../src/types';

const KBDB = 'https://kbdb.test';
const GRAPH = 'https://graph.test';
const TENANT = 'leo'; // wrangler.test.toml CONSOLE_TENANT

beforeAll(() => {
  fetchMock.activate();
  fetchMock.disableNetConnect();
});
afterEach(() => fetchMock.assertNoPendingInterceptors());

/** KBDB `/records/triplet-stats` — 真 COUNT 的形狀：{ success, stats: [{library, triplet_count}] } */
function mockTripletStats(rows: { library: string; triplet_count: number }[] | null, status = 200) {
  fetchMock
    .get(KBDB)
    .intercept({ path: (p: string) => p.startsWith('/records/triplet-stats'), method: 'GET' })
    .reply(status, rows === null ? { success: false, error: 'boom' } : { success: true, stats: rows });
}

// ═══════════════ 1. graphHeaders：打 plugin 的 header 只有一份 ═══════════════

describe('graphHeaders（#100 漂移的根：三處手拼 → 一支函式）', () => {
  it('有 KBDB_INTERNAL_TOKEN → 帶 Bearer（plugin 的 /triplets /graph /search /entities 全靠它）', () => {
    expect(graphHeaders({ KBDB_INTERNAL_TOKEN: 'tok-abc' } as unknown as Bindings)).toEqual({
      Authorization: 'Bearer tok-abc',
    });
  });

  it('沒設 token → 空 headers（plugin 未設 secret 時本來就開放，不硬塞空 Bearer）', () => {
    expect(graphHeaders({} as unknown as Bindings)).toEqual({});
  });

  it('graphBase 仍照舊（KBDB_GRAPH_URL 優先、去尾斜線）', () => {
    expect(graphBase({ KBDB_GRAPH_URL: 'https://graph.test/' } as unknown as Bindings)).toBe('https://graph.test');
  });
});

// ═══════════════ 2. /console/kb-scale-data：數字對得上庫裡真正的數量 ═══════════════

describe('GET /console/kb-scale-data — 三元組數＝KBDB 真 COUNT', () => {
  it('庫裡 1854 條（跨三個庫）→ triplets_total 回 1854，不是 plugin 的分頁長度 100', async () => {
    mockTripletStats([
      { library: 'general', triplet_count: 1200 },
      { library: 'finance', triplet_count: 600 },
      { library: 'ops', triplet_count: 54 },
    ]);
    const res = await SELF.fetch('http://localhost/console/kb-scale-data');
    expect(res.status).toBe(200);
    const d = (await res.json()) as { triplets_total: number | null };
    expect(d.triplets_total).toBe(1854);
  });

  it('反向：triplet-stats 讀不到（500）→ triplets_total = null，**不是 0**', async () => {
    mockTripletStats(null, 500);
    const res = await SELF.fetch('http://localhost/console/kb-scale-data');
    expect(res.status).toBe(200);
    const d = (await res.json()) as { triplets_total: number | null };
    expect(d.triplets_total).toBeNull();
    expect(d.triplets_total).not.toBe(0); // 這一行就是 #100 的整個重點
  });

  it('反向：回應形狀不對（stats 不是陣列）→ null，不半信半疑當 0', async () => {
    fetchMock
      .get(KBDB)
      .intercept({ path: (p: string) => p.startsWith('/records/triplet-stats'), method: 'GET' })
      .reply(200, { success: true, stats: 'oops' });
    const res = await SELF.fetch('http://localhost/console/kb-scale-data');
    const d = (await res.json()) as { triplets_total: number | null };
    expect(d.triplets_total).toBeNull();
  });

  it('真的是 0（庫存在但沒有任何三元組）→ 誠實回 0（0 只在這種時候出現）', async () => {
    mockTripletStats([]);
    const res = await SELF.fetch('http://localhost/console/kb-scale-data');
    const d = (await res.json()) as { triplets_total: number | null };
    expect(d.triplets_total).toBe(0);
  });

  it('kb-scale-data 不再打 graph-plugin（沒有 plugin interceptor 也能拿到數字）', async () => {
    mockTripletStats([{ library: 'general', triplet_count: 7 }]);
    const res = await SELF.fetch('http://localhost/console/kb-scale-data');
    const d = (await res.json()) as { triplets_total: number | null };
    expect(d.triplets_total).toBe(7); // 打 GRAPH 的話 disableNetConnect 會讓它變 null
  });
});

// ═══════════════ 3. /console/dashboard-data：燈號問 plugin、數字問 KBDB ═══════════════

describe('GET /console/dashboard-data — 圖服務健康 vs 三元組數量是兩件事', () => {
  it('打 plugin /triplets/stats **有帶 Bearer** → graph.ok=true；數量仍取 KBDB 真 COUNT', async () => {
    // headers matcher：漏帶 Authorization 就配不到這個 interceptor → 請求失敗 → graph.ok=false
    fetchMock
      .get(GRAPH)
      .intercept({
        path: (p: string) => p.startsWith('/triplets/stats'),
        method: 'GET',
        headers: { authorization: `Bearer ${env.KBDB_INTERNAL_TOKEN}` },
      })
      .reply(200, { total: 100 }); // plugin 的分頁長度，故意與真值不同
    mockTripletStats([{ library: 'general', triplet_count: 1854 }]);
    const res = await SELF.fetch('http://localhost/console/dashboard-data');
    expect(res.status).toBe(200);
    const d = (await res.json()) as {
      system: { graph: { ok: boolean; triplets: number | null } };
      kb: { triplets_total: number | null };
    };
    expect(d.system.graph.ok).toBe(true); // 帶了 token 才會是 true（#100 迴歸閘）
    expect(d.system.graph.triplets).toBe(1854); // 不是 plugin 的 100
    expect(d.kb.triplets_total).toBe(1854);
  });

  it('反向：plugin 打不通 → graph.ok=false，但三元組數照樣是真的（不被服務狀態吞掉）', async () => {
    mockTripletStats([{ library: 'general', triplet_count: 1854 }]);
    const res = await SELF.fetch('http://localhost/console/dashboard-data');
    const d = (await res.json()) as { system: { graph: { ok: boolean; triplets: number | null } } };
    expect(d.system.graph.ok).toBe(false);
    expect(d.system.graph.triplets).toBe(1854);
  });

  it('反向：兩邊都讀不到 → ok=false + triplets=null（不是 0）', async () => {
    const res = await SELF.fetch('http://localhost/console/dashboard-data');
    const d = (await res.json()) as { system: { graph: { ok: boolean; triplets: number | null } } };
    expect(d.system.graph.ok).toBe(false);
    expect(d.system.graph.triplets).toBeNull();
  });
});
