/* =====================================================================
   routes/supporter.js — Öffentlicher Supporter-Bereich: Registrierung,
   E-Mail-Bestätigung, Magic-Link-Login, persönliches Dashboard, Prämien-
   Einlösung, Aboverwaltung.

   Setzt Dogfather_VanVan_Supporter_Abo.odt Abschnitt 5, 6, 9-18, 21-25 um.
   ===================================================================== */
import { generateId, nowIso } from "../lib/crypto.js";
import { json } from "../lib/http.js";
import { logAction } from "../lib/audit.js";
import {
  normalizeEmail, normalizeTikTokUsername, issueAuthCode, verifyAuthCode,
  createSupporterSession, endSupporterSession,
} from "../lib/supporter-auth.js";
import { EmailNotConfiguredError, sendVerifyEmailCode, sendLoginCode } from "../lib/supporter-mail.js";
import { computeProgress, timelineForCurrentCycle, MILESTONES } from "../lib/supporter-cycle.js";
import { verifyGoogleIdToken, GoogleAuthNotConfiguredError } from "../lib/google-auth.js";

function bad(msg, status = 400) {
  return json({ ok: false, error: msg }, status);
}

function emailErrorResponse(err) {
  if (err instanceof EmailNotConfiguredError || err.code === "EMAIL_NOT_CONFIGURED") {
    return json(
      {
        ok: false,
        error:
          "Der E-Mail-Versand ist auf dieser Website noch nicht eingerichtet (RESEND_API_KEY fehlt). " +
          "Bitte zuerst im Worker-Setup konfigurieren, siehe cloudflare-worker/README.md.",
        code: "EMAIL_NOT_CONFIGURED",
      },
      503
    );
  }
  return json({ ok: false, error: "E-Mail konnte nicht verschickt werden." }, 502);
}

/* ---------- Registrierung & Login (öffentlich, keine Session) ---------- */

export async function registerSupporter(request, env) {
  const body = await request.json().catch(() => ({}));
  const displayName = String(body.displayName || "").trim().slice(0, 60);
  const tiktokUsername = normalizeTikTokUsername(body.tiktokUsername);
  const email = normalizeEmail(body.email);

  if (!displayName) return bad("Bitte gib einen Namen oder Anzeigenamen an.");
  if (!email || !email.includes("@")) return bad("Bitte gib eine gültige E-Mail-Adresse an.");

  const existing = await env.DB.prepare(`SELECT id, email_verified FROM supporter_users WHERE email = ?`)
    .bind(email)
    .first();

  let supporterId;
  if (existing) {
    if (existing.email_verified) {
      return bad("Für diese E-Mail-Adresse existiert bereits ein Supporter-Konto. Bitte einloggen.", 409);
    }
    supporterId = existing.id;
    await env.DB.prepare(`UPDATE supporter_users SET display_name = ?, tiktok_username = ? WHERE id = ?`)
      .bind(displayName, tiktokUsername, supporterId)
      .run();
  } else {
    supporterId = generateId();
    await env.DB.prepare(
      `INSERT INTO supporter_users (id, display_name, tiktok_username, email, email_verified, created_at)
       VALUES (?, ?, ?, ?, 0, ?)`
    )
      .bind(supporterId, displayName, tiktokUsername, email, nowIso())
      .run();
    await logAction(env, "system", "supporter.registered", supporterId, { email });
  }

  const code = await issueAuthCode(env, supporterId, "verify_email");
  try {
    await sendVerifyEmailCode(env, { to: email, name: displayName, code });
  } catch (err) {
    return emailErrorResponse(err);
  }
  return json({ ok: true, needsVerification: true });
}

export async function verifySupporterEmail(request, env) {
  const body = await request.json().catch(() => ({}));
  const email = normalizeEmail(body.email);
  const code = String(body.code || "").trim();
  if (!email || !code) return bad("E-Mail und Code werden benötigt.");

  const supporter = await env.DB.prepare(`SELECT id, display_name FROM supporter_users WHERE email = ?`).bind(email).first();
  if (!supporter) return bad("Kein Supporter-Konto mit dieser E-Mail-Adresse gefunden.", 404);

  const valid = await verifyAuthCode(env, supporter.id, "verify_email", code);
  if (!valid) return bad("Der Code ist ungültig oder abgelaufen.", 401);

  await env.DB.prepare(`UPDATE supporter_users SET email_verified = 1, last_login_at = ? WHERE id = ?`)
    .bind(nowIso(), supporter.id)
    .run();
  await logAction(env, "system", "supporter.email_verified", supporter.id, null);

  const token = await createSupporterSession(env, supporter.id);
  return json({ ok: true, token });
}

export async function requestSupporterLogin(request, env) {
  const body = await request.json().catch(() => ({}));
  const email = normalizeEmail(body.email);
  if (!email) return bad("Bitte gib deine E-Mail-Adresse an.");

  const supporter = await env.DB.prepare(
    `SELECT id, display_name, email_verified FROM supporter_users WHERE email = ? AND status = 'active'`
  )
    .bind(email)
    .first();
  // Bewusst IMMER ok:true nach außen (keine Auskunft, ob ein Konto existiert).
  if (!supporter || !supporter.email_verified) return json({ ok: true });

  const code = await issueAuthCode(env, supporter.id, "login");
  try {
    await sendLoginCode(env, { to: email, name: supporter.display_name, code });
  } catch (err) {
    return emailErrorResponse(err);
  }
  return json({ ok: true });
}

export async function verifySupporterLogin(request, env) {
  const body = await request.json().catch(() => ({}));
  const email = normalizeEmail(body.email);
  const code = String(body.code || "").trim();
  if (!email || !code) return bad("E-Mail und Code werden benötigt.");

  const supporter = await env.DB.prepare(`SELECT id FROM supporter_users WHERE email = ? AND status = 'active'`).bind(email).first();
  if (!supporter) return bad("Kein Supporter-Konto gefunden.", 404);

  const valid = await verifyAuthCode(env, supporter.id, "login", code);
  if (!valid) return bad("Der Code ist ungültig oder abgelaufen.", 401);

  await env.DB.prepare(`UPDATE supporter_users SET last_login_at = ? WHERE id = ?`).bind(nowIso(), supporter.id).run();
  const token = await createSupporterSession(env, supporter.id);
  return json({ ok: true, token });
}

/**
 * "Mit Google anmelden" — deckt Registrierung UND Login gleichzeitig ab:
 * Google hat die E-Mail-Adresse bereits selbst bestätigt, deshalb entfällt
 * hier der sonst nötige Bestätigungscode. Existiert schon ein Konto mit
 * dieser E-Mail (egal ob per Code oder vorher schon per Google angelegt),
 * wird es einfach eingeloggt statt doppelt erzeugt.
 */
export async function googleLoginSupporter(request, env) {
  const body = await request.json().catch(() => ({}));
  let result;
  try {
    result = await verifyGoogleIdToken(env, body.credential);
  } catch (err) {
    if (err instanceof GoogleAuthNotConfiguredError) {
      return json(
        {
          ok: false,
          code: "GOOGLE_AUTH_NOT_CONFIGURED",
          error: "Google-Login ist auf dieser Website noch nicht eingerichtet.",
        },
        503
      );
    }
    return json({ ok: false, error: "Google-Anmeldung fehlgeschlagen." }, 502);
  }
  if (!result.ok) return bad(result.reason || "Google-Anmeldung fehlgeschlagen.", 401);

  let supporter = await env.DB.prepare(`SELECT id FROM supporter_users WHERE email = ? AND status = 'active'`)
    .bind(result.email)
    .first();

  if (!supporter) {
    const id = generateId();
    await env.DB.prepare(
      `INSERT INTO supporter_users (id, display_name, email, email_verified, created_at, last_login_at)
       VALUES (?, ?, ?, 1, ?, ?)`
    )
      .bind(id, result.name, result.email, nowIso(), nowIso())
      .run();
    supporter = { id };
    await logAction(env, "system", "supporter.registered_via_google", supporter.id, { email: result.email });
  } else {
    await env.DB.prepare(`UPDATE supporter_users SET last_login_at = ? WHERE id = ?`).bind(nowIso(), supporter.id).run();
  }

  const token = await createSupporterSession(env, supporter.id);
  return json({ ok: true, token });
}

/**
 * Öffentliche, NICHT geheime Konfiguration fürs Frontend (Client-IDs sind
 * per Design öffentlich — nur die zugehörigen Secrets sind es nicht).
 * Fehlt ein Wert, bleibt der jeweilige Button im Frontend einfach
 * ausgeblendet statt kaputt zu sein.
 */
export async function getPublicConfig(request, env) {
  return json({
    ok: true,
    googleClientId: env.GOOGLE_CLIENT_ID || null,
    paypalClientId: env.PAYPAL_CLIENT_ID || null,
    paypalPlanId: env.PAYPAL_PLAN_ID || null,
  });
}

export async function logoutSupporter(request, env) {
  const auth = request.headers.get("Authorization") || "";
  const token = auth.startsWith("Bearer ") ? auth.slice(7) : null;
  await endSupporterSession(env, token);
  return json({ ok: true });
}

/* ---------- Geschützter Supporter-Bereich (Session erforderlich) ---------- */

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",
};

/** Baut die vollständige Dashboard-Antwort (Fenster 1+2+Archiv, Abschnitt 10/15). */
async function buildDashboard(env, supporter) {
  const sub = await env.DB.prepare(
    `SELECT * FROM supporter_subscriptions WHERE supporter_id = ? ORDER BY created_at DESC LIMIT 1`
  )
    .bind(supporter.id)
    .first();

  const totalPaidMonths = sub?.total_paid_months || 0;
  const progress = computeProgress(totalPaidMonths);
  const timeline = timelineForCurrentCycle(totalPaidMonths);

  const premiumsRes = await env.DB.prepare(
    `SELECT p.*, c.name as collection_name, c.collection_number
     FROM supporter_premiums p JOIN supporter_collections c ON c.id = p.collection_id
     WHERE p.supporter_id = ? ORDER BY p.cycle_number ASC, p.milestone_month ASC`
  )
    .bind(supporter.id)
    .all();
  const premiums = (premiumsRes.results || []).map((p) => ({ ...p, label: REDEEM_LABELS[p.premium_type] || p.premium_type }));

  // Archiv: nach Kollektion gruppiert (Abschnitt 15 — abgeschlossene Kollektionen bleiben sichtbar).
  const byCollection = new Map();
  for (const p of premiums) {
    const key = p.collection_number;
    if (!byCollection.has(key)) byCollection.set(key, { collectionNumber: key, collectionName: p.collection_name, premiums: [] });
    byCollection.get(key).premiums.push(p);
  }

  // Zahlungshistorie (Nutzer-Wunsch 04.08.2026: "die Kisten sollen beim
  // Klicken viel mehr Infos zeigen") — für die Detailansicht "Nächste
  // Zahlung" im Frontend. sub kann null sein (noch nie ein Abo gehabt).
  let payments = [];
  if (sub) {
    const paymentsRes = await env.DB.prepare(
      `SELECT amount, currency, payment_status, paid_at, is_sandbox
       FROM supporter_payments WHERE supporter_id = ? ORDER BY paid_at DESC LIMIT 36`
    )
      .bind(supporter.id)
      .all();
    payments = paymentsRes.results || [];
  }

  return {
    profile: {
      displayName: supporter.display_name,
      tiktokUsername: supporter.tiktok_username,
      email: supporter.email,
      hasShippingAddress: !!(supporter.ship_street && supporter.ship_zip),
    },
    subscription: sub
      ? {
          status: sub.status,
          startedAt: sub.started_at,
          nextPaymentAt: sub.next_payment_at,
          paidPeriodEndAt: sub.paid_period_end_at,
          cancelledAt: sub.cancelled_at,
          monthlyPrice: "4,99 €",
          totalPaidMonths,
        }
      : { status: "none", totalPaidMonths: 0, monthlyPrice: "4,99 €" },
    progress,
    timeline,
    premiums,
    archive: [...byCollection.values()].sort((a, b) => a.collectionNumber - b.collectionNumber),
    payments,
  };
}

export async function getSupporterMe(request, env, supporter) {
  return json({ ok: true, ...(await buildDashboard(env, supporter)) });
}

export async function updateSupporterProfile(request, env, supporter) {
  const body = await request.json().catch(() => ({}));
  const displayName = body.displayName !== undefined ? String(body.displayName).trim().slice(0, 60) : supporter.display_name;
  const tiktokUsername =
    body.tiktokUsername !== undefined ? normalizeTikTokUsername(body.tiktokUsername) : supporter.tiktok_username;
  if (!displayName) return bad("Der Name darf nicht leer sein.");

  await env.DB.prepare(`UPDATE supporter_users SET display_name = ?, tiktok_username = ? WHERE id = ?`)
    .bind(displayName, tiktokUsername, supporter.id)
    .run();
  await logAction(env, "system", "supporter.profile_updated", supporter.id, null);
  return json({ ok: true });
}

export async function requestSupporterEmailChange(request, env, supporter) {
  const body = await request.json().catch(() => ({}));
  const newEmail = normalizeEmail(body.newEmail);
  if (!newEmail || !newEmail.includes("@")) return bad("Bitte gib eine gültige neue E-Mail-Adresse an.");

  const clash = await env.DB.prepare(`SELECT id FROM supporter_users WHERE email = ? AND id != ?`)
    .bind(newEmail, supporter.id)
    .first();
  if (clash) return bad("Diese E-Mail-Adresse wird bereits verwendet.", 409);

  await env.DB.prepare(`UPDATE supporter_users SET pending_email = ? WHERE id = ?`).bind(newEmail, supporter.id).run();
  const code = await issueAuthCode(env, supporter.id, "change_email");
  try {
    await sendVerifyEmailCode(env, { to: newEmail, name: supporter.display_name, code });
  } catch (err) {
    return emailErrorResponse(err);
  }
  return json({ ok: true });
}

export async function confirmSupporterEmailChange(request, env, supporter) {
  const body = await request.json().catch(() => ({}));
  const code = String(body.code || "").trim();
  if (!supporter.pending_email) return bad("Es liegt keine E-Mail-Änderung vor.");
  const valid = await verifyAuthCode(env, supporter.id, "change_email", code);
  if (!valid) return bad("Der Code ist ungültig oder abgelaufen.", 401);

  await env.DB.prepare(`UPDATE supporter_users SET email = pending_email, pending_email = NULL WHERE id = ?`)
    .bind(supporter.id)
    .run();
  await logAction(env, "system", "supporter.email_changed", supporter.id, null);
  return json({ ok: true });
}

/** Prämie einlösen (Abschnitt 17) — Versandadresse erst hier, nicht bei der Registrierung. */
export async function redeemPremium(request, env, supporter) {
  const body = await request.json().catch(() => ({}));
  const premium = await env.DB.prepare(`SELECT * FROM supporter_premiums WHERE id = ? AND supporter_id = ?`)
    .bind(body.premiumId, supporter.id)
    .first();
  if (!premium) return bad("Prämie nicht gefunden.", 404);
  if (premium.redeem_status !== "verfuegbar") return bad("Diese Prämie wurde bereits eingelöst oder ist gesperrt.", 409);

  const useSaved = !!body.useSavedAddress && supporter.ship_street;
  const addr = useSaved
    ? {
        name: supporter.ship_name, street: supporter.ship_street, zip: supporter.ship_zip,
        city: supporter.ship_city, country: supporter.ship_country,
      }
    : {
        name: String(body.shipName || "").trim(),
        street: String(body.shipStreet || "").trim(),
        zip: String(body.shipZip || "").trim(),
        city: String(body.shipCity || "").trim(),
        country: String(body.shipCountry || "").trim(),
      };
  if (!addr.name || !addr.street || !addr.zip || !addr.city || !addr.country) {
    return bad("Bitte fülle alle Adressfelder aus.");
  }

  if (premium.premium_type === "hoodie" && !body.hoodieSize) {
    return bad("Bitte wähle eine Hoodie-Größe.");
  }

  await env.DB.prepare(
    `UPDATE supporter_premiums SET redeem_status = 'eingeloest', redeemed_at = ?,
       ship_name = ?, ship_street = ?, ship_zip = ?, ship_city = ?, ship_country = ?,
       hoodie_size = ?, hoodie_variant = ?
     WHERE id = ?`
  )
    .bind(
      nowIso(), addr.name, addr.street, addr.zip, addr.city, addr.country,
      body.hoodieSize || null, body.hoodieVariant || null, premium.id
    )
    .run();

  if (body.saveAddress) {
    await env.DB.prepare(
      `UPDATE supporter_users SET ship_name=?, ship_street=?, ship_zip=?, ship_city=?, ship_country=?, ship_save_address=1 WHERE id=?`
    )
      .bind(addr.name, addr.street, addr.zip, addr.city, addr.country, supporter.id)
      .run();
  }

  await logAction(env, "system", "supporter.premium_redeemed", supporter.id, { premiumId: premium.id, type: premium.premium_type });
  return json({ ok: true });
}

export async function deleteSupporterAccount(request, env, supporter) {
  // Datenschutz (Abschnitt 30): Konto löschbar. Vertrags-/Zahlungsdaten
  // bleiben aus gesetzlichen Aufbewahrungspflichten bestehen (nur an den
  // Nutzerdatensatz selbst wird nicht mehr referenziert) — deshalb hier
  // bewusst "soft delete" statt hartem DELETE der Zahlungshistorie.
  await env.DB.prepare(`UPDATE supporter_users SET status = 'deleted', email = email || '.geloescht.' || id WHERE id = ?`)
    .bind(supporter.id)
    .run();
  await logAction(env, "system", "supporter.account_deleted", supporter.id, null);
  return json({ ok: true });
}
