/**
 * deadletter.ts — translation job failure sink.
 *
 * Marks a translation_jobs row FAILED, writes an outbox event for admin-alert
 * dispatch, and calls afterTranslate so deal visibility recomputes.
 *
 * Pattern: UPDATE translation_jobs → INSERT outbox (TRANSLATION_JOB_FAILED) →
 * enqueueOutbox (best-effort) → afterTranslate.
 *
 * Does NOT throw — failure in any step is logged but never re-thrown, so the
 * queue message always acks and the original error is preserved.
 */

import type { DrizzleClient } from '@/server/db/client.js';
import {
  failTranslationJob,
  getTranslationJobDetails,
  insertTranslationFailureOutbox,
} from '@/server/db/queries/translation/jobs.js';
import { enqueueOutbox } from '@/server/queues/outbox-producer.js';
import { afterTranslate } from '@/server/translation/state.js';

/**
 * Mark a translation job as permanently FAILED.
 *
 * Steps:
 *  1. UPDATE translation_jobs SET status='FAILED', last_error, finished_at.
 *  2. Lookup job row → dealId, targetLocale, attempt for the outbox payload.
 *  3. INSERT outbox row (eventType='TRANSLATION_JOB_FAILED') for admin email dispatch.
 *  4. Call enqueueOutbox (best-effort — process-outbox cron is the safety net).
 *  5. Call afterTranslate so deals.translation_status recomputes; deal remains
 *     visible to source-locale visitors via dealsVisibleWhere (Plan 1).
 *
 * @param db    - Injected DrizzleClient.
 * @param jobId - UUID of the translation_jobs row.
 * @param error - Human-readable error message stored in last_error.
 */
export async function failJob(db: DrizzleClient, jobId: string, error: string): Promise<void> {
  // ── 1. Mark the job FAILED ─────────────────────────────────────────────────
  try {
    await failTranslationJob(db, jobId, error);
  } catch (updateErr) {
    console.error(
      JSON.stringify({
        event: 'translation_job_deadletter_update_failed',
        jobId,
        originalError: error,
        updateError: updateErr instanceof Error ? updateErr.message : String(updateErr),
      }),
    );
    // Cannot proceed without the row — bail early.
    return;
  }

  console.error(
    JSON.stringify({
      event: 'translation_job_deadletter',
      jobId,
      error,
    }),
  );

  // ── 2. Lookup job row for outbox payload ───────────────────────────────────
  let dealId: string | undefined;
  let targetLocale: string | undefined;
  let attempt: number | undefined;

  try {
    const [job] = await getTranslationJobDetails(db, jobId);

    dealId = job?.dealId ?? undefined;
    targetLocale = job?.targetLocale ?? undefined;
    attempt = job?.attempt ?? undefined;
  } catch (lookupErr) {
    console.error(
      JSON.stringify({
        event: 'translation_job_deadletter_lookup_failed',
        jobId,
        error: lookupErr instanceof Error ? lookupErr.message : String(lookupErr),
      }),
    );
    // Fall through — still write outbox with partial payload.
  }

  // ── 3. Write outbox row for admin-alert dispatch ───────────────────────────
  let outboxId: string | undefined;
  try {
    const [outboxRow] = await insertTranslationFailureOutbox(db, {
      jobId,
      dealId: dealId ?? null,
      targetLocale: targetLocale ?? null,
      error,
      attempt: attempt ?? null,
    });

    outboxId = outboxRow?.id;
  } catch (outboxErr) {
    console.error(
      JSON.stringify({
        event: 'translation_job_deadletter_outbox_failed',
        jobId,
        error: outboxErr instanceof Error ? outboxErr.message : String(outboxErr),
      }),
    );
    // process-outbox cron will not have this row — log only.
  }

  // ── 4. Enqueue outbox message (best-effort) ────────────────────────────────
  if (outboxId) {
    try {
      await enqueueOutbox(outboxId);
    } catch (enqueueErr) {
      // Non-fatal — process-outbox cron is the safety net.
      console.error(
        JSON.stringify({
          event: 'translation_job_deadletter_enqueue_failed',
          jobId,
          outboxId,
          error: enqueueErr instanceof Error ? enqueueErr.message : String(enqueueErr),
        }),
      );
    }
  }

  // ── 5. Recompute deal translation_status ──────────────────────────────────
  if (dealId) {
    try {
      await afterTranslate(db, dealId);
    } catch (stateErr) {
      console.error(
        JSON.stringify({
          event: 'translation_job_deadletter_aftertranslate_failed',
          jobId,
          dealId,
          error: stateErr instanceof Error ? stateErr.message : String(stateErr),
        }),
      );
    }
  }
}
