/* =====================================================================
   routes/supporter-paypal.js — PayPal-Abo erstellen + Webhook-Verarbeitung
   (Dogfather_VanVan_Supporter_Abo.odt Abschnitt 6-8). 1:1 portiert aus
   cloudflare-worker/src/routes/supporter-paypal.js.

   WICHTIG: `req.rawBody` (der unveränderte Roh-Text des Request-Bodys,
   von index.js im express.json()-verify-Callback gesetzt) wird für die
   PayPal-Signaturprüfung gebraucht — req.body ist bereits geparst und
   könnte beim Neuzusammenbau minimal vom Original abweichen (Reihenfolge/
   Whitespace), was die Signatur ungültig machen würde.
   ===================================================================== */
import { db } from "../db.js";
import { generateId, nowIso } from "../lib/crypto.js";
import { json } from "../lib/http.js";
import { logAction } from "../lib/audit.js";
import { createSubscription, verifyWebhookSignature, cancelSubscription, PayPalNotConfiguredError } from "../lib/paypal.js";
import { allUnlockedMilestones } from "../lib/supporter-cycle.js";
import { sendMilestoneUnlocked } from "../lib/supporter-mail.js";

const REDEEM_LABELS = {
  teddy_klein: "Kleiner Hasen-Teddy",
  teddy_mittel: "Mittelgroßer Hasen-Teddy",
  teddy_gross: "Großer Hasen-Teddy",
  tasse_karte: "Tasse & Autogrammkarte",
  hoodie: "Exklusiver Hoodie",
};

function paypalNotConfiguredResponse(res) {
  return json(
    res,
    {
      ok: false,
      code: "PAYPAL_NOT_CONFIGURED",
      error:
        "PayPal ist auf dieser Website noch nicht eingerichtet. Der Websitebetreiber muss zuerst " +
        "PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_PLAN_ID und PAYPAL_WEBHOOK_ID setzen " +
        "(siehe cloudflare-worker/README.md, Abschnitt 'Supporter-Abo Setup').",
    },
    503
  );
}

/** Startet ein PayPal-Abonnement für den eingeloggten Supporter (Abschnitt 6, Schritt 6). */
export async function startPaypalSubscription(req, res, supporter) {
  const body = req.body || {};
  const origin = body.origin || "https://dogfather-universe.com";

  try {
    const { paypalSubscriptionId, approveUrl } = await createSubscription({
      supporterId: supporter.id,
      returnUrl: `${origin}/supporter.html?paypal=success`,
      cancelUrl: `${origin}/abonnieren.html?paypal=cancelled`,
    });
    if (!approveUrl) return json(res, { ok: false, error: "PayPal hat keinen Bestätigungslink geliefert." }, 502);

    const subId = generateId();
    db.prepare(
      `INSERT INTO supporter_subscriptions
         (id, supporter_id, paypal_subscription_id, paypal_plan_id, status, total_paid_months, created_at, updated_at)
       VALUES (?, ?, ?, ?, 'pending', 0, ?, ?)`
    ).run(subId, supporter.id, paypalSubscriptionId, process.env.PAYPAL_PLAN_ID, nowIso(), nowIso());

    logAction("system", "supporter.subscription_started", supporter.id, { paypalSubscriptionId });
    return json(res, { ok: true, approveUrl });
  } catch (err) {
    if (err instanceof PayPalNotConfiguredError) return paypalNotConfiguredResponse(res);
    return json(res, { ok: false, error: "PayPal-Abo konnte nicht erstellt werden." }, 502);
  }
}

/**
 * Gegenstück zu den PayPal Smart-Payment-Buttons im Frontend: dort erzeugt
 * das PayPal-JS-SDK die Subscription direkt im Browser (zeigt dabei
 * automatisch alle für die Käuferin/den Käufer verfügbaren Zahlarten an —
 * PayPal-Guthaben, Bankkonto, Kreditkarte als Gast, je nach Land/Konto),
 * damit landet das Geld direkt auf Dogis PayPal-Konto. Der Browser
 * übergibt uns danach nur noch die fertige `subscriptionID`, die wir hier
 * mit dem Supporter-Konto verknüpfen. Der tatsächliche Aktivierungsstatus
 * kommt weiterhin ausschließlich über den geprüften Webhook (Abschnitt 6:
 * "Eine reine Weiterleitung... darf nicht ausreichen, um das Abo als aktiv
 * zu markieren").
 */
export async function confirmPaypalSubscription(req, res, supporter) {
  const body = req.body || {};
  const paypalSubscriptionId = String(body.subscriptionId || "").trim();
  if (!paypalSubscriptionId) return json(res, { ok: false, error: "Keine PayPal-Subscription-ID übergeben." }, 400);

  // Rechts-Update 04.08.2026: Nachweis der Widerrufsrecht-Zustimmung
  // (sofortiger Leistungsbeginn bei digitalen Inhalten, § 356 Abs. 5 BGB /
  // entspr. Regelung, siehe agb.html Ziffer 6). Die Checkbox im Frontend
  // (abonnieren.html) blockiert das Laden der PayPal-Buttons bereits
  // clientseitig — diese serverseitige Prüfung verhindert zusätzlich eine
  // Umgehung per direktem API-Aufruf ohne echte Zustimmung.
  const widerrufConsentAt = String(body.widerrufConsentAt || "").trim();
  const consentDatum = widerrufConsentAt ? new Date(widerrufConsentAt) : null;
  const consentGueltig = consentDatum && !Number.isNaN(consentDatum.getTime()) && consentDatum.getTime() <= Date.now();
  if (!consentGueltig) {
    return json(res, { ok: false, error: "Bitte bestätige zuerst die Checkbox zum sofortigen Leistungsbeginn." }, 400);
  }

  const existing = db.prepare(`SELECT id FROM supporter_subscriptions WHERE paypal_subscription_id = ?`).get(paypalSubscriptionId);
  if (existing) return json(res, { ok: true }); // schon verknüpft (z.B. doppelter Klick) — kein Fehler

  const subId = generateId();
  db.prepare(
    `INSERT INTO supporter_subscriptions
       (id, supporter_id, paypal_subscription_id, paypal_plan_id, status, total_paid_months, created_at, updated_at)
     VALUES (?, ?, ?, ?, 'pending', 0, ?, ?)`
  ).run(subId, supporter.id, paypalSubscriptionId, process.env.PAYPAL_PLAN_ID, nowIso(), nowIso());

  logAction("system", "supporter.subscription_confirmed_client_side", supporter.id, {
    paypalSubscriptionId,
    widerrufConsentAt: consentDatum.toISOString(),
  });
  return json(res, { ok: true });
}

export async function cancelPaypalSubscription(req, res, supporter) {
  const sub = db
    .prepare(
      `SELECT * FROM supporter_subscriptions WHERE supporter_id = ? AND status IN ('active','pending','suspended') ORDER BY created_at DESC LIMIT 1`
    )
    .get(supporter.id);
  if (!sub) return json(res, { ok: false, error: "Kein aktives Abonnement gefunden." }, 404);

  try {
    if (sub.paypal_subscription_id) await cancelSubscription(sub.paypal_subscription_id);
  } catch (err) {
    if (err instanceof PayPalNotConfiguredError) return paypalNotConfiguredResponse(res);
    return json(res, { ok: false, error: "Kündigung bei PayPal fehlgeschlagen." }, 502);
  }

  db.prepare(`UPDATE supporter_subscriptions SET status = 'cancelled', cancelled_at = ?, updated_at = ? WHERE id = ?`).run(
    nowIso(),
    nowIso(),
    sub.id
  );
  logAction("system", "supporter.subscription_cancelled", supporter.id, null);
  return json(res, { ok: true });
}

/* ---------- Webhook (öffentlich erreichbar, aber signaturgeprüft) ---------- */

function findSubscriptionByPaypalId(paypalSubscriptionId) {
  if (!paypalSubscriptionId) return null;
  return db.prepare(`SELECT * FROM supporter_subscriptions WHERE paypal_subscription_id = ?`).get(paypalSubscriptionId);
}

function alreadyProcessed(eventId) {
  if (!eventId) return false;
  const row = db.prepare(`SELECT id FROM supporter_payments WHERE paypal_event_id = ?`).get(eventId);
  return !!row;
}

/** Erfolgreiche Zahlung: Monat anrechnen, ggf. neue Prämien freischalten (Abschnitt 8, 11-13). */
async function handleSuccessfulPayment(sub, event, resource) {
  const isSandbox = process.env.PAYPAL_ENV !== "live";
  const eventId = event.id;
  const paymentId = generateId();

  db.prepare(
    `INSERT INTO supporter_payments
       (id, supporter_id, subscription_id, paypal_transaction_id, paypal_event_id, amount, currency,
        payment_status, is_sandbox, counted_for_progress, paid_at, created_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, 'completed', ?, ?, ?, ?)`
  ).run(
    paymentId, sub.supporter_id, sub.id, resource.id || null, eventId,
    resource.amount?.total || resource.amount?.value || null,
    resource.amount?.currency || "EUR",
    isSandbox ? 1 : 0, isSandbox ? 0 : 1, nowIso(), nowIso()
  );

  if (isSandbox) return; // Testzahlungen zählen NIE als echter Monat (Abschnitt 8/34).

  const oldTotal = sub.total_paid_months;
  const newTotal = oldTotal + 1;
  db.prepare(`UPDATE supporter_subscriptions SET total_paid_months = ?, updated_at = ? WHERE id = ?`).run(newTotal, nowIso(), sub.id);

  const before = new Set(allUnlockedMilestones(oldTotal).map((m) => `${m.cycleNumber}:${m.month}`));
  const after = allUnlockedMilestones(newTotal);
  const newlyUnlocked = after.filter((m) => !before.has(`${m.cycleNumber}:${m.month}`));

  for (const m of newlyUnlocked) {
    // Kollektion für diesen Zyklus ermitteln (per collection_number = cycleNumber),
    // fällt auf Kollektion 1 zurück, falls eine spätere Kollektion im Admin-
    // Bereich noch nicht angelegt wurde — damit niemand ins Leere freigeschaltet wird.
    const collection =
      db.prepare(`SELECT id FROM supporter_collections WHERE collection_number = ?`).get(m.cycleNumber) ||
      db.prepare(`SELECT id FROM supporter_collections WHERE collection_number = 1`).get();
    if (!collection) continue;

    db.prepare(
      `INSERT OR IGNORE INTO supporter_premiums
         (id, supporter_id, collection_id, cycle_number, milestone_month, premium_type, unlocked_at, redeem_status)
       VALUES (?, ?, ?, ?, ?, ?, ?, 'verfuegbar')`
    ).run(generateId(), sub.supporter_id, collection.id, m.cycleNumber, m.month, m.type, nowIso());

    const supporterRow = db.prepare(`SELECT display_name, email FROM supporter_users WHERE id = ?`).get(sub.supporter_id);
    if (supporterRow) {
      await sendMilestoneUnlocked({
        to: supporterRow.email,
        name: supporterRow.display_name,
        premiumLabel: REDEEM_LABELS[m.type] || m.type,
      }).catch(() => {}); // E-Mail-Fehler dürfen die Webhook-Verarbeitung nie blockieren.
    }
    logAction("system", "supporter.milestone_unlocked", sub.supporter_id, { cycle: m.cycleNumber, month: m.month, type: m.type });
  }
}

function handleReversedPayment(sub, event, resource) {
  const eventId = event.id;
  const paymentId = generateId();
  db.prepare(
    `INSERT INTO supporter_payments
       (id, supporter_id, subscription_id, paypal_transaction_id, paypal_event_id, amount, currency,
        payment_status, is_sandbox, counted_for_progress, paid_at, created_at)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?)`
  ).run(
    paymentId, sub.supporter_id, sub.id, resource.id || null, eventId,
    resource.amount?.total || resource.amount?.value || null, resource.amount?.currency || "EUR",
    event.event_type.includes("REFUND") ? "refunded" : "reversed",
    process.env.PAYPAL_ENV !== "live" ? 1 : 0, nowIso(), nowIso()
  );

  // Zurückgebuchte/zurückerstattete Zahlung: betroffenen Monat zur manuellen
  // Prüfung markieren statt automatisch zu löschen (Abschnitt 24 — "muss
  // manuell durch einen Administrator geprüft werden").
  logAction("admin", "supporter.payment_flagged_for_review", sub.supporter_id, {
    eventType: event.event_type,
    transactionId: resource.id,
  });
}

export async function handlePaypalWebhook(req, res) {
  const rawBody = req.rawBody || JSON.stringify(req.body || {});
  let event;
  try {
    event = JSON.parse(rawBody);
  } catch {
    return json(res, { ok: false, error: "Ungültiger Webhook-Body." }, 400);
  }

  let verified = false;
  try {
    verified = await verifyWebhookSignature(req, rawBody);
  } catch (err) {
    if (err instanceof PayPalNotConfiguredError) return paypalNotConfiguredResponse(res);
    return json(res, { ok: false, error: "Webhook-Signaturprüfung fehlgeschlagen." }, 400);
  }
  if (!verified) return json(res, { ok: false, error: "Ungültige Webhook-Signatur." }, 400);

  if (alreadyProcessed(event.id)) return json(res, { ok: true, duplicate: true });

  const resource = event.resource || {};
  const type = event.event_type;

  try {
    if (type === "BILLING.SUBSCRIPTION.ACTIVATED") {
      const sub = findSubscriptionByPaypalId(resource.id);
      if (sub) {
        db.prepare(
          `UPDATE supporter_subscriptions SET status = 'active', started_at = COALESCE(started_at, ?),
             next_payment_at = ?, updated_at = ? WHERE id = ?`
        ).run(nowIso(), resource.billing_info?.next_billing_time || null, nowIso(), sub.id);
      }
    } else if (type === "BILLING.SUBSCRIPTION.UPDATED") {
      const sub = findSubscriptionByPaypalId(resource.id);
      if (sub) {
        db.prepare(`UPDATE supporter_subscriptions SET next_payment_at = ?, updated_at = ? WHERE id = ?`).run(
          resource.billing_info?.next_billing_time || sub.next_payment_at,
          nowIso(),
          sub.id
        );
      }
    } else if (type === "BILLING.SUBSCRIPTION.CANCELLED") {
      const sub = findSubscriptionByPaypalId(resource.id);
      if (sub) {
        db.prepare(
          `UPDATE supporter_subscriptions SET status = 'cancelled', cancelled_at = ?,
             paid_period_end_at = COALESCE(paid_period_end_at, next_payment_at), updated_at = ? WHERE id = ?`
        ).run(nowIso(), nowIso(), sub.id);
      }
    } else if (type === "BILLING.SUBSCRIPTION.SUSPENDED") {
      const sub = findSubscriptionByPaypalId(resource.id);
      if (sub) db.prepare(`UPDATE supporter_subscriptions SET status = 'suspended', updated_at = ? WHERE id = ?`).run(nowIso(), sub.id);
    } else if (type === "BILLING.SUBSCRIPTION.EXPIRED") {
      const sub = findSubscriptionByPaypalId(resource.id);
      if (sub) db.prepare(`UPDATE supporter_subscriptions SET status = 'expired', updated_at = ? WHERE id = ?`).run(nowIso(), sub.id);
    } else if (type === "PAYMENT.SALE.COMPLETED") {
      const sub = findSubscriptionByPaypalId(resource.billing_agreement_id);
      if (sub) await handleSuccessfulPayment(sub, event, resource);
    } else if (type === "PAYMENT.SALE.DENIED") {
      const sub = findSubscriptionByPaypalId(resource.billing_agreement_id);
      if (sub) logAction("system", "supporter.payment_failed", sub.supporter_id, { transactionId: resource.id });
    } else if (type === "PAYMENT.SALE.REFUNDED" || type === "PAYMENT.SALE.REVERSED") {
      const sub = findSubscriptionByPaypalId(resource.billing_agreement_id);
      if (sub) handleReversedPayment(sub, event, resource);
    }
    // Unbekannte/nicht relevante Ereignistypen werden bewusst stillschweigend bestätigt
    // (PayPal wiederholt Zustellversuche sonst unnötig).
  } catch (err) {
    // Bewusst 200 statt 500 an PayPal zurückgeben nur, wenn der Fehler NICHT
    // die Zahlungsverarbeitung selbst betrifft — hier lassen wir echte
    // Fehler durchschlagen, damit PayPal es später erneut zustellt.
    return json(res, { ok: false, error: "Webhook-Verarbeitung fehlgeschlagen." }, 500);
  }

  return json(res, { ok: true });
}
