/**
 * Backfill `document_signers` rows for EOI documents that were generated
 * before the per-recipient signer-row insert landed (pre-2026-05-15).
 *
 * Symptom on the affected docs: the EOI tab's "Signing progress" panel
 * reads "No signers loaded" forever because the webhook handler updates
 * existing rows (by token / email) and never inserts new ones.
 *
 * This script walks every documents row that has a documensoId, status
 * in ('sent', 'partially_signed', 'completed'), and zero signer rows.
 * For each, it pulls the envelope from Documenso and recreates the
 * signer rows from the recipients array. Idempotent — safe to re-run.
 *
 * Usage:
 *   pnpm tsx scripts/backfill-eoi-signers.ts            # dry-run, lists candidates
 *   pnpm tsx scripts/backfill-eoi-signers.ts --apply    # actually inserts
 */

import 'dotenv/config';
import { and, inArray, isNotNull, sql } from 'drizzle-orm';

import { db, closeDb } from '@/lib/db';
import { documents, documentSigners } from '@/lib/db/schema/documents';
import { getDocument as getDocumensoDoc } from '@/lib/services/documenso-client';
import { logger } from '@/lib/logger';

interface BackfillStats {
  scanned: number;
  withZeroSigners: number;
  inserted: number;
  failed: number;
  skipped: number;
}

async function main() {
  const apply = process.argv.includes('--apply');

  // 1. Find candidate documents: in-flight or completed EOIs with a
  //    documensoId and no signer rows.
  const candidates = await db
    .select({
      id: documents.id,
      portId: documents.portId,
      documensoId: documents.documensoId,
      status: documents.status,
      documentType: documents.documentType,
      title: documents.title,
      signerCount: sql<number>`(
        SELECT COUNT(*)::int FROM ${documentSigners}
        WHERE ${documentSigners.documentId} = ${documents.id}
      )`,
    })
    .from(documents)
    .where(
      and(
        inArray(documents.status, ['sent', 'partially_signed', 'completed']),
        isNotNull(documents.documensoId),
      ),
    );

  const stats: BackfillStats = {
    scanned: candidates.length,
    withZeroSigners: 0,
    inserted: 0,
    failed: 0,
    skipped: 0,
  };

  const needsBackfill = candidates.filter((c) => c.signerCount === 0);
  stats.withZeroSigners = needsBackfill.length;

  console.log(
    `Scanned ${stats.scanned} document${stats.scanned === 1 ? '' : 's'}; ${stats.withZeroSigners} need backfill.`,
  );
  if (!apply) {
    console.log('\nDRY RUN (pass --apply to insert):');
    for (const doc of needsBackfill) {
      console.log(`  - ${doc.id} (${doc.title}) — port=${doc.portId}, status=${doc.status}`);
    }
    await closeDb();
    return;
  }

  // 2. For each candidate, fetch the envelope from Documenso and insert
  //    the signer rows. Failures are logged + counted; processing
  //    continues so one broken doc doesn't halt the run.
  for (const doc of needsBackfill) {
    if (!doc.documensoId) {
      stats.skipped++;
      continue;
    }
    try {
      const envelope = await getDocumensoDoc(doc.documensoId, doc.portId);
      if (envelope.recipients.length === 0) {
        logger.warn({ documentId: doc.id }, 'Backfill: envelope has no recipients — skipping');
        stats.skipped++;
        continue;
      }

      // Use the same role-mapping logic as the create-time flow:
      //   - signingOrder=1 + role SIGNER  →  'client' (positional)
      //   - SIGNER otherwise              →  'signer'
      //   - APPROVER                      →  'approver'
      //   - CC / VIEWER                   →  pass-through
      const rows = envelope.recipients.map((r) => {
        const cleanName = (r.name || r.email)
          .replace(/\s*\(was:[^)]*\)/i, '')
          .replace(/\s*\(placeholder\)/i, '')
          .trim();
        const upRole = r.role.toUpperCase();
        const role =
          upRole === 'SIGNER' && r.signingOrder === 1
            ? 'client'
            : upRole === 'APPROVER'
              ? 'approver'
              : upRole === 'CC'
                ? 'cc'
                : upRole === 'VIEWER'
                  ? 'viewer'
                  : 'signer';
        return {
          documentId: doc.id,
          signerName: cleanName || r.email,
          signerEmail: r.email,
          signerRole: role,
          signingOrder: r.signingOrder,
          status: (r.status === 'SIGNED' ? 'signed' : 'pending') as 'signed' | 'pending',
          signingUrl: r.signingUrl ?? null,
          embeddedUrl: r.embeddedUrl ?? null,
          signingToken: r.token ?? null,
          // No invitedAt — the backfill can't reconstruct the original
          // dispatch timestamp. Reps see the card as "Not yet invited"
          // for any pending signer; clicking Send invitation re-stamps.
          invitedAt: null,
        };
      });

      await db.insert(documentSigners).values(rows);
      stats.inserted += rows.length;
      console.log(`  ✓ ${doc.id} (${doc.title}) — inserted ${rows.length} signer row(s)`);
    } catch (err) {
      stats.failed++;
      logger.error(
        { err: err instanceof Error ? err.message : err, documentId: doc.id },
        'Backfill failed for document',
      );
      console.log(`  ✗ ${doc.id} — ${err instanceof Error ? err.message : 'unknown error'}`);
    }
  }

  console.log(`\nDone. inserted=${stats.inserted} failed=${stats.failed} skipped=${stats.skipped}`);
  await closeDb();
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
