/**
 * Bounce-Tracking: persistiert Hard-Bounces in tracking_events + unsubscribes.
 *
 * Wird vom Inbox-Scanner (src/lib/bounces/inbox-scanner.ts) aufgerufen, sobald
 * eine NDR-Mail im Sender-Postfach gefunden und geparsed wurde. Der Recipient-
 * Lookup läuft per Email (case-insensitive), weil NDRs nur die Email-Adresse
 * tragen, keine Recipient-ID.
 *
 * Best-Effort: failures werden geloggt, nicht geworfen — der Inbox-Scanner soll
 * weiter andere NDRs verarbeiten können wenn eine einzelne Insertion fehlschlägt.
 */

import { and, eq, gte, sql } from 'drizzle-orm';
import { db } from './client';
import { recipients, trackingEvents, unsubscribes, campaigns } from './schema';

export interface RecordBounceInput {
  /** Bounce-Empfänger-Email (aus X-Failed-Recipients oder Body-Regex). */
  email: string;
  /** Bounce-Grund / NDR-Diagnose (Body-Auszug, max 500 Zeichen). */
  reason: string;
  /** Optional: messageId des bouncenden Originals (für Audit-Trail). */
  messageId?: string;
  /** Lookback in Tagen — nur Recipients aus diesem Zeitfenster matchen (Default 14). */
  lookbackDays?: number;
}

export type BounceResult =
  | { ok: true; recipientId: string; eventId: string; unsubscribed: boolean }
  | { ok: false; reason: 'no_recent_recipient' | 'db_error' };

export function recordBounce(input: RecordBounceInput): BounceResult {
  try {
    const lookbackDays = input.lookbackDays ?? 14;
    const since = new Date(Date.now() - lookbackDays * 86400_000);

    // Den jüngsten Recipient mit dieser Email finden, der innerhalb des Lookback
    // angeschrieben wurde. Recipients sind pro Campaign gespeichert — wir nehmen
    // den neuesten, weil der bouncende NDR auf den neuesten Versand bezieht.
    const recent = db
      .select({
        id: recipients.id,
        campaignId: recipients.campaignId,
        variantId: recipients.variantId,
      })
      .from(recipients)
      .innerJoin(campaigns, eq(campaigns.id, recipients.campaignId))
      .where(and(
        sql`lower(${recipients.email}) = ${input.email.toLowerCase()}`,
        gte(campaigns.startedAt, since),
      ))
      .orderBy(sql`${campaigns.startedAt} DESC`)
      .limit(1)
      .get();

    if (!recent) return { ok: false, reason: 'no_recent_recipient' };

    const eventId = crypto.randomUUID();
    db.insert(trackingEvents)
      .values({
        id: eventId,
        campaignId: recent.campaignId,
        recipientId: recent.id,
        variantId: recent.variantId ?? null,
        messageId: input.messageId ?? '',
        eventType: 'bounce',
        source: 'system',
        metaJson: { reason: input.reason.slice(0, 500) },
      })
      .run();

    // Empfänger global aus zukünftigen Sends nehmen — Pattern wie ein Unsub
    // mit source='complaint'. SQLite-INSERT-IGNORE via try-catch, weil die
    // email-Spalte unique ist und ein zweiter Bounce-Versuch nicht crashen darf.
    let unsubscribed = false;
    try {
      db.insert(unsubscribes)
        .values({
          id: crypto.randomUUID(),
          email: input.email.toLowerCase(),
          campaignId: recent.campaignId,
          reason: 'hard-bounce: ' + input.reason.slice(0, 200),
          source: 'complaint',
        })
        .run();
      unsubscribed = true;
    } catch (e) {
      // Email schon in unsubscribes (UNIQUE-Constraint) — kein Fehler, nur loggen
      if (e instanceof Error && /UNIQUE/i.test(e.message)) {
        unsubscribed = false;
      } else {
        console.error('[recordBounce] unsubscribe-insert failed:', e);
      }
    }

    return { ok: true, recipientId: recent.id, eventId, unsubscribed };
  } catch (e) {
    console.error('[recordBounce] failed:', e instanceof Error ? e.message : e);
    return { ok: false, reason: 'db_error' };
  }
}
