/**
 * Routing-key selection tests.
 *
 * The single decision shared by every LLM call site: gateway (proxy) mode
 * routes by Role; direct mode sends the resolved model name. See
 * docs/superpowers/plans/2026-06-09-role-based-gateway-routing.md.
 */
import { describe, it, expect, vi } from 'vitest';
import { llmRoutingKey, shouldRequeueForModelMismatch } from './routing-key.js';

describe('llmRoutingKey', () => {
  it('gateway mode sends the role as the key', () => {
    const key = llmRoutingKey({
      isGateway: true,
      role: 'quality',
      resolveDirectModel: () => 'should-not-be-used.gguf',
    });
    expect(key).toBe('quality');
  });

  it('gateway mode never evaluates the direct-model resolver (no /v1/models, no auto-select)', () => {
    const resolveDirectModel = vi.fn(() => 'm');
    llmRoutingKey({ isGateway: true, role: 'fast', resolveDirectModel });
    expect(resolveDirectModel).not.toHaveBeenCalled();
  });

  it('gateway mode falls back to "auto" when role is undefined', () => {
    expect(
      llmRoutingKey({ isGateway: true, role: undefined, resolveDirectModel: () => 'm' }),
    ).toBe('auto');
  });

  it('gateway mode honours a custom role fallback (e.g. reflection)', () => {
    expect(
      llmRoutingKey({ isGateway: true, role: undefined, resolveDirectModel: () => 'm', roleFallback: 'reflection' }),
    ).toBe('reflection');
  });

  it('direct mode returns the resolved model and ignores the role', () => {
    const key = llmRoutingKey({
      isGateway: false,
      role: 'quality',
      resolveDirectModel: () => 'qwen3:8b',
    });
    expect(key).toBe('qwen3:8b');
  });

  it('direct mode preserves an undefined model (llama-server model omission)', () => {
    expect(
      llmRoutingKey({ isGateway: false, role: 'auto', resolveDirectModel: () => undefined }),
    ).toBeUndefined();
  });
});

describe('shouldRequeueForModelMismatch', () => {
  it('NEVER requeues in gateway mode — availableModels holds roles, gateway owns model selection', () => {
    // The regression: in gateway mode availableModels = advertised roles
    // (auto/fast/quality), so a piece pinning a concrete model would never
    // match and the job would requeue forever.
    expect(
      shouldRequeueForModelMismatch({
        isGateway: true,
        pieceModel: 'qwen3:32b',
        availableModels: new Set(['auto', 'fast', 'quality']),
        workerModel: '',
      }),
    ).toBe(false);
  });

  it('direct mode requeues when the piece pins an unavailable model', () => {
    expect(
      shouldRequeueForModelMismatch({
        isGateway: false,
        pieceModel: 'qwen3:32b',
        availableModels: new Set(['qwen3:8b']),
        workerModel: 'qwen3:8b',
      }),
    ).toBe(true);
  });

  it('direct mode does NOT requeue when the piece model is available', () => {
    expect(
      shouldRequeueForModelMismatch({
        isGateway: false,
        pieceModel: 'qwen3:8b',
        availableModels: new Set(['qwen3:8b', 'qwen3:32b']),
        workerModel: 'qwen3:8b',
      }),
    ).toBe(false);
  });

  it('direct mode does NOT requeue when the piece model equals the worker model (probe empty)', () => {
    expect(
      shouldRequeueForModelMismatch({
        isGateway: false,
        pieceModel: 'qwen3:8b',
        availableModels: new Set(['other']),
        workerModel: 'qwen3:8b',
      }),
    ).toBe(false);
  });

  it('direct mode does NOT requeue when the piece pins no model', () => {
    expect(
      shouldRequeueForModelMismatch({
        isGateway: false,
        pieceModel: undefined,
        availableModels: new Set(['qwen3:8b']),
        workerModel: 'qwen3:8b',
      }),
    ).toBe(false);
  });

  it('direct mode does NOT requeue before the model list is probed (size 0)', () => {
    expect(
      shouldRequeueForModelMismatch({
        isGateway: false,
        pieceModel: 'qwen3:32b',
        availableModels: new Set(),
        workerModel: '',
      }),
    ).toBe(false);
  });
});
