/**
 * v0.20.0 Cathedral II Layer 13 E2 — reindex-code tests.
 *
 * Validates the contract that makes reindex-code safe to ship:
 *   - runReindexCode walks code pages from the DB (not the filesystem).
 *   - Returns pre-computed cost + token estimates without running embeddings.
 *   - --dry-run never imports (status='dry_run', 0 reindexed).
 *   - --force bypasses importCodeFile's content_hash early-return.
 *   - Pages without frontmatter.file fail cleanly (counted, not thrown).
 *   - Batching walks every code page regardless of total count.
 *   - --source filter scopes to one sources row.
 */

import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
import { runReindexCode } from '../src/commands/reindex-code.ts';
import { configureGateway, resetGateway } from '../src/core/ai/gateway.ts';

describe('Layer 13 E2 — runReindexCode', () => {
  let engine: PGLiteEngine;
  let prevNudgeEnv: string | undefined;

  beforeAll(async () => {
    // v0.37.3 — runReindexCode now reads getEmbeddingModelName() from the
    // gateway for both the cost-preview model field and the code-model
    // nudge. Configure the gateway with the historical default so the
    // existing model-name assertion stays exact, and suppress the nudge
    // so test stderr stays clean.
    prevNudgeEnv = process.env.GBRAIN_NO_CODE_MODEL_NUDGE;
    process.env.GBRAIN_NO_CODE_MODEL_NUDGE = '1';
    configureGateway({
      embedding_model: 'openai:text-embedding-3-large',
      embedding_dimensions: 1536,
      env: { OPENAI_API_KEY: 'sk-test' },
    });

    engine = new PGLiteEngine();
    await engine.connect({});
    await engine.initSchema();

    // Two code pages with frontmatter.file populated. compiled_truth holds
    // the full content (as importCodeFile writes it).
    await engine.putPage('src-foo-ts', {
      type: 'code',
      page_kind: 'code',
      title: 'src/foo.ts (typescript)',
      compiled_truth: 'export function foo() { return 42; }',
      timeline: '',
      frontmatter: { language: 'typescript', file: 'src/foo.ts' },
    });
    await engine.putPage('src-bar-py', {
      type: 'code',
      page_kind: 'code',
      title: 'src/bar.py (python)',
      compiled_truth: 'def bar():\n    return 42\n',
      timeline: '',
      frontmatter: { language: 'python', file: 'src/bar.py' },
    });

    // One code page with missing frontmatter.file — should fail cleanly.
    await engine.putPage('src-bad-ts', {
      type: 'code',
      page_kind: 'code',
      title: 'src/bad.ts (typescript)',
      compiled_truth: 'export const x = 1;',
      timeline: '',
      frontmatter: { language: 'typescript' }, // no file
    });

    // One markdown page that MUST be ignored.
    await engine.putPage('guides/not-code', {
      type: 'guide',
      title: 'Not code',
      compiled_truth: 'This is a markdown page, not code.',
      timeline: '',
    });
  });

  afterAll(async () => {
    await engine.disconnect();
    resetGateway();
    if (prevNudgeEnv === undefined) delete process.env.GBRAIN_NO_CODE_MODEL_NUDGE;
    else process.env.GBRAIN_NO_CODE_MODEL_NUDGE = prevNudgeEnv;
  }, 30_000);

  test('counts code pages, ignores markdown', async () => {
    const result = await runReindexCode(engine, { dryRun: true, noEmbed: true });
    expect(result.status).toBe('dry_run');
    expect(result.codePages).toBe(3); // foo, bar, bad — not the guide
  });

  test('dry-run reports cost + token count without importing', async () => {
    const result = await runReindexCode(engine, { dryRun: true, noEmbed: true });
    expect(result.status).toBe('dry_run');
    expect(result.reindexed).toBe(0);
    expect(result.totalTokens).toBeGreaterThan(0);
    expect(result.costUsd).toBeGreaterThanOrEqual(0);
    expect(result.model).toBe('text-embedding-3-large');
  });

  test('reindex walks every code page, failures counted per-slug', async () => {
    const result = await runReindexCode(engine, { noEmbed: true });
    expect(result.status).toBe('ok');
    expect(result.codePages).toBe(3);
    // src-bad-ts has no frontmatter.file → fails cleanly.
    expect(result.failed).toBeGreaterThanOrEqual(1);
    expect(result.failures).toBeDefined();
    expect(result.failures!.some(f => f.slug === 'src-bad-ts')).toBe(true);
  });

  test('empty brain returns ok with zero counts', async () => {
    const empty = new PGLiteEngine();
    await empty.connect({});
    await empty.initSchema();
    const result = await runReindexCode(empty, { noEmbed: true });
    expect(result.status).toBe('ok');
    expect(result.codePages).toBe(0);
    expect(result.reindexed).toBe(0);
    expect(result.totalTokens).toBe(0);
    await empty.disconnect();
  }, 30_000);

  test('batch size honored — walks all pages even when total > batchSize', async () => {
    const result = await runReindexCode(engine, {
      dryRun: true,
      noEmbed: true,
      batchSize: 1,
    });
    expect(result.codePages).toBe(3);
  });
});
