/**
 * Tests for `listMessagesByHandle` repo function.
 *
 * Seeds messages directly into `macsync.messages` via `bulkUpsertMessages`.
 * Each test uses a UUID-unique handle so there is no cross-test contamination.
 * Seeded rows are removed in afterAll via cascade on the device FK.
 *
 * Requires: QUINN_MACSYNC_DB_URL pointing at a reachable Postgres instance.
 */

import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
import { randomUUIDv7 } from 'bun';
import type { Pool } from 'pg';

import { bulkUpsertMessages, listMessagesByHandle } from '@/entities/message/repo';
import { pgRun } from '@/shared/db';

import { setupTestDb, teardownTestDb, type TestDb } from './db-harness';

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

let testDb: TestDb;
const seededDeviceIds: string[] = [];

function uq(): string {
  return randomUUIDv7().replace(/-/g, '').slice(0, 8);
}

async function seedDevice(pool: Pool): Promise<string> {
  const id = randomUUIDv7();
  await pgRun(
    pool,
    `INSERT INTO macsync.devices (id, name, token, platform)
     VALUES ($1, 'by-handle-test-device', $2, 'macos')`,
    [id, `tok-byhandle-${id}`],
  );
  seededDeviceIds.push(id);
  return id;
}

async function seedConversation(pool: Pool, deviceId: string): Promise<string> {
  const id = randomUUIDv7();
  await pgRun(
    pool,
    `INSERT INTO macsync.conversations (id, device_id, external_id, display_name, is_group, participants)
     VALUES ($1, $2, $3, 'By-Handle Test Convo', false, '[]'::jsonb)`,
    [id, deviceId, `ext-byhandle-${id}`],
  );
  return id;
}

function makeDraft(
  deviceId: string,
  convId: string,
  fromHandle: string,
  body: string,
  sentAt: string,
) {
  return {
    id: randomUUIDv7(),
    deviceId,
    conversationId: convId,
    externalId: `guid-byhandle-${randomUUIDv7()}`,
    provider: 'imessage' as const,
    fromHandle,
    body,
    isFromMe: false,
    hasAttachments: false,
    attachmentCount: 0,
    sentAt,
  };
}

// ---------------------------------------------------------------------------
// Setup / teardown
// ---------------------------------------------------------------------------

beforeAll(async () => {
  testDb = await setupTestDb();
});

afterAll(async () => {
  if (seededDeviceIds.length > 0) {
    const placeholders = seededDeviceIds.map((_, i) => `$${i + 1}`).join(', ');
    await pgRun(
      testDb.pool,
      `DELETE FROM macsync.messages WHERE device_id IN (${placeholders})`,
      seededDeviceIds,
    );
    await pgRun(
      testDb.pool,
      `DELETE FROM macsync.conversations WHERE device_id IN (${placeholders})`,
      seededDeviceIds,
    );
    await pgRun(
      testDb.pool,
      `DELETE FROM macsync.devices WHERE id IN (${placeholders})`,
      seededDeviceIds,
    );
  }
  await teardownTestDb(testDb);
});

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe('listMessagesByHandle', () => {
  it('returns messages where from_handle matches', async () => {
    const deviceId = await seedDevice(testDb.pool);
    const convId = await seedConversation(testDb.pool, deviceId);
    const handle = `user${uq()}@example.com`;
    const otherHandle = `other${uq()}@example.com`;
    const now = new Date().toISOString();

    await bulkUpsertMessages(testDb.pool, [
      makeDraft(deviceId, convId, handle, 'Hello from handle', now),
      makeDraft(deviceId, convId, handle, 'Second message from handle', now),
      makeDraft(deviceId, convId, otherHandle, 'Message from other handle', now),
    ]);

    const result = await listMessagesByHandle(testDb.pool, { handle, limit: 50 });

    expect(result.length).toBe(2);
    for (const msg of result) {
      expect(msg.fromHandle).toBe(handle);
    }
  });

  it('filters by since — excludes messages with sent_at <= since', async () => {
    const deviceId = await seedDevice(testDb.pool);
    const convId = await seedConversation(testDb.pool, deviceId);
    const handle = `user${uq()}@example.com`;

    const older = new Date('2024-01-01T10:00:00Z').toISOString();
    const newer = new Date('2024-01-01T12:00:00Z').toISOString();
    const cutoff = new Date('2024-01-01T11:00:00Z').toISOString();

    await bulkUpsertMessages(testDb.pool, [
      makeDraft(deviceId, convId, handle, 'Old message', older),
      makeDraft(deviceId, convId, handle, 'New message', newer),
    ]);

    const result = await listMessagesByHandle(testDb.pool, { handle, since: cutoff, limit: 50 });

    expect(result.length).toBe(1);
    expect(result[0]!.body).toBe('New message');
  });

  it('respects the limit parameter', async () => {
    const deviceId = await seedDevice(testDb.pool);
    const convId = await seedConversation(testDb.pool, deviceId);
    const handle = `user${uq()}@example.com`;
    const now = new Date().toISOString();

    const drafts = Array.from({ length: 10 }, (_, i) =>
      makeDraft(deviceId, convId, handle, `Message ${i}`, now),
    );
    await bulkUpsertMessages(testDb.pool, drafts);

    const result = await listMessagesByHandle(testDb.pool, { handle, limit: 3 });

    expect(result.length).toBeLessThanOrEqual(3);
  });

  it('returns empty array when no messages match the handle', async () => {
    const handle = `nonexistent${uq()}@example.com`;

    const result = await listMessagesByHandle(testDb.pool, { handle, limit: 50 });

    expect(result).toEqual([]);
  });

  it('filters by deviceId when provided — excludes messages from other devices', async () => {
    const deviceIdA = await seedDevice(testDb.pool);
    const deviceIdB = await seedDevice(testDb.pool);
    const convIdA = await seedConversation(testDb.pool, deviceIdA);
    const convIdB = await seedConversation(testDb.pool, deviceIdB);
    const handle = `user${uq()}@example.com`;
    const now = new Date().toISOString();

    await bulkUpsertMessages(testDb.pool, [
      makeDraft(deviceIdA, convIdA, handle, 'From device A', now),
      makeDraft(deviceIdB, convIdB, handle, 'From device B', now),
    ]);

    const result = await listMessagesByHandle(testDb.pool, {
      handle,
      deviceId: deviceIdA,
      limit: 50,
    });

    expect(result.length).toBe(1);
    expect(result[0]!.deviceId).toBe(deviceIdA);
    expect(result[0]!.body).toBe('From device A');
  });
});
