/**
 * Regression test for the audit finding "branded post-completion email
 * not firing when Documenso webhook is unreachable".
 *
 * Both delivery paths (webhook receiver + polling fallback) call
 * handleDocumentCompleted, so the branded "all signed" email fan-out
 * inside that function should fire identically regardless of which path
 * triggered it. This test exercises the polling path explicitly and
 * asserts the email is queued.
 */
import { describe, it, expect, beforeAll, vi } from 'vitest';
import { eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { documents, documentFolders, documentSigners } from '@/lib/db/schema/documents';
import { user } from '@/lib/db/schema/users';
import { handleDocumentCompleted } from '@/lib/services/documents.service';
import { ensureSystemRoots } from '@/lib/services/document-folders.service';
import { makeClient, makePort } from '../helpers/factories';

// Stub Documenso download so we don't hit the network.
vi.mock('@/lib/services/documenso-client', async (importOriginal) => {
  const real = await importOriginal<typeof import('@/lib/services/documenso-client')>();
  return {
    ...real,
    downloadSignedPdf: vi.fn(async () => Buffer.from('%PDF-1.4 stub\n')),
  };
});

const stubPuts = new Map<string, Buffer>();
vi.mock('@/lib/storage', async (importOriginal) => {
  const real = await importOriginal<typeof import('@/lib/storage')>();
  return {
    ...real,
    getStorageBackend: vi.fn(async () => ({
      put: async (path: string, data: Buffer) => {
        stubPuts.set(path, data);
      },
      get: async (path: string) => stubPuts.get(path) ?? Buffer.alloc(0),
      head: async (path: string) => {
        const buf = stubPuts.get(path);
        return buf ? { sizeBytes: buf.length, contentType: 'application/pdf' } : null;
      },
      delete: async (path: string) => {
        stubPuts.delete(path);
      },
      presignedGet: async () => 'http://stub-url',
      presignedPut: async () => ({ url: 'http://stub-url', fields: {} }),
    })),
  };
});

// Spy on the email fan-out — replace it with a vi.fn we can assert on.
type SendArgs = Parameters<
  (typeof import('@/lib/services/document-signing-emails.service'))['sendSigningCompleted']
>[0];
const sendSigningCompletedSpy = vi.fn<(args: SendArgs) => Promise<void>>(async () => {});
vi.mock('@/lib/services/document-signing-emails.service', async (importOriginal) => {
  const real =
    await importOriginal<typeof import('@/lib/services/document-signing-emails.service')>();
  return {
    ...real,
    sendSigningCompleted: (args: SendArgs) => sendSigningCompletedSpy(args),
  };
});

let TEST_USER_ID = '';

beforeAll(async () => {
  const [u] = await db.select({ id: user.id }).from(user).limit(1);
  if (!u) throw new Error('No user available; run pnpm db:seed first');
  TEST_USER_ID = u.id;
});

describe('handleDocumentCompleted · email fan-out (polling-path regression)', () => {
  it('queues sendSigningCompleted exactly once for the polling-driven completion', async () => {
    sendSigningCompletedSpy.mockClear();
    const port = await makePort();
    const portId = port.id;
    await db.delete(documentFolders).where(eq(documentFolders.portId, portId));
    await ensureSystemRoots(portId, TEST_USER_ID);
    const client = await makeClient({ portId });

    const documensoId = `docu-email-fanout-${Date.now()}`;
    const [doc] = await db
      .insert(documents)
      .values({
        portId,
        clientId: client.id,
        documensoId,
        documentType: 'eoi',
        title: 'Email fan-out test EOI',
        status: 'sent',
        createdBy: TEST_USER_ID,
      })
      .returning();

    await db.insert(documentSigners).values({
      documentId: doc!.id,
      signerName: 'Test Signer',
      signerEmail: 'signer@example.com',
      signerRole: 'client',
      signingOrder: 1,
      status: 'signed',
    });

    // Polling path: invoked from src/jobs/processors/documenso-poll.ts the
    // exact same way the webhook receiver invokes it. If this stops calling
    // the fan-out, the audit symptom returns.
    await handleDocumentCompleted({ documentId: documensoId, portId });

    expect(sendSigningCompletedSpy).toHaveBeenCalledTimes(1);
    const firstCall = sendSigningCompletedSpy.mock.calls[0];
    expect(firstCall).toBeDefined();
    const args = firstCall![0];
    expect(args.recipients.some((r: { email: string }) => r.email === 'signer@example.com')).toBe(
      true,
    );
    expect(args.documentLabel).toBeTruthy();
    expect(args.signedPdfFileId).toBeTruthy();

    // Idempotency: a second call (e.g. webhook arriving after the poll
    // already completed) must NOT re-fire the email. The status+signedFileId
    // gate in handleDocumentCompleted short-circuits before reaching the
    // fan-out branch.
    await handleDocumentCompleted({ documentId: documensoId, portId });
    expect(sendSigningCompletedSpy).toHaveBeenCalledTimes(1);
  });
});
