/* =====================================================================
   DOGFATHER UNIVERSE — Bewerbungs-Postfach Worker
   Nimmt Bewerbungen von bewerben.html entgegen, speichert sie in KV,
   und gibt sie nur nach korrektem Code (serverseitig geprüft) an
   postfach.html zurück.

   Zugriffs-Modell (wichtig für's Verständnis):
     - Owner-Code (POSTFACH_CODE, Secret) = Dogi selbst. Sieht alle
       Bewerbungen UND kann Helfer-Codes mit Rollen (z.B. "Rechte Hand",
       "Linke Hand") anlegen/entziehen.
     - Helfer-Codes (von Dogi vergeben) = sehen NUR die Bewerbungen.
       Sie können KEINE neuen Codes anlegen, KEINE Rollen verwalten,
       und haben KEINERLEI Möglichkeit, irgendetwas an der Website
       selbst zu verändern oder zu löschen — dafür gibt es in diesem
       Worker schlicht keine Funktion. Er kann ausschließlich
       Bewerbungen entgegennehmen und (mit gültigem Code) anzeigen.
     - Alle Codes werden nur als SHA-256-Hash in KV gespeichert, nie im
       Klartext. Ein neu erzeugter Code wird genau einmal zurückgegeben
       (beim Erstellen) — danach ist er nicht mehr abrufbar.

   Endpoints:
     POST /submit          { type, data }              -> Bewerbung speichern
     POST /postfach         { code }                    -> Bewerbungen ansehen
     POST /admin/roles/list   { ownerCode }                -> Helfer-Codes auflisten (nur Owner)
     POST /admin/roles/create { ownerCode, role, label }    -> neuen Helfer-Code erzeugen (nur Owner)
     POST /admin/roles/revoke { ownerCode, keyId }           -> Helfer-Code entziehen (nur Owner)
     GET  /live-status                                        -> öffentlich: {live, updatedAt}
     POST /live-status/set  { ownerCode, live }                -> Live-Status umschalten (nur Owner)
     GET  /health           -> einfacher Check ob der Worker läuft

   Secrets/Bindings (siehe wrangler.toml + README.md):
     KV Binding:  BEWERBUNGEN   (Cloudflare KV Namespace, wird auch für Rollen genutzt)
     Secret:      POSTFACH_CODE (mit `wrangler secret put POSTFACH_CODE` setzen)
   ===================================================================== */

const ALLOWED_TYPES = ["creator", "scout", "kooperation"];
const ROLE_KEY_PREFIX = "role:";
const LIVE_STATUS_KEY = "meta:live";

function corsHeaders() {
  // TODO: sobald die Domain final steht, statt "*" die echte Domain eintragen
  // (z.B. "https://dogfather-universe.example"), damit nur die eigene Seite
  // an die API senden kann.
  return {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type",
  };
}

function json(data, status = 200) {
  return new Response(JSON.stringify(data), {
    status,
    headers: { "Content-Type": "application/json", ...corsHeaders() },
  });
}

async function sha256Hex(text) {
  const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
  return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
}

function generateFriendlyCode() {
  // z.B. "K3F9-QX2L-7HRT" — leicht abzutippen/vorzulesen, ausreichend Entropie
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // ohne verwechselbare Zeichen (I,O,0,1)
  const bytes = crypto.getRandomValues(new Uint8Array(12));
  let raw = "";
  for (const b of bytes) raw += alphabet[b % alphabet.length];
  return `${raw.slice(0, 4)}-${raw.slice(4, 8)}-${raw.slice(8, 12)}`;
}

async function isOwnerCode(code, env) {
  return !!env.POSTFACH_CODE && code === env.POSTFACH_CODE;
}

async function findHelperRole(code, env) {
  const hash = await sha256Hex(code);
  const raw = await env.BEWERBUNGEN.get(ROLE_KEY_PREFIX + hash);
  return raw ? { keyId: ROLE_KEY_PREFIX + hash, ...JSON.parse(raw) } : null;
}

async function handleSubmit(request, env) {
  let body;
  try {
    body = await request.json();
  } catch {
    return json({ ok: false, error: "Ungültiges JSON." }, 400);
  }

  const { type, data } = body || {};
  if (!ALLOWED_TYPES.includes(type) || typeof data !== "object" || !data) {
    return json({ ok: false, error: "Ungültige Bewerbungsdaten." }, 400);
  }

  const values = Object.values(data).map((v) => String(v || "").trim());
  if (values.every((v) => v === "")) {
    return json({ ok: false, error: "Leere Bewerbung." }, 400);
  }

  const id = crypto.randomUUID();
  const timestamp = new Date().toISOString();
  const key = `sub:${timestamp}:${id}`;

  await env.BEWERBUNGEN.put(key, JSON.stringify({ id, type, data, timestamp }));

  return json({ ok: true });
}

async function handlePostfach(request, env) {
  let body;
  try {
    body = await request.json();
  } catch {
    return json({ ok: false, error: "Ungültiges JSON." }, 400);
  }

  const code = (body && body.code) || "";
  let owner = await isOwnerCode(code, env);
  let helperRole = null;

  if (!owner) {
    helperRole = await findHelperRole(code, env);
  }

  if (!owner && !helperRole) {
    // bewusst generische Fehlermeldung, kein Hinweis ob Code existiert
    return json({ ok: false, error: "Falscher Code." }, 401);
  }

  const list = await env.BEWERBUNGEN.list({ prefix: "sub:", limit: 500 });
  const items = await Promise.all(
    list.keys.map(async (k) => {
      const raw = await env.BEWERBUNGEN.get(k.name);
      return raw ? JSON.parse(raw) : null;
    })
  );

  const bewerbungen = items
    .filter(Boolean)
    .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)); // neueste zuerst

  return json({
    ok: true,
    isOwner: owner,
    roleLabel: owner ? "Dogi (Owner)" : helperRole.label,
    bewerbungen,
  });
}

async function requireOwner(body, env) {
  const ownerCode = (body && body.ownerCode) || "";
  return isOwnerCode(ownerCode, env);
}

async function handleRolesList(request, env) {
  let body;
  try {
    body = await request.json();
  } catch {
    return json({ ok: false, error: "Ungültiges JSON." }, 400);
  }
  if (!(await requireOwner(body, env))) {
    return json({ ok: false, error: "Nur Dogi darf Rollen verwalten." }, 401);
  }

  const list = await env.BEWERBUNGEN.list({ prefix: ROLE_KEY_PREFIX, limit: 200 });
  const roles = await Promise.all(
    list.keys.map(async (k) => {
      const raw = await env.BEWERBUNGEN.get(k.name);
      return raw ? { keyId: k.name, ...JSON.parse(raw) } : null;
    })
  );

  return json({ ok: true, roles: roles.filter(Boolean) });
}

async function handleRolesCreate(request, env) {
  let body;
  try {
    body = await request.json();
  } catch {
    return json({ ok: false, error: "Ungültiges JSON." }, 400);
  }
  if (!(await requireOwner(body, env))) {
    return json({ ok: false, error: "Nur Dogi darf Rollen verwalten." }, 401);
  }

  const role = String((body && body.role) || "").slice(0, 60).trim() || "Helfer";
  const label = String((body && body.label) || "").slice(0, 80).trim() || role;

  const newCode = generateFriendlyCode();
  const hash = await sha256Hex(newCode);
  const record = { role, label, createdAt: new Date().toISOString() };

  await env.BEWERBUNGEN.put(ROLE_KEY_PREFIX + hash, JSON.stringify(record));

  // Der Klartext-Code wird NUR jetzt einmalig zurückgegeben — danach nie wieder.
  return json({ ok: true, code: newCode, ...record });
}

async function handleRolesRevoke(request, env) {
  let body;
  try {
    body = await request.json();
  } catch {
    return json({ ok: false, error: "Ungültiges JSON." }, 400);
  }
  if (!(await requireOwner(body, env))) {
    return json({ ok: false, error: "Nur Dogi darf Rollen verwalten." }, 401);
  }

  const keyId = String((body && body.keyId) || "");
  if (!keyId.startsWith(ROLE_KEY_PREFIX)) {
    return json({ ok: false, error: "Ungültige keyId." }, 400);
  }

  await env.BEWERBUNGEN.delete(keyId);
  return json({ ok: true });
}

async function handleLiveStatusGet(env) {
  const raw = await env.BEWERBUNGEN.get(LIVE_STATUS_KEY);
  const data = raw ? JSON.parse(raw) : { live: false, updatedAt: null };
  return json({ ok: true, ...data });
}

async function handleLiveStatusSet(request, env) {
  let body;
  try {
    body = await request.json();
  } catch {
    return json({ ok: false, error: "Ungültiges JSON." }, 400);
  }
  if (!(await requireOwner(body, env))) {
    return json({ ok: false, error: "Nur Dogi darf den Live-Status ändern." }, 401);
  }

  const live = !!(body && body.live);
  const record = { live, updatedAt: new Date().toISOString() };
  await env.BEWERBUNGEN.put(LIVE_STATUS_KEY, JSON.stringify(record));

  return json({ ok: true, ...record });
}

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

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

    if (url.pathname === "/health") {
      return json({ ok: true, service: "dogfather-universe-postfach" });
    }

    if (url.pathname === "/submit" && request.method === "POST") {
      return handleSubmit(request, env);
    }

    if (url.pathname === "/postfach" && request.method === "POST") {
      return handlePostfach(request, env);
    }

    if (url.pathname === "/admin/roles/list" && request.method === "POST") {
      return handleRolesList(request, env);
    }

    if (url.pathname === "/admin/roles/create" && request.method === "POST") {
      return handleRolesCreate(request, env);
    }

    if (url.pathname === "/admin/roles/revoke" && request.method === "POST") {
      return handleRolesRevoke(request, env);
    }

    if (url.pathname === "/live-status" && request.method === "GET") {
      return handleLiveStatusGet(env);
    }

    if (url.pathname === "/live-status/set" && request.method === "POST") {
      return handleLiveStatusSet(request, env);
    }

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