import type { AIProviderRegistry, ChatMessage } from '@genesislcap/foundation-ai';
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
// Side-effect import — MUST come before `./chat-driver` so the driver subclasses
// jsdom's EventTarget rather than Node's native one (CustomEvent dispatch then
// works). Mirrors `chat-driver.test.ts`.
import './align-event-globals';
import { ChatDriver } from './chat-driver';

// GENC-1351 §5.7 — driver-level compaction: the atomic, destructive replace.
// A minimal fake registry stands in for a provider, so no network is involved.

const asMsg = (o: Record<string, unknown>): ChatMessage => o as unknown as ChatMessage;

/** A registry whose default provider summarizes via `prompt` (undefined = unsupported). */
function registryWith(prompt: ((m: string) => Promise<string>) | undefined): AIProviderRegistry {
  const provider = { prompt, getStatus: async () => ({ model: 'test-model' }) };
  return { default: () => provider } as unknown as AIProviderRegistry;
}

/** 10 messages alternating user/assistant (user at even indices); last carries usage. */
function tenTurnHistory(): ChatMessage[] {
  return Array.from({ length: 10 }, (_, i) =>
    asMsg({
      role: i % 2 === 0 ? 'user' : 'assistant',
      content: `m${i}`,
      timestamp: `2026-01-01T00:00:${String(i).padStart(2, '0')}.000Z`,
      ...(i === 9 ? { inputTokens: 1234 } : {}),
    }),
  );
}

const Suite = createLogicSuite('chat-driver-compact');

Suite('compact() destructively replaces older turns with one head summary + metadata', async () => {
  const driver = new ChatDriver(registryWith(async () => 'THE SUMMARY'));
  driver.loadHistory(tenTurnHistory());
  const before = driver.getHistory().length;

  const summary = await driver.compact();
  assert.ok(summary, 'returns the created summary message');
  assert.is(summary!.role, 'compacted-summary');
  assert.is(summary!.content, 'THE SUMMARY');
  assert.is(summary!.compaction?.tokensBefore, 1234, 'captures pre-compaction input tokens');
  assert.is(summary!.compaction?.model, 'test-model', 'records the summarizing model');

  const after = driver.getHistory();
  assert.ok(after.length < before, 'history shrank (destructive)');
  assert.is(after[0].role, 'compacted-summary', 'summary sits at the head');
  assert.is(
    after[0].timestamp,
    '2026-01-01T00:00:00.000Z',
    'summary inherits the covered region start → sorts to the head in time-ordered views',
  );
  assert.is(after[0].compaction?.compactedCount, before - (after.length - 1));
  assert.ok(
    after.slice(1).every((m) => m.role !== 'compacted-summary'),
    'exactly one summary — no marker chain',
  );
});

Suite('compact() is atomic on failure — no splice, surfaces a "sorry" message', async () => {
  const driver = new ChatDriver(
    registryWith(async () => {
      throw new Error('network');
    }),
  );
  const original = tenTurnHistory();
  driver.loadHistory(original);

  const result = await driver.compact();
  assert.is(result, null, 'returns null on failure');
  const after = driver.getHistory();
  assert.ok(
    after.every((m) => m.role !== 'compacted-summary'),
    'nothing compacted',
  );
  // The covered turns are untouched (atomic); only a brief apology is appended so
  // the user knows it failed (GENC-1351 follow-up — surfaced like a turn error).
  assert.is(after.length, original.length + 1, 'only an apology appended');
  assert.is(after[after.length - 1].role, 'assistant');
  assert.ok(/sorry/i.test(after[after.length - 1].content), 'apology is an agent message');
});

Suite('compact() rolls a prior summary into a single fresh one', async () => {
  const driver = new ChatDriver(registryWith(async () => 'COMBINED'));
  driver.loadHistory([
    asMsg({ role: 'compacted-summary', content: 'old', compaction: { compactedCount: 5 } }),
    ...tenTurnHistory(),
  ]);
  const summary = await driver.compact();
  assert.ok(summary);
  const after = driver.getHistory();
  assert.is(
    after.filter((m) => m.role === 'compacted-summary').length,
    1,
    'still exactly one summary after re-compaction',
  );
  assert.is(after[0].content, 'COMBINED');
});

Suite('compact() no-ops when there is nothing worth compacting', async () => {
  const driver = new ChatDriver(registryWith(async () => 'unused'));
  driver.loadHistory([
    asMsg({ role: 'user', content: 'hi' }),
    asMsg({ role: 'assistant', content: 'yo' }),
  ]);
  assert.is(await driver.compact(), null);
  assert.is(driver.getHistory().length, 2, 'short history untouched');
});

Suite('compact() no-ops when the default provider cannot summarize', async () => {
  const driver = new ChatDriver(registryWith(undefined));
  driver.loadHistory(tenTurnHistory());
  assert.is(await driver.compact(), null);
  assert.is(driver.getHistory().length, 10, 'history untouched without prompt support');
});

Suite("canCompact reflects compact()-would-act, keyed on the driver's own history", () => {
  const rich = new ChatDriver(registryWith(async () => 'x'));
  rich.loadHistory(tenTurnHistory());
  assert.is(rich.canCompact(), true, 'enough history → gate open');

  const thin = new ChatDriver(registryWith(async () => 'x'));
  thin.loadHistory([
    asMsg({ role: 'user', content: 'hi' }),
    asMsg({ role: 'assistant', content: 'yo' }),
  ]);
  assert.is(thin.canCompact(), false, 'too short → gate closed (same source compact() uses)');
});

Suite.run();
