/**
 * credential-store-migration T8（回填端點）+ T9（治理端點）測試。
 *
 * 範圍限制（誠實記錄，非本檔缺陷）：`putWorkerSecret` / `deleteWorkerSecret` 呼叫真實
 * Cloudflare API（`fetch` 到 api.cloudflare.com）。測試環境（wrangler.test.toml）刻意不設
 * CF_SECRETS_API_TOKEN/CF_ACCOUNT_ID，所以本檔只覆蓋「不需要真的打 CF API」的路徑：
 *   - D1-only 的 GET /credentials、/credentials/catalog
 *   - migrate 端點的冪等 skip 分支（D1 已有 row 就不會走到 putWorkerSecret）
 *   - migrate 端點在缺 CF token 時對「真的需要新建」的 row 誠實回報 fail（不假綠）
 *   - DELETE 在 D1 無 row 時 fallback 刪舊 KV（不會走到 deleteWorkerSecret）
 * 真正打 CF Workers Secrets API 成功寫入/刪除的路徑，由部署到 leo21c 帳號後的端到端
 * curl 驗證覆蓋（見 credential-store-migration.md T8/T9 完成記錄）。
 */
import { describe, it, expect, beforeEach } from 'vitest';
import { env, SELF } from 'cloudflare:test';

const API_KEY = 'test-tenant-t89';

async function insertCredentialRow(
  name: string,
  secretRef: string,
  extra: Partial<{ service: string | null; sensitivity: string; last_used_at: number | null }> = {},
): Promise<void> {
  await env.CREDENTIALS_DB
    .prepare(
      `INSERT INTO credentials (api_key, name, service, sensitivity, secret_ref, created_at, last_used_at)
       VALUES (?, ?, ?, ?, ?, ?, ?)`,
    )
    .bind(
      API_KEY,
      name,
      extra.service ?? null,
      extra.sensitivity ?? 'standard',
      secretRef,
      Math.floor(Date.now() / 1000),
      extra.last_used_at ?? null,
    )
    .run();
}

async function clearTenantRows(): Promise<void> {
  await env.CREDENTIALS_DB.prepare(`DELETE FROM credentials WHERE api_key = ?`).bind(API_KEY).run();
}

describe('GET /credentials (D1, T9)', () => {
  beforeEach(clearTenantRows);

  it('缺 X-Arcrun-API-Key → 401', async () => {
    const res = await SELF.fetch('https://cypher.test/credentials');
    expect(res.status).toBe(401);
  });

  it('無資料 → 空陣列（非拋錯）', async () => {
    const res = await SELF.fetch('https://cypher.test/credentials', {
      headers: { 'X-Arcrun-API-Key': API_KEY },
    });
    expect(res.status).toBe(200);
    const body = await res.json() as { success: boolean; credentials: unknown[]; total: number };
    expect(body.success).toBe(true);
    expect(body.credentials).toEqual([]);
    expect(body.total).toBe(0);
  });

  it('回傳 metadata，絕不含 secret_ref 或值', async () => {
    await insertCredentialRow('telegram_bot_token', 'CRED_TELEGRAM_BOT_TOKEN_ABCDEF01', { service: 'telegram' });
    const res = await SELF.fetch('https://cypher.test/credentials', {
      headers: { 'X-Arcrun-API-Key': API_KEY },
    });
    const body = await res.json() as { success: boolean; credentials: Array<Record<string, unknown>> };
    expect(body.success).toBe(true);
    expect(body.credentials).toHaveLength(1);
    const row = body.credentials[0];
    expect(row.name).toBe('telegram_bot_token');
    expect(row.service).toBe('telegram');
    expect(row).not.toHaveProperty('secret_ref');
    expect(row).not.toHaveProperty('value');
    expect(JSON.stringify(row)).not.toMatch(/CRED_/);
  });

  it('/credentials/catalog 回同一份資料（Console 相容別名）', async () => {
    await insertCredentialRow('notion_token', 'CRED_NOTION_TOKEN_ABCDEF01');
    const [listRes, catalogRes] = await Promise.all([
      SELF.fetch('https://cypher.test/credentials', { headers: { 'X-Arcrun-API-Key': API_KEY } }),
      SELF.fetch('https://cypher.test/credentials/catalog', { headers: { 'X-Arcrun-API-Key': API_KEY } }),
    ]);
    const [listBody, catalogBody] = await Promise.all([listRes.json(), catalogRes.json()]) as Array<{
      credentials: Array<{ name: string }>;
    }>;
    expect(listBody.credentials.map(r => r.name)).toEqual(catalogBody.credentials.map(r => r.name));
  });
});

describe('POST /credentials/migrate-to-workers-secrets (T8)', () => {
  beforeEach(async () => {
    await clearTenantRows();
    await env.CREDENTIALS_KV.list({ prefix: `${API_KEY}:cred:` }).then(async (list) => {
      for (const k of list.keys) await env.CREDENTIALS_KV.delete(k.name);
    });
  });

  it('缺 X-Arcrun-API-Key → 401', async () => {
    const res = await SELF.fetch('https://cypher.test/credentials/migrate-to-workers-secrets', { method: 'POST' });
    expect(res.status).toBe(401);
  });

  it('D1 已有 row（曾回填過）→ 跳過，不誤判為失敗', async () => {
    await insertCredentialRow('already_migrated', 'CRED_ALREADY_MIGRATED_ABCDEF01');
    // 對應的舊 KV row 仍在（§4.3 回滾錨點：回填後不刪 KV），驗證「有 D1 row 就跳過」而非重打 CF API
    await env.CREDENTIALS_KV.put(
      `${API_KEY}:cred:already_migrated`,
      JSON.stringify({ encrypted: 'irrelevant', iv: 'irrelevant' }),
    );

    const res = await SELF.fetch('https://cypher.test/credentials/migrate-to-workers-secrets', {
      method: 'POST',
      headers: { 'X-Arcrun-API-Key': API_KEY },
    });
    const body = await res.json() as {
      success: boolean; total: number; migrated: number; skipped: number; failed: number;
      results: Array<{ name: string; ok: boolean; skipped?: boolean }>;
    };
    expect(body.success).toBe(true);
    expect(body.total).toBe(1);
    expect(body.skipped).toBe(1);
    expect(body.migrated).toBe(0);
    expect(body.failed).toBe(0);
    expect(body.results[0]).toMatchObject({ name: 'already_migrated', ok: true, skipped: true });
  });

  it('無任何舊 KV row → 空結果，success:true（沒東西可回填不是失敗）', async () => {
    const res = await SELF.fetch('https://cypher.test/credentials/migrate-to-workers-secrets', {
      method: 'POST',
      headers: { 'X-Arcrun-API-Key': API_KEY },
    });
    const body = await res.json() as { success: boolean; total: number };
    expect(body.success).toBe(true);
    expect(body.total).toBe(0);
  });

  it('真正需要回填的 row（D1 無資料）在測試環境缺 CF token 時誠實回報 fail，不假綠', async () => {
    await env.CREDENTIALS_KV.put(
      `${API_KEY}:cred:needs_migration`,
      JSON.stringify({ encrypted: 'ZmFrZQ==', iv: 'ZmFrZQ==' }),
    );
    const res = await SELF.fetch('https://cypher.test/credentials/migrate-to-workers-secrets', {
      method: 'POST',
      headers: { 'X-Arcrun-API-Key': API_KEY },
    });
    const body = await res.json() as {
      success: boolean; failed: number; results: Array<{ name: string; ok: boolean; error?: string }>;
    };
    // 解密本身可能因假造的 base64 密文而失敗，或走到 putWorkerSecret 因缺 CF_SECRETS_API_TOKEN 失敗——
    // 兩者都應該落在「誠實回報 fail」而非靜默假裝成功
    expect(body.success).toBe(false);
    expect(body.failed).toBe(1);
    const row = body.results.find(r => r.name === 'needs_migration');
    expect(row?.ok).toBe(false);
    expect(row?.error).toBeTruthy();
  });
});

describe('DELETE /credentials/:name (T9)', () => {
  beforeEach(clearTenantRows);

  it('D1 無 row（從未回填）→ fallback 刪舊 KV，不誤報找不到', async () => {
    await env.CREDENTIALS_KV.put(
      `${API_KEY}:cred:legacy_only`,
      JSON.stringify({ encrypted: 'x', iv: 'y' }),
    );
    const res = await SELF.fetch('https://cypher.test/credentials/legacy_only', {
      method: 'DELETE',
      headers: { 'X-Arcrun-API-Key': API_KEY },
    });
    const body = await res.json() as { success: boolean; source: string };
    expect(res.status).toBe(200);
    expect(body.success).toBe(true);
    expect(body.source).toBe('legacy-kv');
    const raw = await env.CREDENTIALS_KV.get(`${API_KEY}:cred:legacy_only`);
    expect(raw).toBeNull();
  });
});
