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

import {
  enqueueMailSend,
  listPendingMailSends,
  markMailSendResult,
} from '@/features/imail/sendService';
import { injectDb, resetDb } from '@/shared/db';

class FakePool {
  rows: Array<Record<string, unknown>> = [];

  async query<T = Record<string, unknown>>(sql: string, params: unknown[] = []) {
    if (sql.startsWith('INSERT')) {
      const [deviceId, action, payloadJson] = params as [string, string, string];
      const row = {
        id: `id-${this.rows.length + 1}`,
        device_id: deviceId,
        action,
        payload: JSON.parse(payloadJson),
        status: 'queued',
        sent_at: null,
        failure_reason: null,
        created_at: new Date(Date.now() + this.rows.length).toISOString(),
        updated_at: new Date().toISOString(),
      };
      this.rows.push(row);
      return { rows: [{ id: row.id }] as unknown as T[] };
    }
    if (sql.includes('SELECT id, device_id')) {
      const [deviceId] = params as [string];
      const matching = this.rows
        .filter((r) => r.device_id === deviceId && r.status === 'queued')
        .sort((a, b) => String(a.created_at).localeCompare(String(b.created_at)));
      return { rows: matching as unknown as T[] };
    }
    if (sql.includes("status = 'sent'")) {
      const [id] = params as [string];
      const row = this.rows.find((r) => r.id === id);
      if (row) {
        row.status = 'sent';
        row.sent_at = new Date().toISOString();
      }
      return { rows: [] as unknown as T[] };
    }
    if (sql.includes("status = 'failed'")) {
      const [id, reason] = params as [string, string | null];
      const row = this.rows.find((r) => r.id === id);
      if (row) {
        row.status = 'failed';
        row.failure_reason = reason;
      }
      return { rows: [] as unknown as T[] };
    }
    return { rows: [] as unknown as T[] };
  }
}

describe('imail sendService', () => {
  let pool: FakePool;

  beforeEach(() => {
    pool = new FakePool();
    injectDb(pool as unknown as Pool);
  });

  afterEach(() => {
    resetDb();
  });

  test('enqueue → listPending → markResult round-trip', async () => {
    const { id } = await enqueueMailSend({
      deviceId: 'dev-1',
      action: 'send_mail',
      payload: {
        to: 'alice@example.com',
        subject: 'Hello',
        body: 'Hi Alice',
      },
    });

    const pending = await listPendingMailSends('dev-1');
    expect(pending).toHaveLength(1);
    expect(pending[0]?.id).toBe(id);
    expect(pending[0]?.action).toBe('send_mail');
    expect(pending[0]?.payload.to).toBe('alice@example.com');
    expect(pending[0]?.payload.subject).toBe('Hello');

    await markMailSendResult({ id, status: 'sent' });
    const after = await listPendingMailSends('dev-1');
    expect(after).toHaveLength(0);
  });

  test('rejects unknown action', async () => {
    await expect(
      enqueueMailSend({
        deviceId: 'dev-1',
        action: 'bogus' as never,
        payload: {
          to: 'alice@example.com',
          subject: 's',
          body: 'b',
        },
      }),
    ).rejects.toThrow();
  });

  test('rejects invalid email address', async () => {
    await expect(
      enqueueMailSend({
        deviceId: 'dev-1',
        action: 'send_mail',
        payload: {
          to: 'not-an-email',
          subject: 's',
          body: 'b',
        },
      }),
    ).rejects.toThrow();
  });

  test('failure result is recorded with reason', async () => {
    const { id } = await enqueueMailSend({
      deviceId: 'dev-1',
      action: 'send_mail',
      payload: {
        to: 'bob@example.com',
        subject: 'Test',
        body: 'Test body',
      },
    });
    await markMailSendResult({ id, status: 'failed', error: 'Mail.app unavailable' });
    const pending = await listPendingMailSends('dev-1');
    expect(pending).toHaveLength(0);
  });
});
