/**
 * Contract tests for the per-turn task-list injection in agentScheduler.
 *
 * Why this exists: the Talisman production failure was caused by
 * post-compaction context loss — the agent forgot it already had a
 * 9-task plan and called `taskmanager sync` with 4 unrelated tasks,
 * silently destroying the in-flight work. The destructive-sync
 * guardrail in taskManagerTool catches that AT THE SYNC POINT, but
 * the cleaner fix is to never let the agent forget the task list in
 * the first place — inject it into the system prompt every turn.
 *
 * This test pins the contract by source-grep (matches the existing
 * pattern in agentScheduler.taskLifecycleInstruction.test.js): the
 * scheduler's source must contain the injection block AND its key
 * properties. Reading the file is cheaper than instantiating the
 * full scheduler graph (websocket, compaction service, AI service,
 * etc.) just to assert on prompt content.
 */
import { describe, test, expect } from '@jest/globals';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __dirname = dirname(fileURLToPath(import.meta.url));
const SRC = readFileSync(join(__dirname, '../agentScheduler.js'), 'utf-8');

describe('agentScheduler — per-turn task-list injection', () => {
  test('injection block exists in agentScheduler.js source', () => {
    expect(SRC).toContain('CURRENT TASK LIST');
  });

  test('emits the heading the agent will key off of', () => {
    // The block heading must be specific and stable — the agent's
    // system prompt is the only thing it sees, so the heading needs
    // to be unambiguous.
    expect(SRC).toMatch(/##\s*CURRENT TASK LIST/);
  });

  test('reads from durable agent state (agent.taskList.tasks), not from conversation history', () => {
    // The whole point of this injection is to recover from a
    // conversation-history loss (compaction). It MUST read the live,
    // durable taskList — not derive from messages.
    expect(SRC).toMatch(/agent\.taskList\??\.tasks/);
  });

  test('the block runs AFTER the plan/* injection (cache-friendly ordering)', () => {
    // Stable cacheable system-prompt prefix is critical for the
    // model-side prompt cache. Both injections are dynamic per turn,
    // but ordering must be consistent so the prefix up to plan/*
    // stays stable across turns when only the task list moves.
    const planIdx = SRC.indexOf('AGENT WORKING PLAN');
    const taskIdx = SRC.indexOf('CURRENT TASK LIST');
    expect(planIdx).toBeGreaterThan(-1);
    expect(taskIdx).toBeGreaterThan(-1);
    expect(taskIdx).toBeGreaterThan(planIdx);
  });

  test('mentions compaction-resistance explicitly', () => {
    // The agent reads the injected block. It must know that the
    // injection is a deliberate signal — not just historical residue
    // — so it gives appropriate weight to the listed tasks.
    expect(SRC).toMatch(/(survives compaction|compaction summarized|compaction)/);
  });

  test('warns the agent about destructive sync inside the injected block', () => {
    // The injection is the natural place to point the agent at the
    // guardrail. Strong message: do not blindly sync past the
    // existing tasks.
    expect(SRC).toMatch(/(sync|drop).*existing|before issuing.*sync/i);
  });

  test('groups tasks by status (in_progress / pending / completed / cancelled)', () => {
    // A flat list of 30 tasks is hard to scan. Status grouping puts
    // in_progress first — what the agent needs to act on.
    expect(SRC).toContain('in_progress');
    expect(SRC).toContain('pending');
    expect(SRC).toContain('completed');
    expect(SRC).toContain('cancelled');
  });

  test('degrades gracefully — never throws on missing taskList', () => {
    // If taskList is undefined or .tasks is missing/empty, the
    // injection should silently skip — never break the turn.
    expect(SRC).toMatch(/agent\.taskList\?\.tasks/);
    // And the whole block is wrapped in try/catch with a logger warn.
    const block = SRC.slice(
      SRC.indexOf('Auto-inject CURRENT TASK LIST'),
      SRC.indexOf('Check if streaming is enabled'),
    );
    expect(block).toMatch(/try\s*{/);
    expect(block).toMatch(/catch\s*\(/);
    expect(block).toMatch(/continuing without/);
  });
});
