/* =====================================================================
   lib/paypal.js — PayPal Subscriptions API Anbindung (Supporter-Abo).
   1:1 portiert aus cloudflare-worker/src/lib/paypal.js, env.X → process.env.X.

   WICHTIG — offener Punkt (wie im Original): vollständig fertig
   implementiert (OAuth2, Subscription erstellen, Subscription abfragen,
   Webhook-Signatur prüfen), kann aber erst wirklich Geld verarbeiten,
   sobald folgende echten Werte in der .env gesetzt sind:
     PAYPAL_CLIENT_ID
     PAYPAL_CLIENT_SECRET
     PAYPAL_PLAN_ID        (aus dem PayPal-Business-Konto)
     PAYPAL_WEBHOOK_ID
     PAYPAL_ENV = "sandbox" | "live"   (unkritisch, kein Secret)
   Diese Werte kann NUR der Website-Betreiber selbst erzeugen (PayPal-
   Business-Konto + Developer-App sind an seine eigene Identität/Bank
   gebunden) — deshalb "fail closed" mit sprechendem Fehler statt eines
   Absturzes, solange sie fehlen. NIEMALS in .env.example oder Vault!
   ===================================================================== */

export class PayPalNotConfiguredError extends Error {
  constructor() {
    super("PayPal ist noch nicht eingerichtet (PAYPAL_CLIENT_ID/SECRET/PLAN_ID fehlen).");
    this.name = "PayPalNotConfiguredError";
    this.code = "PAYPAL_NOT_CONFIGURED";
  }
}

function baseUrl() {
  return process.env.PAYPAL_ENV === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com";
}

function assertConfigured() {
  if (!process.env.PAYPAL_CLIENT_ID || !process.env.PAYPAL_CLIENT_SECRET || !process.env.PAYPAL_PLAN_ID) {
    throw new PayPalNotConfiguredError();
  }
}

let cachedToken = null; // { value, expiresAt } — pro Serverprozess, spart Requests

async function getAccessToken() {
  assertConfigured();
  if (cachedToken && cachedToken.expiresAt > Date.now() + 30_000) return cachedToken.value;

  const res = await fetch(`${baseUrl()}/v1/oauth2/token`, {
    method: "POST",
    headers: {
      Authorization: `Basic ${btoa(`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`)}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: "grant_type=client_credentials",
  });
  if (!res.ok) throw new Error(`PayPal-OAuth fehlgeschlagen (${res.status}): ${await res.text().catch(() => "")}`);
  const data = await res.json();
  cachedToken = { value: data.access_token, expiresAt: Date.now() + (data.expires_in || 3600) * 1000 };
  return cachedToken.value;
}

async function paypalFetch(path, options = {}) {
  const token = await getAccessToken();
  const res = await fetch(`${baseUrl()}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
      ...(options.headers || {}),
    },
  });
  const text = await res.text();
  const data = text ? JSON.parse(text) : null;
  if (!res.ok) {
    const err = new Error(`PayPal-Anfrage fehlgeschlagen (${res.status}): ${text.slice(0, 400)}`);
    err.paypalStatus = res.status;
    err.paypalBody = data;
    throw err;
  }
  return data;
}

/**
 * Erstellt ein PayPal-Abonnement für einen Supporter und liefert den
 * "approve"-Link zurück, zu dem der Nutzer weitergeleitet werden muss, um
 * das Abo direkt bei PayPal zu bestätigen. custom_id trägt unsere interne
 * supporter_id, damit der Webhook später weiß, wem die Zahlung gehört.
 */
export async function createSubscription({ supporterId, returnUrl, cancelUrl }) {
  const data = await paypalFetch("/v1/billing/subscriptions", {
    method: "POST",
    headers: { "PayPal-Request-Id": `sub-${supporterId}-${Date.now()}` },
    body: JSON.stringify({
      plan_id: process.env.PAYPAL_PLAN_ID,
      custom_id: supporterId,
      application_context: {
        brand_name: "Dogfather",
        locale: "de-DE",
        shipping_preference: "NO_SHIPPING",
        user_action: "SUBSCRIBE_NOW",
        return_url: returnUrl,
        cancel_url: cancelUrl,
      },
    }),
  });
  const approveLink = (data.links || []).find((l) => l.rel === "approve");
  return { paypalSubscriptionId: data.id, approveUrl: approveLink?.href || null, raw: data };
}

export async function getSubscription(paypalSubscriptionId) {
  return paypalFetch(`/v1/billing/subscriptions/${encodeURIComponent(paypalSubscriptionId)}`);
}

/** Kündigung — Zugang bleibt bis Periodenende bestehen. */
export async function cancelSubscription(paypalSubscriptionId, reason) {
  await paypalFetch(`/v1/billing/subscriptions/${encodeURIComponent(paypalSubscriptionId)}/cancel`, {
    method: "POST",
    body: JSON.stringify({ reason: reason || "Kündigung durch Supporter" }),
  });
  return true;
}

/**
 * Prüft die Echtheit eines eingehenden Webhooks über PayPals offizielle
 * Verify-Signature-Funktion. `req` ist ein Express-Request (Header über
 * req.headers Objekt, nicht .get()/.headers.get() wie bei Cloudflare).
 */
export async function verifyWebhookSignature(req, rawBody) {
  assertConfigured();
  if (!process.env.PAYPAL_WEBHOOK_ID) throw new PayPalNotConfiguredError();

  const h = req.headers;
  const verification = await paypalFetch("/v1/notifications/verify-webhook-signature", {
    method: "POST",
    body: JSON.stringify({
      auth_algo: h["paypal-auth-algo"],
      cert_url: h["paypal-cert-url"],
      transmission_id: h["paypal-transmission-id"],
      transmission_sig: h["paypal-transmission-sig"],
      transmission_time: h["paypal-transmission-time"],
      webhook_id: process.env.PAYPAL_WEBHOOK_ID,
      webhook_event: JSON.parse(rawBody),
    }),
  });
  return verification.verification_status === "SUCCESS";
}
