/* =====================================================================
   DOGFATHER UNIVERSE — Interner Bereich Worker

   Setzt "Geschuetzter_Zugang_Rollen_Benutzerverwaltung.pdf" um:
   Session-basierte Anmeldung, verschlüsselte (reversible) Code-Speicherung
   NUR für den Owner einsehbar, vollständig konfigurierbare Rollen mit
   granularen Rechten, individuelle Personen-Ausnahmen, Aktivitätsprotokoll,
   Login-Lockout nach Fehlversuchen.

   Architektur:
     src/lib/crypto.js        Hashing, AES-GCM (Code-Verschlüsselung), IDs
     src/lib/auth.js          Login-Prüfung, Sessions, Rate-Limiting
     src/lib/permissions.js   Vollständiger Permission-Katalog
     src/lib/audit.js         Aktivitätsprotokoll schreiben
     src/lib/http.js          CORS/JSON-Helfer
     src/routes/*.js          Endpoint-Handler pro Bereich

   Bindings (siehe wrangler.toml):
     D1:      DB               (Datenbank für Users/Rollen/Sessions/Audit/Bewerbungen)
     KV:      BEWERBUNGEN      (nur noch für den Live-Status-Schalter genutzt)
     Secret:  POSTFACH_CODE    (Owner-Code, mit `wrangler secret put` gesetzt)
     Secret:  ENCRYPTION_KEY   (AES-256-Schlüssel, base64, für Code-Verschlüsselung)
   ===================================================================== */
import { json, corsHeaders } from "./lib/http.js";
import { requireSession } from "./lib/auth.js";
import { login, logout, setOwnerCode } from "./routes/auth.js";
import {
  listRoles, createRole, updateRole, deleteRole,
} from "./routes/roles.js";
import {
  listUsers, prepareUser, finalizeUser, setUserCode, revealUserCode,
  lockUser, unlockUser, deleteUser, setUserRole, setUserPermissions,
  endUserSessions, updateUserMeta,
} from "./routes/users.js";
import {
  submitApplication, listApplications, changeApplicationStatus,
  takeOverApplication, assignApplication, archiveApplication,
  restoreApplication, deleteApplication, markAnswered,
  listNotes, addNote, deleteNote, listDrafts, saveDraft,
} from "./routes/applications.js";
import { listAuditLog } from "./routes/audit.js";
import { checkTikTokLive } from "./lib/tiktok-live.js";
import { logAction } from "./lib/audit.js";
import { requireSupporterSession } from "./lib/supporter-auth.js";
import {
  registerSupporter, verifySupporterEmail, requestSupporterLogin, verifySupporterLogin, logoutSupporter,
  googleLoginSupporter, getPublicConfig,
  getSupporterMe, updateSupporterProfile, requestSupporterEmailChange, confirmSupporterEmailChange,
  redeemPremium, deleteSupporterAccount,
} from "./routes/supporter.js";
import {
  startPaypalSubscription, confirmPaypalSubscription, cancelPaypalSubscription, handlePaypalWebhook,
} from "./routes/supporter-paypal.js";
import {
  listSupporters, getSupporterDetail, updatePremium, listCollections, upsertCollection, getAdminOverview,
} from "./routes/supporter-admin.js";

/* =====================================================================
   LIVE-STATUS

   Der grüne Punkt auf der Startseite läuft automatisch: ein Cron-Auftrag
   fragt jede Minute bei TikTok nach, ob @dogfather0804 gerade sendet, und
   schreibt das Ergebnis in die Tabelle app_settings.

   Zwei Schlüssel:
     live_auto     – letzter ERFOLGREICH ermittelter Stand (wird bei einem
                     fehlgeschlagenen Abruf bewusst NICHT überschrieben,
                     damit ein TikTok-Aussetzer den Punkt nicht fälschlich
                     auf offline zieht)
     live_override – manueller Vorrang aus dem Postfach, mit Ablaufzeit.
                     Solange er gilt, schlägt er die Automatik.
   ===================================================================== */

const DEFAULT_OVERRIDE_MINUTES = 180;

async function readSetting(env, key) {
  const row = await env.DB.prepare(`SELECT value FROM app_settings WHERE key = ?`)
    .bind(key)
    .first()
    .catch(() => null);
  if (!row || !row.value) return null;
  try {
    return JSON.parse(row.value);
  } catch {
    return null;
  }
}

async function writeSetting(env, key, value) {
  await env.DB.prepare(
    `INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, ?)
     ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
  )
    .bind(key, JSON.stringify(value), new Date().toISOString())
    .run();
}

/** Ermittelt den Stand, der nach außen gilt (Vorrang: manuell vor Automatik). */
async function resolveLiveStatus(env) {
  const auto = (await readSetting(env, "live_auto")) || {};
  const override = await readSetting(env, "live_override");

  const overrideActive =
    override && override.until && new Date(override.until).getTime() > Date.now();

  if (overrideActive) {
    return {
      live: !!override.live,
      source: "manuell",
      updatedAt: override.setAt || null,
      overrideUntil: override.until,
      autoLive: auto.live ?? null,
      autoCheckedAt: auto.checkedAt || null,
      autoHealthy: auto.ok !== false,
    };
  }

  return {
    live: !!auto.live,
    source: "automatisch",
    updatedAt: auto.changedAt || auto.checkedAt || null,
    title: auto.title || null,
    autoLive: auto.live ?? null,
    autoCheckedAt: auto.checkedAt || null,
    autoHealthy: auto.ok !== false,
  };
}

async function handleLiveStatusGet(env) {
  const state = await resolveLiveStatus(env);
  return json({ ok: true, ...state });
}

/**
 * Manueller Eingriff aus dem Postfach.
 *   { live: true|false, minutes?: number }  → Vorrang setzen
 *   { auto: true }                          → Vorrang aufheben, Automatik übernimmt
 */
async function handleLiveStatusSet(request, env, session) {
  if (!session?.isOwner) return json({ ok: false, error: "Nur Dogi darf den Live-Status ändern." }, 403);
  const body = await request.json().catch(() => ({}));

  if (body.auto === true) {
    await env.DB.prepare(`DELETE FROM app_settings WHERE key = 'live_override'`).run();
    await logAction(env, "owner", "live.override_cleared", null, null);
    return json({ ok: true, ...(await resolveLiveStatus(env)) });
  }

  const minutes = Number.isFinite(body.minutes) ? Math.min(Math.max(body.minutes, 5), 1440) : DEFAULT_OVERRIDE_MINUTES;
  const now = new Date();
  await writeSetting(env, "live_override", {
    live: !!body.live,
    setAt: now.toISOString(),
    until: new Date(now.getTime() + minutes * 60_000).toISOString(),
  });
  await logAction(env, "owner", "live.override_set", null, { live: !!body.live, minutes });
  return json({ ok: true, ...(await resolveLiveStatus(env)) });
}

/** Wird vom Cron-Auftrag aufgerufen — fragt TikTok ab und schreibt das Ergebnis. */
async function refreshLiveFromTikTok(env) {
  const username = env.TIKTOK_USERNAME || "dogfather0804";
  const result = await checkTikTokLive(username);
  const now = new Date().toISOString();

  if (!result.ok) {
    // Fehlgeschlagen: letzten guten Stand behalten, nur den Fehler vermerken.
    const previous = (await readSetting(env, "live_auto")) || {};
    await writeSetting(env, "live_auto", {
      ...previous,
      ok: false,
      lastError: result.reason,
      checkedAt: now,
    });
    return { ok: false, reason: result.reason };
  }

  const previous = (await readSetting(env, "live_auto")) || {};
  const changed = previous.live !== result.live;

  await writeSetting(env, "live_auto", {
    ok: true,
    live: result.live,
    title: result.title,
    roomId: result.roomId,
    rawStatus: result.rawStatus,
    checkedAt: now,
    changedAt: changed ? now : previous.changedAt || now,
    lastError: null,
  });

  if (changed) {
    await logAction(env, "system", result.live ? "live.started" : "live.ended", null, {
      title: result.title,
    });
  }
  return { ok: true, live: result.live, changed };
}

// Routen, die eine gültige Session brauchen. `session` wird injiziert.
const AUTHED_ROUTES = {
  "POST /auth/logout": (req, env) => logout(req, env),
  "POST /admin/owner/set-code": (req, env, session) => setOwnerCode(req, env, session),
  "POST /live-status/set": (req, env, session) => handleLiveStatusSet(req, env, session),

  "POST /admin/applications/list": (req, env, session) => listApplications(req, env, session),
  "POST /admin/applications/status": (req, env, session) => changeApplicationStatus(req, env, session),
  "POST /admin/applications/take-over": (req, env, session) => takeOverApplication(req, env, session),
  "POST /admin/applications/assign": (req, env, session) => assignApplication(req, env, session),
  "POST /admin/applications/archive": (req, env, session) => archiveApplication(req, env, session),
  "POST /admin/applications/restore": (req, env, session) => restoreApplication(req, env, session),
  "POST /admin/applications/delete": (req, env, session) => deleteApplication(req, env, session),
  "POST /admin/applications/mark-answered": (req, env, session) => markAnswered(req, env, session),

  "POST /admin/notes/list": (req, env, session) => listNotes(req, env, session),
  "POST /admin/notes/add": (req, env, session) => addNote(req, env, session),
  "POST /admin/notes/delete": (req, env, session) => deleteNote(req, env, session),

  "POST /admin/drafts/list": (req, env, session) => listDrafts(req, env, session),
  "POST /admin/drafts/save": (req, env, session) => saveDraft(req, env, session),

  "POST /admin/roles/list": (req, env, session) => listRoles(env, session),
  "POST /admin/roles/create": (req, env, session) => createRole(req, env, session),
  "POST /admin/roles/update": (req, env, session) => updateRole(req, env, session),
  "POST /admin/roles/delete": (req, env, session) => deleteRole(req, env, session),

  "POST /admin/users/list": (req, env, session) => listUsers(env, session),
  "POST /admin/users/prepare": (req, env, session) => prepareUser(req, env, session),
  "POST /admin/users/finalize": (req, env, session) => finalizeUser(req, env, session),
  "POST /admin/users/set-code": (req, env, session) => setUserCode(req, env, session),
  "POST /admin/users/reveal-code": (req, env, session) => revealUserCode(req, env, session),
  "POST /admin/users/lock": (req, env, session) => lockUser(req, env, session),
  "POST /admin/users/unlock": (req, env, session) => unlockUser(req, env, session),
  "POST /admin/users/delete": (req, env, session) => deleteUser(req, env, session),
  "POST /admin/users/set-role": (req, env, session) => setUserRole(req, env, session),
  "POST /admin/users/set-permissions": (req, env, session) => setUserPermissions(req, env, session),
  "POST /admin/users/end-sessions": (req, env, session) => endUserSessions(req, env, session),
  "POST /admin/users/update-meta": (req, env, session) => updateUserMeta(req, env, session),

  "POST /admin/audit/list": (req, env, session) => listAuditLog(req, env, session),

  // Supporter-Abo — Administrationsbereich (interne Team-Session + eigene
  // SUPPORTER_*-Rechte, siehe permissions.js).
  "POST /admin/overview": (req, env, session) => getAdminOverview(req, env, session),
  "POST /admin/supporter/list": (req, env, session) => listSupporters(req, env, session),
  "POST /admin/supporter/detail": (req, env, session) => getSupporterDetail(req, env, session),
  "POST /admin/supporter/premium/update": (req, env, session) => updatePremium(req, env, session),
  "POST /admin/supporter/collections/list": (req, env, session) => listCollections(req, env, session),
  "POST /admin/supporter/collections/upsert": (req, env, session) => upsertCollection(req, env, session),
};

// Routen des ÖFFENTLICHEN Supporter-Bereichs, die eine gültige
// Supporter-Session brauchen (eigenes, von AUTHED_ROUTES komplett
// getrenntes Token-System — siehe lib/supporter-auth.js). `supporter`
// (die volle supporter_users-Zeile) wird injiziert statt `session`.
const SUPPORTER_AUTHED_ROUTES = {
  "POST /supporter/logout": (req, env) => logoutSupporter(req, env),
  "POST /supporter/me": (req, env, supporter) => getSupporterMe(req, env, supporter),
  "POST /supporter/update-profile": (req, env, supporter) => updateSupporterProfile(req, env, supporter),
  "POST /supporter/request-email-change": (req, env, supporter) => requestSupporterEmailChange(req, env, supporter),
  "POST /supporter/confirm-email-change": (req, env, supporter) => confirmSupporterEmailChange(req, env, supporter),
  "POST /supporter/redeem-premium": (req, env, supporter) => redeemPremium(req, env, supporter),
  "POST /supporter/delete-account": (req, env, supporter) => deleteSupporterAccount(req, env, supporter),
  "POST /supporter/paypal/start": (req, env, supporter) => startPaypalSubscription(req, env, supporter),
  "POST /supporter/paypal/confirm": (req, env, supporter) => confirmPaypalSubscription(req, env, supporter),
  "POST /supporter/paypal/cancel": (req, env, supporter) => cancelPaypalSubscription(req, env, supporter),
};

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const routeKey = `${request.method} ${url.pathname}`;

    if (request.method === "OPTIONS") {
      return new Response(null, { headers: corsHeaders() });
    }

    if (url.pathname === "/health" && request.method === "GET") {
      return json({ ok: true, service: "dogfather-universe-interner-bereich" });
    }
    if (url.pathname === "/submit" && request.method === "POST") {
      return submitApplication(request, env, ctx);
    }
    if (url.pathname === "/live-status" && request.method === "GET") {
      return handleLiveStatusGet(env);
    }
    if (url.pathname === "/auth/login" && request.method === "POST") {
      return login(request, env);
    }

    // Supporter-Abo — öffentliche Routen ohne Session (Registrierung/Login).
    if (url.pathname === "/supporter/register" && request.method === "POST") return registerSupporter(request, env);
    if (url.pathname === "/supporter/verify-email" && request.method === "POST") return verifySupporterEmail(request, env);
    if (url.pathname === "/supporter/login-request" && request.method === "POST") return requestSupporterLogin(request, env);
    if (url.pathname === "/supporter/verify-login" && request.method === "POST") return verifySupporterLogin(request, env);
    if (url.pathname === "/supporter/google-login" && request.method === "POST") return googleLoginSupporter(request, env);
    if (url.pathname === "/supporter/public-config" && request.method === "GET") return getPublicConfig(request, env);
    // PayPal ruft diese Adresse direkt auf — eigene Signaturprüfung statt Session (siehe paypal.js).
    if (url.pathname === "/supporter/paypal-webhook" && request.method === "POST") return handlePaypalWebhook(request, env);

    const supporterHandler = SUPPORTER_AUTHED_ROUTES[routeKey];
    if (supporterHandler) {
      const supporter = await requireSupporterSession(request, env);
      if (!supporter) return json({ ok: false, error: "Sitzung abgelaufen oder ungültig. Bitte erneut einloggen." }, 401);
      return supporterHandler(request, env, supporter);
    }

    const handler = AUTHED_ROUTES[routeKey];
    if (handler) {
      const session = await requireSession(request, env);
      if (!session) return json({ ok: false, error: "Sitzung abgelaufen oder ungültig. Bitte erneut anmelden." }, 401);
      return handler(request, env, session);
    }

    return json({ ok: false, error: "Not found." }, 404);
  },

  /* Cron-Auftrag: läuft jede Minute und hält den Live-Punkt aktuell. */
  async scheduled(event, env, ctx) {
    ctx.waitUntil(refreshLiveFromTikTok(env));
  },
};
