import { describe, it, expect } from 'vitest';
import { shouldUseWarmBackend, isTurnOkForStopReason } from '../warm-acp-routing.js';

/**
 * Phase 2 routing predicate: which ACP chat-agent tasks go to the warm pool backend
 * vs the existing one-shot ACP path.
 *
 * The warm backend is keyed (chat_id, agent) — the persistent context unit (same
 * as opencode-server). A task only routes warm when it carries BOTH a session_id
 * and a chat_id, i.e. it originates from a real chat tab (the coder message route
 * stamps both). Session-less creators (arena, MCP-created, generic /api/tasks,
 * new_task) lack chat_id/session_id and keep the one-shot worktree-per-task path,
 * which never spawns a warm process.
 */
describe('shouldUseWarmBackend (Phase 2 routing)', () => {
  it('routes a chat-tab task (session_id + chat_id) to the warm backend', () => {
    expect(shouldUseWarmBackend({ agent: 'qwen', session_id: 's1', chat_id: 'c1' })).toBe(true);
    expect(shouldUseWarmBackend({ agent: 'goose', session_id: 's1', chat_id: 'c1' })).toBe(true);
    expect(shouldUseWarmBackend({ agent: 'reasonix', session_id: 's1', chat_id: 'c1' })).toBe(true);
  });

  it('keeps a session-less arena/MCP task on the one-shot path', () => {
    expect(shouldUseWarmBackend({ agent: 'qwen', session_id: null, chat_id: null })).toBe(false);
  });

  it('keeps a task with a session but no chat on the one-shot path', () => {
    // chat_id is the warm-key half; without it ensureSession would get a degenerate
    // (null, agent) key, so fall back to one-shot rather than synthesize a chat.
    expect(shouldUseWarmBackend({ agent: 'goose', session_id: 's1', chat_id: null })).toBe(false);
  });

  it('keeps a task with a chat but no session on the one-shot path', () => {
    expect(shouldUseWarmBackend({ agent: 'qwen', session_id: null, chat_id: 'c1' })).toBe(false);
  });

  it('only applies to warm-capable ACP agents; others never warm here', () => {
    // opencode has its own dedicated warm path; native/claude/etc. are not ACP-warm.
    expect(shouldUseWarmBackend({ agent: 'opencode', session_id: 's1', chat_id: 'c1' })).toBe(false);
    expect(shouldUseWarmBackend({ agent: 'claude', session_id: 's1', chat_id: 'c1' })).toBe(false);
    expect(shouldUseWarmBackend({ agent: null, session_id: 's1', chat_id: 'c1' })).toBe(false);
  });
});

describe('isTurnOkForStopReason (ACP stop-reason → ok/fail)', () => {
  it('treats normal completions as ok', () => {
    expect(isTurnOkForStopReason('end_turn')).toBe(true);
    expect(isTurnOkForStopReason('max_tokens')).toBe(true);
    expect(isTurnOkForStopReason('max_turn_requests')).toBe(true);
  });

  it('treats refusal and cancelled as failures', () => {
    expect(isTurnOkForStopReason('refusal')).toBe(false);
    expect(isTurnOkForStopReason('cancelled')).toBe(false);
  });

  it('defaults an absent stop reason to a successful end_turn', () => {
    expect(isTurnOkForStopReason(undefined)).toBe(true);
    expect(isTurnOkForStopReason(null)).toBe(true);
  });
});
