/* Plazmacentrum booking client v2
 *
 * Smart cache strategy:
 *   - Day overview (Supabase):   30s in-memory cache
 *   - Slot list per day (no email): 5min sessionStorage cache
 *   - Exclusion check (with email): always live, but updates the slot cache
 *   - Submit: server-side Edge Function does final live check (defense in depth)
 *
 * Exposes window.bookingClient = {
 *   fetchDayOverview,
 *   fetchSlotsForDay,
 *   checkExclusion,
 *   getCachedSlotsFor,
 *   getLastFetchAge,
 *   invalidateCache,
 *   createBooking,
 * }
 */
(function () {
  const SUPABASE_URL = "https://hoounjzhomovgvzovtyz.supabase.co";
  const ANON_KEY =
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
    "eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imhvb3Vuanpob21vdmd2em92dHl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzg1MTI1MTcsImV4cCI6MjA5NDA4ODUxN30." +
    "0tvJmG_WQVT79XG4kCilN_LTbJIQa-1ITK8-zY-HItY";

  const supaHeaders = {
    Authorization: "Bearer " + ANON_KEY,
    apikey: ANON_KEY,
  };

  /* ---------- Cache ---------- */
  // Day overview cache: in-memory only, 30s
  const overviewCache = new Map(); // key: site -> { data, ts }
  const OVERVIEW_TTL = 30 * 1000;

  // Per-day slot cache: sessionStorage, 5 min
  const SLOT_TTL = 5 * 60 * 1000;
  const SS_PREFIX = "plazma:slot:";

  function ssGet(key) {
    try {
      const raw = sessionStorage.getItem(SS_PREFIX + key);
      if (!raw) return null;
      const parsed = JSON.parse(raw);
      if (!parsed || typeof parsed.ts !== "number") return null;
      return parsed;
    } catch (e) {
      return null;
    }
  }
  function ssSet(key, value) {
    try {
      sessionStorage.setItem(SS_PREFIX + key, JSON.stringify(value));
    } catch (e) {/* sessionStorage might be full / disabled */}
  }
  function ssDelete(key) {
    try { sessionStorage.removeItem(SS_PREFIX + key); } catch (e) {/* noop */}
  }

  function slotCacheKey(site, date) {
    return site + ":" + date;
  }

  /** Age (in ms) of the most recent slot fetch for site+date. Returns Infinity if no cache. */
  function getLastFetchAge(site, date) {
    const c = ssGet(slotCacheKey(site, date));
    if (!c) return Infinity;
    return Date.now() - c.ts;
  }

  /** Return cached slots if fresh, else null. */
  function getCachedSlotsFor(site, date, maxAgeMs) {
    const max = typeof maxAgeMs === "number" ? maxAgeMs : SLOT_TTL;
    const c = ssGet(slotCacheKey(site, date));
    if (!c) return null;
    if (Date.now() - c.ts > max) return null;
    return c.data;
  }

  function setSlotCache(site, date, data) {
    ssSet(slotCacheKey(site, date), { data, ts: Date.now() });
  }

  function invalidateCache(site, date) {
    if (date) ssDelete(slotCacheKey(site, date));
    else overviewCache.delete(site);
  }

  /* ---------- 1) Day overview (Supabase get-slots) ---------- */
  /** Get next-20-day capacity overview (for the coloured day-pill dots).
   *  30s in-memory cache. */
  async function fetchDayOverview(site) {
    const cached = overviewCache.get(site);
    if (cached && Date.now() - cached.ts < OVERVIEW_TTL) return cached.data;

    const today = new Date();
    const to = new Date(today.getTime() + 20 * 86400000);
    const fmt = (d) =>
      d.getFullYear() + "-" +
      String(d.getMonth() + 1).padStart(2, "0") + "-" +
      String(d.getDate()).padStart(2, "0");

    const url =
      SUPABASE_URL +
      "/functions/v1/get-slots?site=" + encodeURIComponent(site) +
      "&from=" + fmt(today) +
      "&to=" + fmt(to);
    const res = await fetch(url, { headers: supaHeaders });
    const data = await res.json().catch(() => ({}));
    if (!res.ok) {
      const err = new Error(data.error || "Failed to fetch day overview");
      err.status = res.status;
      throw err;
    }
    overviewCache.set(site, { data, ts: Date.now() });
    return data;
  }

  /* ---------- 2) Slot list for a single day (no email) ---------- */
  /** Get bookable slots for a specific date from the centrum's authoritative API.
   *  5min sessionStorage cache (set forceFresh=true to bypass).
   *  Returns: { ok:true, data:[...HH:MM], message?: string }  OR  { ok:false, message: "..." }
   */
  async function fetchSlotsForDay(site, date, opts) {
    opts = opts || {};
    if (!opts.forceFresh) {
      const cached = getCachedSlotsFor(site, date);
      if (cached) return cached;
    }
    // Direct call to the centrum API (all 3 centrums send CORS headers as of 2026-05-30)
    const url =
      "https://" + site + ".plazmacentrum.hu/api/v1/appointment/free/" +
      encodeURIComponent(date);
    const res = await fetch(url);
    const json = await res.json().catch(() => ({}));
    if (!res.ok) {
      // Network/HTTP error
      const out = { ok: false, message: "Hiba a foglaló rendszerrel — próbáld újra." };
      return out;
    }
    if (json.response === false) {
      // Closed day or other reason from API
      const out = { ok: false, message: json.message || "Ezen a napon nincs foglalás." };
      setSlotCache(site, date, out);
      return out;
    }
    const out = {
      ok: true,
      data: Array.isArray(json.data) ? json.data : [],
      limit: json.limit || {},
    };
    setSlotCache(site, date, out);
    return out;
  }

  /* ---------- 3) Exclusion check (with email) ---------- */
  /** Call centrum API WITH email to check donor exclusions.
   *  Always live (no cache).
   *  Updates the slot cache too (since the response also includes fresh slot data).
   *  Returns: {
   *    ok: true | false,
   *    excluded: boolean,
   *    reason: "no_bloodtest" | "currently" | "permanently" | "cancellation" | null,
   *    detail: { ending_date, message, ... } | null,
   *    data: [...HH:MM],   // also-fresh slot list
   *    message?: string,
   *  }
   */
  async function checkExclusion(site, date, email) {
    // Direct call to the centrum API (all 3 centrums send CORS headers as of 2026-05-30)
    const url =
      "https://" + site + ".plazmacentrum.hu/api/v1/appointment/free/" +
      encodeURIComponent(date) +
      "?email=" + encodeURIComponent(email);
    let res, json;
    try {
      res = await fetch(url);
      json = await res.json();
    } catch (e) {
      return { ok: false, excluded: false, reason: null, detail: null, data: [], message: "Hálózati hiba." };
    }
    if (!res.ok || json.response === false) {
      return { ok: false, excluded: false, reason: null, detail: null, data: [], message: json && json.message || "Hiba a foglaló rendszerrel." };
    }

    const limit = json.limit || {};
    let excluded = false;
    let reason = null;
    let detail = null;

    // Order matters: check the strongest first.
    // NOTE: no_verified_bloodtest_excluded is NOT a blocker — Plazmacentrum does the
    // blood test on-site. We surface it as an INFO notice instead (see info field).
    let info = null;
    if (limit.permanently_excluded && typeof limit.permanently_excluded === "object") {
      excluded = true;
      reason = "permanently";
      detail = limit.permanently_excluded;
    } else if (limit.currently_excluded && typeof limit.currently_excluded === "object") {
      excluded = true;
      reason = "currently";
      detail = limit.currently_excluded;
    } else if (limit.cancellation_excluded && typeof limit.cancellation_excluded === "object") {
      excluded = true;
      reason = "cancellation";
      detail = limit.cancellation_excluded;
    }
    // Non-blocking info: missing verified bloodtest
    if (limit.no_verified_bloodtest_excluded === true) {
      info = { kind: "no_bloodtest", ending_date: limit.verified_bloodtest_excluded_ending || null };
    }

    const data = Array.isArray(json.data) ? json.data : [];

    // Refresh the no-email cache with the latest slot list too (slightly conservative — this
    // list is post-exclusion-filter for the donor, so other users would see same or more)
    setSlotCache(site, date, { ok: true, data, limit });

    return { ok: true, excluded, reason, detail, info, data };
  }

  /* ---------- 4) Create booking ---------- */
  async function createBooking(payload) {
    const res = await fetch(SUPABASE_URL + "/functions/v1/book-appointment", {
      method: "POST",
      headers: Object.assign({}, supaHeaders, { "Content-Type": "application/json" }),
      body: JSON.stringify(payload),
    });
    const data = await res.json().catch(() => ({}));
    if (!res.ok) {
      const err = new Error(data.error || "Booking failed");
      err.status = res.status;
      err.code = data.code;
      err.field = data.field;
      err.detail = data.detail;
      throw err;
    }
    return data;
  }

  window.bookingClient = {
    fetchDayOverview,
    fetchSlotsForDay,
    checkExclusion,
    getCachedSlotsFor,
    getLastFetchAge,
    invalidateCache,
    createBooking,
  };
})();
