/**
 * v0.31.12 — THE regression test for the bug class that motivated this release.
 *
 * gbrain v0.31.6 shipped `claude-sonnet-4-6-20250929` as the chat default.
 * That ID 404s on the Anthropic API, which made `isAvailable("chat")` return
 * false in every code path that loaded the recipe's model list. The headline
 * v0.31.6 feature (real-time facts extraction during sync) was a no-op on the
 * happy path: `extractFactsFromTurn` silently returned `[]` with no
 * user-visible signal.
 *
 * Codex F7+F8+F15 in plan review (the boil-the-lake decision): the existing
 * gateway E2Es did not cover the `isAvailable('chat') === false → silent []`
 * path. This test exists so the bug class is impossible to ship again.
 *
 * No API key required — uses the gateway's chat-transport test seam
 * (`__setChatTransportForTests`) to simulate a "model 404" without actually
 * hitting Anthropic. The full real-provider E2E lives elsewhere (it requires
 * `ANTHROPIC_API_KEY`); this test is the structural regression guard that
 * runs in every parallel-test shard.
 */
import { afterAll, describe, test, expect, beforeEach } from 'bun:test';
import {
  configureGateway,
  isAvailable,
  resetGateway,
  __setChatTransportForTests,
  getChatModel,
} from '../src/core/ai/gateway.ts';
import { extractFactsFromTurn } from '../src/core/facts/extract.ts';

beforeEach(() => {
  resetGateway();
  __setChatTransportForTests(null);
});

// Shard hygiene: this file's tests each configureGateway WITHOUT
// embedding_dimensions, and the file used to END in that state. The
// legacy-embedding-preload only re-pins 1536 when the gateway is RESET
// (it checks by calling getEmbeddingDimensions, which doesn't throw on a
// configured-but-dimensionless gateway), and the NEXT file's beforeAll
// (often engine.initSchema, which sizes vector columns from ambient
// gateway state) runs before any beforeEach — so this file poisoned every
// later fresh-schema file in its shard down to 1280-d columns under
// 1536-d fixtures (bit engine-find-trajectory in CI shard 5). Restore the
// legacy pin on exit.
afterAll(() => {
  __setChatTransportForTests(null);
  configureGateway({
    embedding_model: 'openai:text-embedding-3-large',
    embedding_dimensions: 1536,
    env: { ...process.env },
  });
});

describe('facts extract — silent-no-op regression (v0.31.6 bug class)', () => {
  test('with valid model and ANTHROPIC_API_KEY present, isAvailable("chat") is true', () => {
    configureGateway({
      chat_model: 'anthropic:claude-sonnet-4-6',
      env: { ANTHROPIC_API_KEY: 'sk-ant-test' },
    });
    expect(isAvailable('chat')).toBe(true);
    expect(getChatModel()).toBe('anthropic:claude-sonnet-4-6');
  });

  test('with the v0.31.6-shipped broken model id, reverse alias rescues isAvailable("chat")', () => {
    // The reverse alias `claude-sonnet-4-6-20250929` → `claude-sonnet-4-6`
    // is the v0.31.12 back-compat path for users with stale config strings
    // (models.dream.synthesize, facts.extraction_model, etc.).
    // After alias resolution, the model is in the recipe's chat list and
    // ANTHROPIC_API_KEY is present, so isAvailable returns true. This is the
    // structural fix for the bug class — the broken ID never reaches the
    // provider as a 404 because the alias rewrites it first.
    configureGateway({
      chat_model: 'anthropic:claude-sonnet-4-6-20250929',
      env: { ANTHROPIC_API_KEY: 'sk-ant-test' },
    });
    expect(isAvailable('chat')).toBe(true);
  });

  test('with a chat model the recipe does not declare AND no config override, isAvailable("chat") is false', () => {
    // This is the failure mode that produced silent [] in v0.31.6: the
    // hardcoded default referenced a model not in the recipe's `models:` list,
    // so isAvailable returned false. v0.31.12 prevents this by:
    //   1. The recipe-models merge — configured models extend the recipe's
    //      allowlist per gateway instance.
    //   2. The doctor probe — operators can run `gbrain models doctor` to
    //      catch a bad model at config time, not call time.
    // To reproduce the OLD bug, we bypass the merge by clearing the gateway
    // and providing a fictional model NOT in the anthropic recipe.
    resetGateway();
    // NOTE: configureGateway registers `cfg.chat_model` into the extended-
    // models set so any caller-supplied id passes validation. We assert the
    // POSITIVE path: a valid configured model produces isAvailable=true.
    configureGateway({
      chat_model: 'anthropic:claude-nonexistent-model-xyz',
      env: { ANTHROPIC_API_KEY: 'sk-ant-test' },
    });
    // The user opted into this model via config; the gateway permits it.
    // The 404 surfaces at provider call time (via `gbrain models doctor`).
    expect(isAvailable('chat')).toBe(true);
  });

  test('extractFactsFromTurn returns [] gracefully when chat is unavailable (no API key)', async () => {
    // Make chat unavailable by omitting ANTHROPIC_API_KEY.
    // This is the legitimate "graceful degradation" path — not a silent bug,
    // because the user knows they didn't configure a key.
    configureGateway({
      chat_model: 'anthropic:claude-sonnet-4-6',
      env: {},  // no API key
    });
    expect(isAvailable('chat')).toBe(false);
    const facts = await extractFactsFromTurn({
      turnText: 'Garry founded Initialized in 2010 with Alexis.',
      source: 'test:no-op-regression',
    });
    expect(facts).toEqual([]);
  });

  test('extractFactsFromTurn USES the chat transport when available — does NOT silently return []', async () => {
    // The smoking-gun test: when chat IS available, extract MUST actually call
    // the chat transport. If it silently returns [] without calling chat, the
    // bug class is alive again.
    configureGateway({
      chat_model: 'anthropic:claude-sonnet-4-6',
      env: { ANTHROPIC_API_KEY: 'sk-ant-test' },
    });
    let chatCalled = false;
    __setChatTransportForTests(async () => {
      chatCalled = true;
      // Return a malformed result so extract returns [] for a DIFFERENT reason
      // (parse failure, not chat-unavailability). The assertion below is on
      // `chatCalled`, not on the facts array — we just need to prove the call
      // path is exercised.
      return {
        text: '[]',
        blocks: [{ type: 'text', text: '[]' }],
        stopReason: 'end' as const,
        usage: { input_tokens: 1, output_tokens: 1, cache_read_tokens: 0, cache_creation_tokens: 0 },
        model: 'anthropic:claude-sonnet-4-6',
        providerId: 'anthropic',
      };
    });
    await extractFactsFromTurn({
      turnText: 'Garry founded Initialized in 2010 with Alexis.',
      source: 'test:smoking-gun',
    });
    expect(chatCalled).toBe(true);  // ← THE bug-class assertion
  });
});
