import type { ChatMessage } from '@genesislcap/foundation-ai';
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import {
  buildCompactionSummaryPrompt,
  compactionDividerLabel,
  COMPACT_KEEP_RECENT_MESSAGES,
  COMPACT_MIN_MESSAGES_TO_COMPACT,
  findCompactionCut,
  normalizeForProvider,
} from './history-transform';

const asMsg = (o: Record<string, unknown>): ChatMessage => o as unknown as ChatMessage;
const user = (content: string) => asMsg({ role: 'user', content });
const assistant = (content: string) => asMsg({ role: 'assistant', content });

// GENC-1351 §5.7 — the pure compaction helpers (no DOM/provider deps).
const Suite = createLogicSuite('history-transform-compaction');

// ── findCompactionCut ─────────────────────────────────────────────────────────

Suite('findCompactionCut returns null for a transcript too short to be worth compacting', () => {
  assert.is(findCompactionCut([]), null);
  // One below the floor (keep-recent + min-to-compact) → nothing to do.
  const justUnder = Array.from(
    { length: COMPACT_KEEP_RECENT_MESSAGES + COMPACT_MIN_MESSAGES_TO_COMPACT - 1 },
    (_, i) => (i % 2 === 0 ? user(`u${i}`) : assistant(`a${i}`)),
  );
  assert.is(findCompactionCut(justUnder), null);
});

Suite('findCompactionCut cuts at a user-message boundary, keeping the recent tail', () => {
  // 10 messages alternating user/assistant; user at even indices.
  const h = Array.from({ length: 10 }, (_, i) =>
    i % 2 === 0 ? user(`u${i}`) : assistant(`a${i}`),
  );
  const cut = findCompactionCut(h);
  // target = 10 - 4 = 6; history[6] is a user message → cut there.
  assert.is(cut, 6);
  assert.is(h[cut!].role, 'user', 'tail must start on a user boundary');
});

Suite('findCompactionCut walks back to the nearest earlier user boundary', () => {
  // user only at indices 0,1,3,5,7,9 — index 6 is assistant, so it walks back to 5.
  const h = [
    user('u0'),
    user('u1'),
    assistant('a2'),
    user('u3'),
    assistant('a4'),
    user('u5'),
    assistant('a6'),
    user('u7'),
    assistant('a8'),
    user('u9'),
  ];
  // target = 6 (assistant) → walk back to 5 (user).
  assert.is(findCompactionCut(h), 5);
});

Suite('findCompactionCut returns null when the only user turn is the first message', () => {
  // No user boundary after index 0 → nothing can be summarized behind a boundary.
  const h = [user('u0'), ...Array.from({ length: 9 }, (_, i) => assistant(`a${i}`))];
  assert.is(findCompactionCut(h), null);
});

Suite(
  'findCompactionCut includes an existing head summary in the region to compact (rolling)',
  () => {
    const h = [
      asMsg({ role: 'compacted-summary', content: 'prior', compaction: { compactedCount: 3 } }),
      user('u1'),
      assistant('a2'),
      user('u3'),
      assistant('a4'),
      user('u5'),
      assistant('a6'),
      user('u7'),
      assistant('a8'),
      user('u9'),
    ];
    const cut = findCompactionCut(h);
    assert.ok(
      cut != null && cut >= 1,
      'cut should leave the head summary inside the compacted region',
    );
    assert.is(h.slice(0, cut!)[0].role, 'compacted-summary', 'prior summary is folded back in');
  },
);

// ── buildCompactionSummaryPrompt ──────────────────────────────────────────────

Suite('buildCompactionSummaryPrompt wraps the transcript and labels roles', () => {
  const { systemPrompt, userMessage } = buildCompactionSummaryPrompt([
    user('book a trade'),
    assistant('sure, which counterparty?'),
  ]);
  assert.ok(/summar/i.test(systemPrompt), 'system prompt frames a summarization task');
  assert.ok(userMessage.includes('<conversation_history>'));
  assert.ok(userMessage.includes('</conversation_history>'));
  assert.ok(userMessage.includes('User: book a trade'));
  assert.ok(userMessage.includes('Assistant: sure, which counterparty?'));
});

Suite('buildCompactionSummaryPrompt renders tool activity and a prior summary', () => {
  const { userMessage } = buildCompactionSummaryPrompt([
    asMsg({
      role: 'compacted-summary',
      content: 'earlier stuff',
      compaction: { compactedCount: 2 },
    }),
    asMsg({
      role: 'assistant',
      content: '',
      toolCalls: [{ id: 't1', name: 'searchTrades', args: {} }],
    }),
    asMsg({ role: 'tool', content: '', toolResult: { toolCallId: 't1', content: '3 hits' } }),
  ]);
  assert.ok(userMessage.includes('[Earlier summary] earlier stuff'));
  assert.ok(userMessage.includes('searchTrades'), 'tool call name is preserved for continuity');
  assert.ok(userMessage.includes('Tool result: 3 hits'));
});

Suite(
  'buildCompactionSummaryPrompt folds in reasoning + narration, labelled Thinking / Working',
  () => {
    const { userMessage } = buildCompactionSummaryPrompt([
      asMsg({
        role: 'assistant',
        content: 'weigh the counterparty options',
        category: 'reasoning',
      }),
      asMsg({ role: 'assistant', content: 'searching the trade blotter', category: 'narration' }),
    ]);
    assert.ok(userMessage.includes('Thinking: weigh the counterparty options'));
    assert.ok(userMessage.includes('Working: searching the trade blotter'));
  },
);

// ── normalizeForProvider (the no-leak guarantee) ──────────────────────────────

Suite(
  'normalizeForProvider translates compacted-summary into a <conversation_summary> user msg',
  () => {
    const out = normalizeForProvider([
      asMsg({ role: 'compacted-summary', content: 'the gist', compaction: { compactedCount: 9 } }),
      user('and now?'),
    ]);
    assert.is(out[0].role, 'user', 'the raw display-only role is gone');
    assert.ok(out[0].content.includes('<conversation_summary>'));
    assert.ok(out[0].content.includes('the gist'));
    assert.is(out[0].compaction, undefined, 'compaction metadata is dropped from the sent message');
  },
);

Suite('normalizeForProvider never emits a compacted-summary role (no leak to a transport)', () => {
  const out = normalizeForProvider([
    asMsg({ role: 'compacted-summary', content: 's1', compaction: { compactedCount: 1 } }),
    user('u'),
    assistant('a'),
    asMsg({ role: 'system-event', content: 'Switched agent' }),
  ]);
  assert.ok(
    out.every((m) => m.role !== 'compacted-summary'),
    'no compacted-summary role survives normalization',
  );
});

Suite('normalizeForProvider passes non-summary messages through untouched (same ref)', () => {
  const u = user('hello');
  const a = assistant('hi');
  const out = normalizeForProvider([u, a]);
  assert.is(out[0], u);
  assert.is(out[1], a);
});

// ── compactionDividerLabel ────────────────────────────────────────────────────

Suite('compactionDividerLabel pluralizes and falls back to 0', () => {
  assert.is(
    compactionDividerLabel(
      asMsg({ role: 'compacted-summary', content: '', compaction: { compactedCount: 1 } }),
    ),
    'Compacted 1 earlier message',
  );
  assert.is(
    compactionDividerLabel(
      asMsg({ role: 'compacted-summary', content: '', compaction: { compactedCount: 12 } }),
    ),
    'Compacted 12 earlier messages',
  );
  assert.is(
    compactionDividerLabel(asMsg({ role: 'compacted-summary', content: '' })),
    'Compacted 0 earlier messages',
  );
});

Suite.run();
