/**
 * Unit tests for the conversation-read repo SQL (Handoff 04 — logical-read
 * model). Capturing FakePool, no Postgres.
 *
 * NOTE: like outbox.repo.test.ts / calls.repo.test.ts this imports
 * `@/shared/db`, which pulls the private `@lilith/quinn-db-pg` package — run
 * where that registry is reachable (`bun install && bun test
 * src/test/conversation-read.repo.test.ts`).
 */

import { describe, expect, test } from 'bun:test';
import type { Pool } from 'pg';

import { markRead, markAllRepliedRead, getReadState } from '@/entities/conversation-read/repo';

interface Captured {
  sql: string;
  params: unknown[];
}

class FakePool {
  calls: Captured[] = [];
  private readonly rows: Record<string, unknown>[];
  constructor(rows: Record<string, unknown>[] = []) {
    this.rows = rows;
  }
  async query<T = Record<string, unknown>>(sql: string, params: unknown[] = []) {
    this.calls.push({ sql, params });
    return { rows: this.rows as unknown as T[] };
  }
}

describe('markRead', () => {
  test('upserts with monotonic GREATEST read_through and defaults to now', async () => {
    const pool = new FakePool([{ read_through: '2026-06-28T12:00:00.000Z' }]);
    const res = await markRead(pool as unknown as Pool, {
      deviceId: 'dev-1',
      handle: '+14155550000',
    });
    expect(res).toEqual({ readThrough: '2026-06-28T12:00:00.000Z' });
    const { sql, params } = pool.calls[0];
    expect(sql).toContain('ON CONFLICT (device_id, handle) DO UPDATE');
    expect(sql).toContain('GREATEST(macsync.conversation_read.read_through, EXCLUDED.read_through)');
    expect(sql).toContain('COALESCE($3::timestamptz, now())');
    // readThrough omitted → null (COALESCE falls back to now()); source default manual.
    expect(params).toEqual(['dev-1', '+14155550000', null, 'manual']);
  });

  test('passes explicit readThrough and source through', async () => {
    const pool = new FakePool([{ read_through: '2026-06-28T09:00:00.000Z' }]);
    await markRead(pool as unknown as Pool, {
      deviceId: 'dev-1',
      handle: 'a@b.com',
      readThrough: '2026-06-28T09:00:00.000Z',
      source: 'outbox_send',
    });
    expect(pool.calls[0].params).toEqual(['dev-1', 'a@b.com', '2026-06-28T09:00:00.000Z', 'outbox_send']);
  });
});

describe('markAllRepliedRead', () => {
  test('marks threads whose latest message is outbound and returns affected handles', async () => {
    const pool = new FakePool([{ handle: '+14155550000' }, { handle: 'a@b.com' }]);
    const handles = await markAllRepliedRead(pool as unknown as Pool, 'dev-1');
    expect(handles).toEqual(['+14155550000', 'a@b.com']);
    const { sql, params } = pool.calls[0];
    expect(sql).toContain('WHERE latest.is_from_me = true');
    expect(sql).toContain("source       = 'all_replied'");
    expect(sql).toContain('GREATEST(macsync.conversation_read.read_through, EXCLUDED.read_through)');
    expect(params).toEqual(['dev-1']);
  });
});

describe('getReadState', () => {
  test('unread when inbound is newer than read_through', async () => {
    const pool = new FakePool([
      {
        read_through: '2026-06-28T10:00:00.000Z',
        read_at: '2026-06-28T10:00:00.000Z',
        source: 'manual',
        last_inbound_at: '2026-06-28T11:00:00.000Z',
      },
    ]);
    const st = await getReadState(pool as unknown as Pool, 'dev-1', '+1');
    expect(st.unread).toBe(true);
    expect(st.readThrough).toBe('2026-06-28T10:00:00.000Z');
  });

  test('read when read_through is at/after the last inbound', async () => {
    const pool = new FakePool([
      {
        read_through: '2026-06-28T12:00:00.000Z',
        read_at: '2026-06-28T12:00:00.000Z',
        source: 'manual',
        last_inbound_at: '2026-06-28T11:00:00.000Z',
      },
    ]);
    const st = await getReadState(pool as unknown as Pool, 'dev-1', '+1');
    expect(st.unread).toBe(false);
  });

  test('no inbound at all → read (nothing to handle)', async () => {
    const pool = new FakePool([
      { read_through: null, read_at: null, source: null, last_inbound_at: null },
    ]);
    const st = await getReadState(pool as unknown as Pool, 'dev-1', '+1');
    expect(st.unread).toBe(false);
    expect(st.readThrough).toBeNull();
  });

  test('inbound exists but never marked read → unread', async () => {
    const pool = new FakePool([
      {
        read_through: null,
        read_at: null,
        source: null,
        last_inbound_at: '2026-06-28T11:00:00.000Z',
      },
    ]);
    const st = await getReadState(pool as unknown as Pool, 'dev-1', '+1');
    expect(st.unread).toBe(true);
  });
});
