// Tests for durable sequencing — monotonic seqId assignment and deduplication.
// Uses bun:test.

import { describe, it, expect } from 'bun:test';

// ── Replicated seqId counter from agent-store.svelte.ts ─────────────────────

function createSeqCounter() {
  const counters = new Map<string, number>();
  return {
    next(sessionId: string): number {
      const current = counters.get(sessionId) ?? 0;
      const next = current + 1;
      counters.set(sessionId, next);
      return next;
    },
    get(sessionId: string): number {
      return counters.get(sessionId) ?? 0;
    },
    set(sessionId: string, value: number): void {
      counters.set(sessionId, value);
    },
  };
}

// ── Deduplication logic ─────────────────────────────────────────────────────

interface RawMsg {
  msgId: string;
  seqId: number;
  content: string;
}

function deduplicateMessages(messages: RawMsg[]): { deduplicated: RawMsg[]; maxSeqId: number } {
  const seqIdSet = new Set<number>();
  const deduplicated: RawMsg[] = [];
  let maxSeqId = 0;

  for (const m of messages) {
    const sid = m.seqId ?? 0;
    if (sid > 0 && seqIdSet.has(sid)) continue;
    if (sid > 0) seqIdSet.add(sid);
    if (sid > maxSeqId) maxSeqId = sid;
    deduplicated.push(m);
  }

  return { deduplicated, maxSeqId };
}

// ── Tests ───────────────────────────────────────────────────────────────────

describe('seqId monotonic assignment', () => {
  it('starts at 1', () => {
    const counter = createSeqCounter();
    expect(counter.next('s1')).toBe(1);
  });

  it('never decreases', () => {
    const counter = createSeqCounter();
    let prev = 0;
    for (let i = 0; i < 100; i++) {
      const next = counter.next('s1');
      expect(next).toBeGreaterThan(prev);
      prev = next;
    }
  });

  it('each call returns unique value', () => {
    const counter = createSeqCounter();
    const ids = new Set<number>();
    for (let i = 0; i < 50; i++) {
      ids.add(counter.next('s1'));
    }
    expect(ids.size).toBe(50);
  });

  it('independent per session', () => {
    const counter = createSeqCounter();
    expect(counter.next('a')).toBe(1);
    expect(counter.next('b')).toBe(1);
    expect(counter.next('a')).toBe(2);
    expect(counter.next('b')).toBe(2);
  });
});

describe('deduplication', () => {
  it('removes messages with duplicate seqIds', () => {
    const messages: RawMsg[] = [
      { msgId: '1', seqId: 1, content: 'hello' },
      { msgId: '2', seqId: 2, content: 'world' },
      { msgId: '3', seqId: 1, content: 'hello-dup' }, // duplicate
    ];
    const { deduplicated } = deduplicateMessages(messages);
    expect(deduplicated).toHaveLength(2);
    expect(deduplicated.map(m => m.msgId)).toEqual(['1', '2']);
  });

  it('keeps first occurrence of duplicate seqId', () => {
    const messages: RawMsg[] = [
      { msgId: 'a', seqId: 5, content: 'first' },
      { msgId: 'b', seqId: 5, content: 'second' },
    ];
    const { deduplicated } = deduplicateMessages(messages);
    expect(deduplicated).toHaveLength(1);
    expect(deduplicated[0].msgId).toBe('a');
  });

  it('preserves messages with seqId 0 (unsequenced)', () => {
    const messages: RawMsg[] = [
      { msgId: 'x', seqId: 0, content: 'legacy' },
      { msgId: 'y', seqId: 0, content: 'legacy2' },
    ];
    const { deduplicated } = deduplicateMessages(messages);
    expect(deduplicated).toHaveLength(2);
  });

  it('returns correct maxSeqId', () => {
    const messages: RawMsg[] = [
      { msgId: '1', seqId: 3, content: 'a' },
      { msgId: '2', seqId: 7, content: 'b' },
      { msgId: '3', seqId: 5, content: 'c' },
    ];
    const { maxSeqId } = deduplicateMessages(messages);
    expect(maxSeqId).toBe(7);
  });
});

describe('restore resumes from max seqId', () => {
  it('counter resumes after restoring maxSeqId', () => {
    const counter = createSeqCounter();
    // Simulate: restored messages had max seqId 42
    counter.set('session-1', 42);
    expect(counter.next('session-1')).toBe(43);
    expect(counter.next('session-1')).toBe(44);
  });

  it('handles empty restore (maxSeqId 0)', () => {
    const counter = createSeqCounter();
    counter.set('session-1', 0);
    expect(counter.next('session-1')).toBe(1);
  });
});
