/**
 * SET 51 — checkpointed persistence helpers + SW merge / ACK contract.
 */
import { describe, expect, it } from 'vitest';
import {
  buildMinimalFallbackRecord,
  formatCheckpointHeader,
  formatPersistStep,
  isCheckpointedUnfinalized,
  mergePersistRecord,
  parsePersistAck,
  RUN_RECORD_MISSING,
} from '../src/tier1/persistSafe';

describe('SET 51a checkpoint viewer header', () => {
  it('formats the canonical truncated-checkpoint header', () => {
    expect(formatCheckpointHeader('2026-07-26T19:35:02.000Z'))
      .toBe('record checkpointed through 2026-07-26T19:35:02.000Z — finalization failed');
  });

  it('detects unfinalized checkpoint / interrupted records', () => {
    expect(isCheckpointedUnfinalized({ status: 'running', checkpointAt: 't1' })).toBe(true);
    expect(isCheckpointedUnfinalized({ status: 'interrupted', events: [{}] })).toBe(true);
    expect(isCheckpointedUnfinalized({ status: 'complete', finalized: true })).toBe(false);
    expect(isCheckpointedUnfinalized({ status: 'failed', finalized: true })).toBe(false);
    expect(isCheckpointedUnfinalized(null)).toBe(false);
  });
});

describe('SET 51b persist ACK', () => {
  it('accepts ok+ack+id and rejects missing ACK', () => {
    expect(parsePersistAck({ ok: true, ack: true, id: 'abc', eventCount: 25 }))
      .toEqual({
        ok: true, ack: true, id: 'abc', eventCount: 25,
        status: null, checkpointAt: null, bytes: undefined,
      });
    expect(parsePersistAck({ ok: true })).toEqual({ ok: false, error: 'ACK missing ok/ack/id' });
    expect(parsePersistAck(null)).toEqual({ ok: false, error: 'no ACK (null response)' });
    expect(parsePersistAck({ error: 'quota' })).toEqual({ ok: false, error: 'quota' });
  });

  it('formats step-level debug lines', () => {
    expect(formatPersistStep('sanitize')).toBe('persist:sanitize');
    expect(formatPersistStep('sendMessage', '120ms')).toBe('persist:sendMessage — 120ms');
  });
});

describe('SET 51c minimal fallback + merge', () => {
  it('builds a header-only fallback small enough for structured clone', () => {
    const fb = buildMinimalFallbackRecord({
      id: 'deadbeef',
      promptHash: 'a1b2c3d4',
      status: 'interrupted',
      error: 'channel dead',
      model: 'local',
    });
    expect(fb.events).toEqual([]);
    expect(fb.result).toBeNull();
    expect(fb.fallback).toBe(true);
    expect(fb.finalized).toBe(false);
    expect(() => structuredClone(fb)).not.toThrow();
    expect(JSON.stringify(fb).length).toBeLessThan(800);
  });

  it('merge keeps prior events when fallback arrives with empty events', () => {
    const prev = {
      id: 'r1',
      status: 'running',
      events: [{ message: 'a' }, { message: 'b' }],
      error: null as string | null,
    };
    const next = buildMinimalFallbackRecord({ id: 'r1', status: 'interrupted', error: 'boom' });
    const merged = mergePersistRecord(prev, next);
    expect(merged.events).toHaveLength(2);
    expect(merged.status).toBe('interrupted');
    expect(merged.error).toBe('boom');
  });

  it('merge prefers the longer event list from a later checkpoint', () => {
    const prev = { id: 'r1', events: [{ m: 1 }] };
    const next = { id: 'r1', status: 'running', events: [{ m: 1 }, { m: 2 }, { m: 3 }] };
    expect(mergePersistRecord(prev, next).events).toHaveLength(3);
  });

  it('does not conflate checkpoint header with RUN_RECORD_MISSING', () => {
    expect(formatCheckpointHeader('t')).not.toBe(RUN_RECORD_MISSING);
  });
});

describe('SET 51 acceptance simulation — kill mid-run leaves checkpoint', () => {
  it('register → checkpoint → kill leaves events through last ACK', () => {
    // Pure store simulation of SW upsert + offscreen checkpoint cadence.
    const store = new Map<string, { id: string; status: string; events: unknown[]; finalized?: boolean }>();
    const upsert = (rec: { id: string; status: string; events: unknown[]; finalized?: boolean }) => {
      const prev = store.get(rec.id);
      store.set(rec.id, mergePersistRecord(prev, rec));
      return { ok: true as const, ack: true as const, id: rec.id, eventCount: rec.events.length };
    };

    const id = 'killmid';
    expect(parsePersistAck(upsert({ id, status: 'running', events: [] })).ok).toBe(true);
    expect(parsePersistAck(upsert({
      id, status: 'running',
      events: Array.from({ length: 25 }, (_, i) => ({ i })),
    })).ok).toBe(true);
    // Kill: no finalize. Record must still exist with checkpoint events.
    const left = store.get(id)!;
    expect(left.events).toHaveLength(25);
    expect(left.status).toBe('running');
    expect(isCheckpointedUnfinalized(left)).toBe(true);

    // Complete path: finalize lands full record.
    upsert({
      id: 'done1',
      status: 'complete',
      finalized: true,
      events: [{ m: 'ok' }],
    });
    expect(store.get('done1')!.finalized).toBe(true);
    expect(isCheckpointedUnfinalized(store.get('done1'))).toBe(false);
  });
});
