/**
 * Unit tests for `listCalls` query construction — the `since`/`limit`/`offset`
 * filters and newest-first ordering the inbox-triage consumer relies on.
 *
 * Uses a capturing FakePool (no Postgres) so we assert the exact SQL + params
 * the repo emits, mirroring the FakePool pattern in SendQueueRepo.test.ts.
 */

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

import { listCalls } from '@/entities/call/repo';
import type { CallFilter } from '@/entities/call';

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

/** Captures the query the repo issues and returns canned rows. */
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[] };
  }
}

function run(filter: CallFilter, rows: Record<string, unknown>[] = []) {
  const pool = new FakePool(rows);
  const promise = listCalls(pool as unknown as Pool, filter);
  return { pool, promise };
}

describe('listCalls query builder', () => {
  test('no filters → default limit/offset, no WHERE, newest-first ordering', async () => {
    const { pool, promise } = run({});
    await promise;
    const { sql, params } = pool.calls[0];
    expect(sql).not.toContain('WHERE');
    expect(sql).toContain('ORDER BY COALESCE(started_at, synced_at) DESC');
    // Only LIMIT + OFFSET params, with the documented defaults.
    expect(params).toEqual([500, 0]);
  });

  test('since becomes an exclusive started_at lower bound', async () => {
    const since = '2026-06-28T00:00:00.000Z';
    const { pool, promise } = run({ since, limit: 50, offset: 10 });
    await promise;
    const { sql, params } = pool.calls[0];
    expect(sql).toContain('started_at > $1');
    // since is $1, then LIMIT $2, OFFSET $3.
    expect(params).toEqual([since, 50, 10]);
  });

  test('all filters combine with AND in stable param order', async () => {
    const since = '2026-06-01T12:00:00.000Z';
    const { pool, promise } = run({
      deviceId: 'dev-1',
      direction: 'incoming',
      callType: 'facetime_video',
      since,
      limit: 200,
      offset: 0,
    });
    await promise;
    const { sql, params } = pool.calls[0];
    expect(sql).toContain('device_id = $1');
    expect(sql).toContain('direction = $2');
    expect(sql).toContain('call_type = $3');
    expect(sql).toContain('started_at > $4');
    expect(sql).toContain('AND');
    expect(params).toEqual(['dev-1', 'incoming', 'facetime_video', since, 200, 0]);
  });

  test('rows are hydrated into camelCase CallRecords', async () => {
    const { pool, promise } = run(
      { deviceId: 'dev-1' },
      [
        {
          id: 'call-1',
          device_id: 'dev-1',
          external_id: 'zpk:42',
          address: '+14155551234',
          normalized_address: '+14155551234',
          contact_name: 'Alice',
          direction: 'incoming',
          call_type: 'telephony',
          answered: true,
          duration_seconds: 127.5,
          started_at: '2026-06-28T10:00:00.000Z',
          service_provider: 'com.apple.Telephony',
          synced_at: '2026-06-28T10:00:05.000Z',
        },
      ],
    );
    const result = await promise;
    expect(pool.calls).toHaveLength(1);
    expect(result).toHaveLength(1);
    expect(result[0]).toMatchObject({
      id: 'call-1',
      deviceId: 'dev-1',
      externalId: 'zpk:42',
      normalizedAddress: '+14155551234',
      contactName: 'Alice',
      callType: 'telephony',
      answered: true,
      durationSeconds: 127.5,
    });
  });
});
