const express = require('express');
const path = require('path');
const cors = require('cors');

const { v4: uuid } = require('uuid');
const sessionStore = require('./services/sessionStore');
const credentialStore = require('./services/credentialStore');
const { readConfig, writeConfig } = require('./services/configStore');
const foodsharingClient = require('./services/foodsharingClient');
const {
  scheduleConfig,
  runStoreWatchCheck,
  runImmediatePickupCheck,
  runDormantMembershipCheck,
  refreshStaleLastPickupInfo,
  getRegularPickupSchedule,
  scheduleRegularPickupRefresh
} = require('./services/pickupScheduler');
const adminConfig = require('./services/adminConfig');
const { readNotificationSettings, writeNotificationSettings } = require('./services/userSettingsStore');
const notificationService = require('./services/notificationService');
const { readStoreWatch, writeStoreWatch, listWatcherProfiles } = require('./services/storeWatchStore');
const { readPreferences, writePreferences, sanitizeLocation } = require('./services/userPreferencesStore');
const { readStoresCache, writeStoresCache } = require('./services/storeCacheStore');
const requestLogStore = require('./services/requestLogStore');
const backgroundJobOrchestrator = require('./services/backgroundJobOrchestrator');
const {
  readJournal,
  writeJournal,
  saveJournalImage,
  deleteJournalImage,
  getProfileImageDir
} = require('./services/journalStore');
const { normalizeReminderReference } = require('./services/journalReminderUtils');
const { withSessionRetry } = require('./services/sessionRefresh');
const {
  getStoreStatus: getCachedStoreStatusEntry,
  setStoreStatus: setCachedStoreStatusEntry,
  persistStoreStatusCache
} = require('./services/storeStatusCache');
const maintenanceMode = require('./services/maintenanceMode');

const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000;
const adminEmail = (process.env.ADMIN_EMAIL || '').toLowerCase();
const SIXTY_DAYS_MS = 60 * 24 * 60 * 60 * 1000;
const PROFILE_DETAILS_TTL_MS = 6 * 60 * 60 * 1000;

const app = express();
const port = process.env.PORT || 3000;
const storeRefreshJobs = new Map();
const cachedStoreSnapshots = new Map();
const regionStoreCache = new Map();
const REGION_STORE_CACHE_MS = 15 * 60 * 1000;
const STORE_STATUS_MAX_AGE_MS = 60 * 24 * 60 * 60 * 1000;
const storeLocationIndex = new Map();
let storeLocationIndexUpdatedAt = 0;
const STORE_LOCATION_INDEX_TTL_MS = 12 * 60 * 60 * 1000;
const BACKGROUND_STORE_REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;

function maintenanceSummary() {
  const snapshot = maintenanceMode.getSnapshot();
  return {
    active: snapshot.active,
    activatedAt: snapshot.activatedAt,
    reason: snapshot.reason
  };
}

function normalizePickupEntry(pickup) {
  if (!pickup || typeof pickup !== 'object') {
    return null;
  }
  const date = pickup.date || pickup.pickupDate || null;
  const parsedDate = date ? new Date(date) : null;
  if (!parsedDate || Number.isNaN(parsedDate.getTime())) {
    return null;
  }
  const store = pickup.store && typeof pickup.store === 'object' ? pickup.store : {};
  const storeId = store.id != null ? String(store.id) : pickup.storeId != null ? String(pickup.storeId) : '';
  return {
    date: parsedDate.toISOString(),
    storeId,
    storeName: store.name || pickup.storeName || (storeId ? `Store ${storeId}` : 'Unbekannter Betrieb'),
    description: pickup.description || '',
    slots: Number.isFinite(Number(pickup.slots)) ? Number(pickup.slots) : null,
    isConfirmed: pickup.isConfirmed === true
  };
}

function toRadians(value) {
  return (value * Math.PI) / 180;
}

function haversineDistanceKm(lat1, lon1, lat2, lon2) {
  if (
    !Number.isFinite(lat1) ||
    !Number.isFinite(lon1) ||
    !Number.isFinite(lat2) ||
    !Number.isFinite(lon2)
  ) {
    return null;
  }
  const R = 6371;
  const dLat = toRadians(lat2 - lat1);
  const dLon = toRadians(lon2 - lon1);
  const radLat1 = toRadians(lat1);
  const radLat2 = toRadians(lat2);
  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(radLat1) * Math.cos(radLat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c;
}

app.use(cors());
app.use(express.json({ limit: '15mb' }));
app.use(express.static(path.join(__dirname, 'build')));

app.use((req, res, next) => {
  const startedAt = Date.now();
  let responseBodySnippet = null;

  const captureBody = (body) => {
    responseBodySnippet = body;
    return body;
  };

  const originalJson = res.json.bind(res);
  res.json = (body) => originalJson(captureBody(body));

  const originalSend = res.send.bind(res);
  res.send = (body) => originalSend(captureBody(body));

  res.on('finish', () => {
    try {
      requestLogStore.add({
        direction: 'incoming',
        method: req.method,
        path: req.originalUrl || req.url,
        status: res.statusCode,
        durationMs: Date.now() - startedAt,
        sessionId: req.session?.id || null,
        profileId: req.session?.profile?.id || null,
        profileName: req.session?.profile?.name || null,
        responseBody: responseBodySnippet
      });
    } catch (error) {
      console.warn('[REQUEST-LOG] Schreiben fehlgeschlagen:', error.message);
    }
  });
  next();
});

function isAdmin(profile) {
  if (!adminEmail || !profile?.email) {
    return false;
  }
  return profile.email.toLowerCase() === adminEmail;
}

async function fetchProfileWithCache(session, { force = false } = {}) {
  if (!session?.id) {
    return null;
  }
  const cached = session.profileDetailsCache;
  const isFresh = cached?.fetchedAt && Date.now() - cached.fetchedAt <= PROFILE_DETAILS_TTL_MS;
  if (!force && isFresh && cached?.data) {
    return cached.data;
  }
  try {
    const details = await withSessionRetry(
      session,
      () => foodsharingClient.fetchProfile(session.cookieHeader, { throwOnError: true }, session),
      { label: 'fetchProfile' }
    );
    sessionStore.update(session.id, {
      profileDetailsCache: { data: details, fetchedAt: Date.now() }
    });
    return details;
  } catch (error) {
    if (cached?.data) {
      return cached.data;
    }
    throw error;
  }
}

async function buildRegularPickupMapForConfig(session, config) {
  try {
    const entries = Array.isArray(config) ? config : [];
    const storeIds = Array.from(
      new Set(entries.filter((entry) => entry?.id && !entry.hidden).map((entry) => String(entry.id)))
    );
    if (storeIds.length === 0) {
      return {};
    }
    const results = await Promise.all(
      storeIds.map(async (storeId) => {
        const result = await getRegularPickupSchedule(session, storeId);
        return [storeId, Array.isArray(result.rules) ? result.rules : []];
      })
    );
    return results.reduce((acc, [storeId, rules]) => {
      acc[storeId] = rules;
      return acc;
    }, {});
  } catch (error) {
    console.warn('[PICKUP] Regular-Pickup-Map konnte nicht geladen werden:', error.message);
    return {};
  }
}

function scheduleWithCurrentSettings(sessionId, config) {
  const settings = adminConfig.readSettings();
  scheduleConfig(sessionId, config, settings);
  const session = sessionStore.get(sessionId);
  if (session?.profile?.id) {
    backgroundJobOrchestrator.refreshProfile(session.profile.id, { config, settings }).catch((error) => {
      console.error('[BACKGROUND] Profil-Jobs konnten nicht aktualisiert werden:', error.message);
    });
  }
}

function rescheduleAllSessions() {
  const settings = adminConfig.readSettings();
  sessionStore.list().forEach((session) => {
    if (!session?.profile?.id) {
      return;
    }
    const config = readConfig(session.profile.id);
    scheduleConfig(session.id, config, settings);
  });
  scheduleRegularPickupRefresh(settings);
  backgroundJobOrchestrator.refreshAll({ settings }).catch((error) => {
    console.error('[BACKGROUND] Profil-Jobs konnten nicht neu geplant werden:', error.message);
  });
}

function mergeStoresIntoConfig(config = [], stores = []) {
  const entries = Array.isArray(config) ? config : [];
  const storeList = Array.isArray(stores) ? stores : [];
  const map = new Map();
  entries.forEach((entry) => {
    if (!entry || !entry.id) {
      return;
    }
    map.set(String(entry.id), { ...entry, id: String(entry.id) });
  });

  let changed = false;
  storeList.forEach((store) => {
    if (!store?.id) {
      return;
    }
    const id = String(store.id);
    if (!map.has(id)) {
      const hideByDefault = store.hasPickupSlots === false;
      map.set(id, {
        id,
        label: store.name || `Store ${id}`,
        active: false,
        autoDeactivate: true,
        checkProfileId: true,
        onlyNotify: false,
        hidden: hideByDefault
      });
      changed = true;
      return;
    }

    const existing = map.get(id);
    if (!existing.label && store.name) {
      existing.label = store.name;
      changed = true;
    }
  });

  const activeStoreIds = new Set(
    storeList
      .filter((store) => store?.id)
      .map((store) => String(store.id))
  );

  Array.from(map.keys()).forEach((id) => {
    if (activeStoreIds.has(id)) {
      return;
    }
    map.delete(id);
    changed = true;
  });

  return { merged: Array.from(map.values()), changed };
}

function getMissingLastPickupStoreIds(config = []) {
  if (!Array.isArray(config)) {
    return [];
  }
  return config
    .filter((entry) => entry && entry.id && !entry.hidden && !entry.skipDormantCheck && !entry.lastPickupAt)
    .map((entry) => String(entry.id));
}

function getCachedRegionStores(regionId) {
  const entry = regionStoreCache.get(String(regionId));
  if (!entry) {
    return null;
  }
  if (Date.now() - entry.fetchedAt > REGION_STORE_CACHE_MS) {
    regionStoreCache.delete(String(regionId));
    return null;
  }
  return entry.payload;
}

function setCachedRegionStores(regionId, payload) {
  regionStoreCache.set(String(regionId), {
    fetchedAt: Date.now(),
    payload
  });
}

function getCachedStoreStatus(storeId) {
  return getCachedStoreStatusEntry(storeId);
}

function resolveStoreTeamStatus(details) {
  const status = Number(details?.teamStatus ?? details?.teamSearchStatus);
  return Number.isFinite(status) ? status : null;
}

function isStoreStatusAccessError(error) {
  const status = error?.response?.status;
  return status === 403 || status === 404;
}

function normalizeJournalReminder(reminder = {}) {
  const unit = ['days', 'weeks', 'months'].includes(reminder.beforeUnit) ? reminder.beforeUnit : 'days';
  const parsedBeforeValue = Number(reminder.beforeValue);
  const parsedDaysBefore = Number(reminder.daysBefore);
  const beforeValue = Number.isFinite(parsedBeforeValue)
    ? Math.max(0, parsedBeforeValue)
    : Number.isFinite(parsedDaysBefore)
      ? Math.max(0, parsedDaysBefore)
      : 6;
  const daysBefore = unit === 'weeks' ? beforeValue * 7 : unit === 'months' ? beforeValue * 30 : beforeValue;
  const reference = normalizeReminderReference(reminder.reference);
  return {
    enabled: !!reminder.enabled,
    interval: reference.mode === 'event'
      ? 'yearly'
      : ['monthly', 'quarterly', 'yearly'].includes(reminder.interval)
      ? reminder.interval
      : 'yearly',
    beforeUnit: unit,
    beforeValue,
    daysBefore,
    reference
  };
}

function parseDataUrl(dataUrl) {
  if (typeof dataUrl !== 'string') {
    return null;
  }
  const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
  if (!match) {
    return null;
  }
  const mimeType = match[1];
  const buffer = Buffer.from(match[2], 'base64');
  return { mimeType, buffer };
}

function resolveImageExtension(mimeType) {
  if (mimeType === 'image/jpeg') {
    return '.jpg';
  }
  if (mimeType === 'image/png') {
    return '.png';
  }
  if (mimeType === 'image/gif') {
    return '.gif';
  }
  if (mimeType === 'image/webp') {
    return '.webp';
  }
  return '';
}

function ingestStoreLocations(stores = []) {
  let changed = false;
  stores.forEach((store) => {
    const lat = Number(store?.location?.lat);
    const lon = Number(store?.location?.lon);
    if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
      return;
    }
    const storeId = String(store.id);
    const entry = {
      storeId,
      lat,
      lon,
      name: store.name || `Store ${storeId}`,
      city: store.city || '',
      regionName: store.region?.name || '',
      label:
        store.city && store.region?.name && store.city.toLowerCase() !== store.region.name.toLowerCase()
          ? `${store.city} • ${store.region.name}`
          : store.city || store.region?.name || store.name || `Store ${storeId}`
    };
    storeLocationIndex.set(storeId, entry);
    changed = true;
  });
  if (changed) {
    storeLocationIndexUpdatedAt = Date.now();
  }
}

async function ensureStoreLocationIndex(session, { force = false } = {}) {
  if (!session?.cookieHeader) {
    throw new Error('Keine gültige Session für Standortbestimmung verfügbar.');
  }
  const fresh = Date.now() - storeLocationIndexUpdatedAt < STORE_LOCATION_INDEX_TTL_MS;
  if (!force && storeLocationIndex.size > 0 && fresh) {
    return;
  }
  const details = await fetchProfileWithCache(session);
  const regions = Array.isArray(details?.regions)
    ? details.regions.filter((region) => Number(region?.classification) === 1)
    : [];
  if (regions.length === 0) {
    return;
  }
  for (const region of regions) {
    let payload = getCachedRegionStores(region.id);
    if (!payload) {
      const result = await withSessionRetry(
        session,
        () => foodsharingClient.fetchRegionStores(region.id, session.cookieHeader, session),
        { label: 'fetchRegionStores' }
      );
      payload = {
        total: Number(result?.total) || 0,
        stores: Array.isArray(result?.stores) ? result.stores : []
      };
      setCachedRegionStores(region.id, payload);
    }
    const filtered = payload.stores
      .filter((store) => Number(store.cooperationStatus) === 5)
      .map((store) => ({ ...store, id: String(store.id) }));
    ingestStoreLocations(filtered);
  }
}

function findNearestStoreLocation(lat, lon) {
  if (storeLocationIndex.size === 0) {
    return null;
  }
  let closest = null;
  storeLocationIndex.forEach((entry) => {
    const distance = haversineDistanceKm(lat, lon, entry.lat, entry.lon);
    if (distance === null) {
      return;
    }
    if (!closest || distance < closest.distanceKm) {
      closest = {
        storeId: entry.storeId,
        label: entry.label,
        name: entry.name,
        city: entry.city,
        regionName: entry.regionName,
        distanceKm: distance
      };
    }
  });
  return closest;
}

async function notifyWatchersForStatusChanges(changes = [], storeInfoMap = new Map()) {
  if (!Array.isArray(changes) || changes.length === 0) {
    return;
  }
  const changeMap = new Map();
  changes.forEach((change) => {
    if (!change) {
      return;
    }
    changeMap.set(String(change.storeId), change);
  });
  if (changeMap.size === 0) {
    return;
  }
  const profiles = listWatcherProfiles();
  for (const profileId of profiles) {
    const watchers = readStoreWatch(profileId);
    if (!Array.isArray(watchers) || watchers.length === 0) {
      continue;
    }
    let changed = false;
    for (const watcher of watchers) {
      const change = changeMap.get(String(watcher.storeId));
      if (!change) {
        continue;
      }
      if (watcher.lastTeamSearchStatus !== change.newStatus) {
        if (change.newStatus === 1 && watcher.lastTeamSearchStatus !== 1) {
          const details = storeInfoMap.get(String(watcher.storeId)) || {};
          await notificationService.sendStoreWatchNotification({
            profileId,
            storeName: details.name || watcher.storeName,
            storeId: watcher.storeId,
            regionName: details.region?.name || watcher.regionName
          });
        }
        watcher.lastTeamSearchStatus = change.newStatus;
        watcher.lastStatusCheckAt = change.fetchedAt || Date.now();
        changed = true;
      }
    }
    if (changed) {
      writeStoreWatch(profileId, watchers);
    }
  }
}

async function refreshStoreStatus(
  storeIds = [],
  session,
  { force = false, storeInfoMap = new Map() } = {}
) {
  if (!Array.isArray(storeIds) || storeIds.length === 0 || !session?.cookieHeader) {
    return { refreshed: 0, changes: [] };
  }
  const now = Date.now();
  let refreshed = 0;
  const changes = [];
  for (const id of storeIds) {
    const storeId = String(id);
    const entry = getCachedStoreStatus(storeId);
    const ageOk = entry && now - (entry.fetchedAt || 0) <= STORE_STATUS_MAX_AGE_MS;
    if (!force && ageOk) {
      continue;
    }
    try {
      const marker = await withSessionRetry(
        session,
        () => foodsharingClient.fetchStoreMarker(storeId, session.cookieHeader, session),
        { label: 'fetchStoreMarker' }
      );
      const normalized = resolveStoreTeamStatus(marker);
      const previous = entry ? entry.teamSearchStatus : null;
      setCachedStoreStatusEntry(storeId, {
        teamSearchStatus: normalized,
        fetchedAt: now
      });
      if (previous !== normalized) {
        const info = storeInfoMap.get(storeId) || {};
        changes.push({
          storeId,
          previousStatus: previous,
          newStatus: normalized,
          fetchedAt: now,
          storeName: info.name || null,
          regionName: info.region?.name || info.regionName || null
        });
      }
      refreshed += 1;
    } catch (error) {
      if (isStoreStatusAccessError(error)) {
        setCachedStoreStatusEntry(storeId, {
          teamSearchStatus: null,
          fetchedAt: now
        });
        refreshed += 1;
        continue;
      }
      console.error(`[STORE-STATUS] Status für Store ${storeId} konnte nicht aktualisiert werden:`, error.message);
    }
  }
  if (refreshed > 0) {
    persistStoreStatusCache();
  }
  return { refreshed, changes };
}

async function enrichStoresWithTeamStatus(stores = [], session, { forceRefresh = false } = {}) {
  if (!Array.isArray(stores) || stores.length === 0) {
    return { stores, statusMeta: { total: 0, refreshed: 0, fromCache: 0, missing: 0 } };
  }
  const now = Date.now();
  const staleIds = [];
  let cacheHits = 0;
  const storeInfoMap = new Map();
  stores.forEach((store) => {
    if (store?.id) {
      storeInfoMap.set(String(store.id), store);
    }
    const entry = getCachedStoreStatus(store.id);
    const fresh = entry && now - (entry.fetchedAt || 0) <= STORE_STATUS_MAX_AGE_MS;
    if (entry && fresh && !forceRefresh) {
      store.teamSearchStatus = entry.teamSearchStatus;
      store.teamStatusUpdatedAt = entry.fetchedAt || null;
      cacheHits += 1;
    } else {
      staleIds.push(store.id);
    }
  });

  let refreshed = 0;
  let changes = [];
  if (staleIds.length > 0) {
    const result = await refreshStoreStatus(staleIds, session, {
      force: forceRefresh,
      storeInfoMap
    });
    refreshed = result.refreshed || 0;
    changes = result.changes || [];
  }

  stores.forEach((store) => {
    if (store.teamSearchStatus === undefined) {
      const entry = getCachedStoreStatus(store.id);
      if (entry) {
        store.teamSearchStatus = entry.teamSearchStatus;
        store.teamStatusUpdatedAt = entry.fetchedAt || null;
      } else {
        store.teamSearchStatus = null;
        store.teamStatusUpdatedAt = null;
      }
    }
  });

  const statusMeta = {
    total: stores.length,
    refreshed,
    fromCache: cacheHits,
    missing: stores.filter((store) => store.teamStatusUpdatedAt == null).length,
    generatedAt: Date.now()
  };
  if (changes.length > 0) {
    await notifyWatchersForStatusChanges(changes, storeInfoMap);
  }
  return { stores, statusMeta };
}

function isStoreCacheFresh(session) {
  if (!session?.storesCache?.fetchedAt) {
    return false;
  }
  return Date.now() - session.storesCache.fetchedAt <= SIXTY_DAYS_MS;
}

function summarizeJob(job) {
  if (!job) {
    return null;
  }
  return {
    id: job.id,
    status: job.status,
    processed: job.processed,
    total: job.total,
    currentStore: job.currentStore,
    startedAt: job.startedAt,
    finishedAt: job.finishedAt,
    reason: job.reason || null,
    error: job.error || null
  };
}

function getStoreRefreshJob(sessionId) {
  const job = storeRefreshJobs.get(sessionId);
  if (!job) {
    return null;
  }
  if (job.status === 'done' && Date.now() - (job.finishedAt || 0) > 5 * 60 * 1000) {
    storeRefreshJobs.delete(sessionId);
    return null;
  }
  return job;
}

function triggerStoreRefresh(session, { force = false, reason } = {}) {
  if (!session?.id) {
    return { started: false };
  }
  if (maintenanceMode.isActive()) {
    return { started: false, maintenance: true, maintenanceStatus: maintenanceSummary() };
  }
  const existing = getStoreRefreshJob(session.id);
  if (existing && existing.status === 'running') {
    return { started: false, job: existing };
  }

  const cacheFresh = isStoreCacheFresh(session);
  if (!force && cacheFresh) {
    return { started: false, cacheFresh: true };
  }

  const job = {
    id: uuid(),
    status: 'queued',
    processed: 0,
    total: 0,
    currentStore: null,
    startedAt: null,
    finishedAt: null,
    reason: reason || null,
    error: null
  };

  storeRefreshJobs.set(session.id, job);
  runStoreRefreshJob(session, job).catch((error) => {
    console.error('[STORE-REFRESH] Job fehlgeschlagen:', error.message);
    job.status = 'error';
    job.error = error.message;
    job.finishedAt = Date.now();
  });
  return { started: true, job };
}

async function runStoreRefreshJob(session, job) {
  maintenanceMode.ensureInactive('Store-Refresh ist während des Wartungsmodus deaktiviert.');
  job.status = 'running';
  job.startedAt = Date.now();
  const settings = adminConfig.readSettings();
  const stores = await withSessionRetry(
    session,
    () =>
      foodsharingClient.fetchStores(
        session.cookieHeader,
        session.profile.id,
        {
          delayBetweenRequestsMs: settings.storePickupCheckDelayMs,
          onStoreCheck: (store, processed, total) => {
            job.processed = processed;
            job.total = total;
            job.currentStore = store.name || `Store ${store.id}`;
          }
        },
        session
      ),
    { label: 'fetchStores' }
  );
  if (stores.length === 0) {
    throw new Error('Store-Refresh lieferte keine Betriebe; bestehende Daten bleiben unverändert.');
  }
  job.processed = stores.length;
  job.total = stores.length;
  job.currentStore = null;

  sessionStore.update(session.id, {
    storesCache: { data: stores, fetchedAt: Date.now() }
  });
  writeStoresCache(session.profile.id, sessionStore.get(session.id)?.storesCache);

  let config = readConfig(session.profile.id);
  const { merged, changed } = mergeStoresIntoConfig(config, stores);
  if (changed) {
    config = merged;
    writeConfig(session.profile.id, config);
    scheduleWithCurrentSettings(session.id, config);
  }
  const missingLastPickupStoreIds = getMissingLastPickupStoreIds(config);
  if (missingLastPickupStoreIds.length > 0) {
    try {
      await runDormantMembershipCheck(session.id, { storeIds: missingLastPickupStoreIds });
    } catch (error) {
      console.warn(
        `[DORMANT] Letzte Abholung nach Store-Refresh konnte nicht aktualisiert werden:`,
        error.message
      );
    }
  }

  job.status = 'done';
  job.finishedAt = Date.now();
}

async function loadStoresForSession(session, _settings, { forceRefresh = false, reason } = {}) {
  if (!session?.profile?.id) {
    return { stores: [], refreshed: false };
  }

  const cacheFresh = isStoreCacheFresh(session);
  if ((forceRefresh || !cacheFresh) && session.cookieHeader) {
    triggerStoreRefresh(session, { force: true, reason: reason || 'session-check' });
  }

  return {
    stores: session.storesCache?.data || [],
    refreshed: cacheFresh
  };
}

function startBackgroundStoreRefreshTicker() {
  const runCheck = () => {
    sessionStore.list().forEach((session) => {
      if (!session?.id || !session.cookieHeader) {
        return;
      }
      const cacheFresh = isStoreCacheFresh(session);
      const job = getStoreRefreshJob(session.id);
      const jobRunning = job?.status === 'running';
      if (jobRunning || cacheFresh) {
        return;
      }
      const reason = session.storesCache?.fetchedAt ? 'background-stale-cache' : 'background-no-cache';
      const result = triggerStoreRefresh(session, { force: true, reason });
      if (result?.started) {
        console.log(`[STORE-REFRESH] Hintergrund-Refresh gestartet für Session ${session.id} (${reason})`);
      }
    });
  };

  setTimeout(() => {
    runCheck();
    setInterval(runCheck, BACKGROUND_STORE_REFRESH_INTERVAL_MS);
  }, 60 * 1000);
}

async function restoreSessionsFromDisk() {
  const saved = credentialStore.loadAll();
  const entries = Object.entries(saved);
  if (entries.length === 0) {
    return;
  }

  console.log(`[RESTORE] Versuche ${entries.length} gespeicherte Anmeldung(en) zu laden...`);
  const schedulerSettings = adminConfig.readSettings();

  for (const [profileId, credentials] of entries) {
    if (!credentials?.email || !credentials?.password) {
      continue;
    }
    try {
      const auth = await foodsharingClient.login(credentials.email, credentials.password);
      const profile = {
        id: String(auth.profile.id),
        name: auth.profile.name,
        email: auth.profile.email || credentials.email
      };
      const isAdminUser = isAdmin(profile);
      let config = readConfig(profile.id);

      const session = sessionStore.create({
        cookieHeader: auth.cookieHeader,
        csrfToken: auth.csrfToken,
        profile,
        credentials,
        isAdmin: isAdminUser
      }, credentials.token, ONE_YEAR_MS);
      const persistedStoresCache = readStoresCache(profile.id);
      credentialStore.save(profile.id, {
        email: credentials.email,
        password: credentials.password,
        token: session.id
      });
      sessionStore.update(session.id, {
        storesCache: persistedStoresCache
      });
      scheduleConfig(session.id, config, schedulerSettings);
      triggerStoreRefresh(sessionStore.get(session.id), { force: true, reason: 'restore' });
      console.log(`[RESTORE] Session fuer Profil ${profile.id} (${profile.name}) reaktiviert.`);
    } catch (error) {
      console.error(`[RESTORE] Login fuer Profil ${profileId} fehlgeschlagen:`, error.message);
      try {
        await notificationService.sendAdminSessionErrorNotification({
          profileId,
          profileEmail: credentials?.email,
          error: error?.message,
          label: 'restore'
        });
      } catch (notifyError) {
        console.error('[NOTIFY] Admin-Session-Fehler beim Restore konnte nicht gemeldet werden:', notifyError.message);
      }
    }
  }
}

function requireAuth(req, res, next) {
  const header = req.headers.authorization || '';
  const [scheme, token] = header.split(' ');
  if (scheme !== 'Bearer' || !token) {
    return res.status(401).json({ error: 'Unautorisiert' });
  }

  const session = sessionStore.get(token);
  if (!session) {
    return res.status(401).json({ error: 'Session nicht gefunden oder abgelaufen' });
  }

  req.session = session;
  next();
}

function requireAdmin(req, res, next) {
  if (!req.session?.isAdmin) {
    return res.status(403).json({ error: 'Nur für Admins verfügbar' });
  }
  next();
}

app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body || {};
  if (!email || !password) {
    return res.status(400).json({ error: 'E-Mail und Passwort sind erforderlich' });
  }

  try {
    const auth = await foodsharingClient.login(email, password);
    const profile = {
      id: String(auth.profile.id),
      name: auth.profile.name,
      email: auth.profile.email || email
    };
    const isAdminUser = isAdmin(profile);
    const settings = adminConfig.readSettings();
    let config = readConfig(profile.id);

    const existingCredentials = credentialStore.get(profile.id);
    const existingToken = existingCredentials?.token;
  const previousSession = existingToken ? sessionStore.get(existingToken) : null;
    if (existingToken) {
      sessionStore.delete(existingToken);
    }

    const session = sessionStore.create({
      cookieHeader: auth.cookieHeader,
      csrfToken: auth.csrfToken,
      profile,
      credentials: { email, password },
      isAdmin: isAdminUser
    }, existingToken, ONE_YEAR_MS);

    credentialStore.save(profile.id, { email, password, token: session.id });
    scheduleConfig(session.id, config, settings);
    if (previousSession?.storesCache) {
      sessionStore.update(session.id, { storesCache: previousSession.storesCache });
    } else if (cachedStoreSnapshots.has(profile.id)) {
      sessionStore.update(session.id, { storesCache: cachedStoreSnapshots.get(profile.id) });
      cachedStoreSnapshots.delete(profile.id);
    } else {
      const persistedStoresCache = readStoresCache(profile.id);
      if (persistedStoresCache) {
        sessionStore.update(session.id, { storesCache: persistedStoresCache });
      }
    }
    const currentSession = sessionStore.get(session.id);
    const needsRefresh = !isStoreCacheFresh(currentSession);
    const refreshResult = needsRefresh ? triggerStoreRefresh(currentSession, { force: true, reason: 'login' }) : {};

    return res.json({
      token: session.id,
      profile,
      stores: sessionStore.get(session.id)?.storesCache?.data || [],
      config,
      isAdmin: isAdminUser,
      adminSettings: isAdminUser ? settings : undefined,
      storeRefreshJob: summarizeJob(refreshResult?.job),
      storesFresh: isStoreCacheFresh(sessionStore.get(session.id))
    });
  } catch (error) {
    console.error('Login fehlgeschlagen:', error.message);
    return res.status(401).json({ error: 'Login fehlgeschlagen' });
  }
});

app.post('/api/auth/logout', requireAuth, (req, res) => {
  if (req.session?.profile?.id && req.session?.storesCache) {
    cachedStoreSnapshots.set(req.session.profile.id, req.session.storesCache);
  }
  sessionStore.update(req.session.id, { uiLoggedOutAt: Date.now() });
  storeRefreshJobs.delete(req.session.id);
  res.json({ success: true, backgroundJobsActive: true });
});

app.get('/api/auth/session', requireAuth, async (req, res) => {
  const settings = adminConfig.readSettings();
  const { stores } = await loadStoresForSession(req.session, settings, { reason: 'session-check' });
  const config = readConfig(req.session.profile.id);
  const regularPickupMap = await buildRegularPickupMapForConfig(req.session, config);
  res.json({
    profile: req.session.profile,
    stores,
    config,
    regularPickupMap,
    isAdmin: !!req.session.isAdmin,
    adminSettings: req.session.isAdmin ? settings : undefined,
    storeRefreshJob: summarizeJob(getStoreRefreshJob(req.session.id)),
    storesFresh: isStoreCacheFresh(req.session)
  });
});

app.get('/api/profile', requireAuth, async (req, res) => {
  try {
    const details = await fetchProfileWithCache(req.session);
    res.json({
      profile: details || req.session.profile
    });
  } catch (error) {
    console.error('[PROFILE] Profil konnte nicht geladen werden:', error.message);
    res.status(500).json({ error: 'Profil konnte nicht geladen werden' });
  }
});

app.get('/api/location/nearest-store', requireAuth, async (req, res) => {
  const lat = Number(req.query.lat);
  const lon = Number(req.query.lon);
  if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
    return res.status(400).json({ error: 'Ungültige Koordinaten' });
  }
  try {
    await ensureStoreLocationIndex(req.session);
    const store = findNearestStoreLocation(lat, lon);
    res.json({ store });
  } catch (error) {
    console.error('[LOCATION] Reverse Lookup fehlgeschlagen:', error.message);
    res.status(500).json({ error: 'Ort konnte nicht bestimmt werden' });
  }
});

app.get('/api/store-watch/regions', requireAuth, requireAdmin, async (req, res) => {
  try {
    const details = await fetchProfileWithCache(req.session);
    const regions = Array.isArray(details?.regions)
      ? details.regions.filter((region) => Number(region?.classification) === 1)
      : [];
    res.json({ regions });
  } catch (error) {
    console.error('[STORE-WATCH] Regionen konnten nicht geladen werden:', error.message);
    res.status(500).json({ error: 'Regionen konnten nicht geladen werden' });
  }
});

app.get('/api/store-watch/regions/:regionId/stores', requireAuth, requireAdmin, async (req, res) => {
  const { regionId } = req.params;
  if (!regionId) {
    return res.status(400).json({ error: 'Region-ID fehlt' });
  }
  const forceRegionRefresh = req.query.force === '1';
  const forceStatusRefresh = req.query.forceStatus === '1';

  let basePayload = null;
  if (!forceRegionRefresh) {
    const cached = getCachedRegionStores(regionId);
    if (cached) {
      basePayload = cached;
    }
  }

  if (!basePayload) {
    try {
      const result = await withSessionRetry(
        req.session,
        () => foodsharingClient.fetchRegionStores(regionId, req.session.cookieHeader, req.session),
        { label: 'fetchRegionStores' }
      );
      basePayload = {
        total: Number(result?.total) || 0,
        stores: Array.isArray(result?.stores) ? result.stores : []
      };
      setCachedRegionStores(regionId, basePayload);
    } catch (error) {
      console.error(`[STORE-WATCH] Stores für Region ${regionId} konnten nicht geladen werden:`, error.message);
      return res.status(500).json({ error: 'Betriebe konnten nicht geladen werden' });
    }
  }

  const filteredStores = basePayload.stores
    .filter((store) => Number(store.cooperationStatus) === 5)
    .map((store) => ({ ...store, id: String(store.id) }));

  ingestStoreLocations(filteredStores);

  const { stores: enrichedStores, statusMeta } = await enrichStoresWithTeamStatus(
    filteredStores,
    req.session,
    { forceRefresh: forceStatusRefresh }
  );

  res.json({
    total: filteredStores.length,
    stores: enrichedStores,
    statusMeta
  });
});

app.get('/api/store-watch/subscriptions', requireAuth, requireAdmin, (req, res) => {
  const stores = readStoreWatch(req.session.profile.id);
  res.json({ stores });
});

app.post('/api/store-watch/subscriptions', requireAuth, requireAdmin, (req, res) => {
  if (!req.body || !Array.isArray(req.body.stores)) {
    return res.status(400).json({ error: 'Erwartet eine Liste von Betrieben' });
  }
  const previous = readStoreWatch(req.session.profile.id);
  const previousMap = new Map(previous.map((entry) => [entry.storeId, entry]));
  const normalized = [];
  req.body.stores.forEach((store) => {
    const storeId = store?.storeId || store?.id;
    const regionId = store?.regionId || store?.region?.id;
    if (!storeId || !regionId) {
      return;
    }
      const entry = {
        storeId: String(storeId),
        storeName: store?.storeName || store?.name || `Store ${storeId}`,
        regionId: String(regionId),
        regionName: store?.regionName || store?.region?.name || '',
        lastTeamSearchStatus: previousMap.get(String(storeId))?.lastTeamSearchStatus ?? null,
        lastStatusCheckAt: previousMap.get(String(storeId))?.lastStatusCheckAt ?? null
      };
      normalized.push(entry);
    });

  const persisted = writeStoreWatch(req.session.profile.id, normalized);
  const config = readConfig(req.session.profile.id);
  scheduleWithCurrentSettings(req.session.id, config);
  res.json({ success: true, stores: persisted });
});

app.post('/api/store-watch/check', requireAuth, requireAdmin, async (req, res) => {
  try {
    maintenanceMode.ensureInactive('Ad-hoc-Store-Watch ist während des Wartungsmodus deaktiviert.');
    const settings = adminConfig.readSettings();
    const summary = await runStoreWatchCheck(req.session.id, settings, {
      sendSummary: true,
      triggeredBy: 'manual'
    });
    res.json({ success: true, stores: Array.isArray(summary) ? summary : [] });
  } catch (error) {
    if (error?.code === 'MAINTENANCE_MODE_ACTIVE') {
      return res.status(error.statusCode || 503).json({
        error: error.message,
        maintenance: maintenanceSummary()
      });
    }
    console.error('[STORE-WATCH] Ad-hoc-Prüfung fehlgeschlagen:', error.message);
    res.status(500).json({ error: 'Ad-hoc-Prüfung fehlgeschlagen' });
  }
});

app.get('/api/user/preferences', requireAuth, async (req, res) => {
  const preferences = readPreferences(req.session.profile.id);
  let location = preferences.location;
  try {
    const details = await fetchProfileWithCache(req.session);
    const coords = details?.coordinates;
    const sanitized = sanitizeLocation({ lat: coords?.lat, lon: coords?.lon });
    if (sanitized) {
      const city = details?.city?.trim() || '';
      location = {
        ...sanitized,
        label: city || preferences.location?.label || 'Foodsharing-Profil',
        city,
        updatedAt: sanitized.updatedAt
      };
    }
  } catch (error) {
    console.error('[PREFERENCES] Profilstandort konnte nicht geladen werden:', error.message);
  }
  res.json({
    ...preferences,
    location
  });
});

app.post('/api/user/preferences/location', requireAuth, (req, res) => {
  const { lat, lon } = req.body || {};
  if (lat === null || lon === null || lat === undefined || lon === undefined) {
    const updated = writePreferences(req.session.profile.id, { location: null });
    return res.json({ location: updated.location });
  }
  const parsedLat = Number(lat);
  const parsedLon = Number(lon);
  if (
    !Number.isFinite(parsedLat) ||
    !Number.isFinite(parsedLon) ||
    parsedLat < -90 ||
    parsedLat > 90 ||
    parsedLon < -180 ||
    parsedLon > 180
  ) {
    return res.status(400).json({ error: 'Ungültige Koordinaten' });
  }
  const updated = writePreferences(req.session.profile.id, {
    location: { lat: parsedLat, lon: parsedLon }
  });
  res.json({ location: updated.location });
});

app.get('/api/journal', requireAuth, requireAdmin, (req, res) => {
  const entries = readJournal(req.session.profile.id);
  const normalized = entries.map((entry) => ({
    ...entry,
    images: Array.isArray(entry.images)
      ? entry.images.map((image) => ({
          ...image,
          url: `/api/journal/images/${image.id}`
        }))
      : []
  }));
  res.json(normalized);
});

app.post('/api/journal', requireAuth, requireAdmin, (req, res) => {
  const profileId = req.session.profile.id;
  const { storeId, storeName, pickupDate, note, reminder, images } = req.body || {};
  if (!storeId || !pickupDate) {
    return res.status(400).json({ error: 'Store und Abholdatum sind erforderlich' });
  }
  const parsedDate = new Date(`${pickupDate}T00:00:00`);
  if (Number.isNaN(parsedDate.getTime())) {
    return res.status(400).json({ error: 'Ungültiges Abholdatum' });
  }

  const entryId = uuid();
  const timestamp = new Date().toISOString();
  const normalizedReminder = normalizeJournalReminder(reminder);
  const maxImages = 5;
  const maxImageBytes = 5 * 1024 * 1024;
  const collectedImages = [];

  if (Array.isArray(images)) {
    images.slice(0, maxImages).forEach((image) => {
      const payload = parseDataUrl(image?.dataUrl);
      if (!payload) {
        return;
      }
      if (payload.buffer.length > maxImageBytes) {
        return;
      }
      const imageId = uuid();
      const ext = resolveImageExtension(payload.mimeType) || path.extname(image?.name || '');
      const filename = `${imageId}${ext || ''}`;
      const saved = saveJournalImage(profileId, {
        id: imageId,
        filename,
        buffer: payload.buffer
      });
      collectedImages.push({
        id: imageId,
        filename: saved.filename,
        originalName: image?.name || saved.filename,
        mimeType: payload.mimeType,
        uploadedAt: timestamp
      });
    });
  }

  const entry = {
    id: entryId,
    storeId: String(storeId),
    storeName: storeName || `Store ${storeId}`,
    pickupDate,
    note: note || '',
    reminder: normalizedReminder,
    images: collectedImages,
    createdAt: timestamp,
    updatedAt: timestamp,
    lastReminderAt: null
  };

  const entries = readJournal(profileId);
  entries.push(entry);
  writeJournal(profileId, entries);

  res.json({
    ...entry,
    images: collectedImages.map((image) => ({
      ...image,
      url: `/api/journal/images/${image.id}`
    }))
  });
});

app.put('/api/journal/:id', requireAuth, requireAdmin, (req, res) => {
  const profileId = req.session.profile.id;
  const { storeId, storeName, pickupDate, note, reminder, images, keepImageIds } = req.body || {};
  if (!storeId || !pickupDate) {
    return res.status(400).json({ error: 'Store und Abholdatum sind erforderlich' });
  }
  const parsedDate = new Date(`${pickupDate}T00:00:00`);
  if (Number.isNaN(parsedDate.getTime())) {
    return res.status(400).json({ error: 'Ungültiges Abholdatum' });
  }

  const entries = readJournal(profileId);
  const index = entries.findIndex((entry) => entry.id === req.params.id);
  if (index === -1) {
    return res.status(404).json({ error: 'Eintrag nicht gefunden' });
  }

  const existing = entries[index];
  const normalizedReminder = normalizeJournalReminder(reminder);
  const maxImages = 5;
  const maxImageBytes = 5 * 1024 * 1024;
  const keepIds = Array.isArray(keepImageIds) ? new Set(keepImageIds.map(String)) : new Set();
  const keptImages = (existing.images || []).filter((image) => keepIds.has(String(image.id)));
  const removedImages = (existing.images || []).filter((image) => !keepIds.has(String(image.id)));
  removedImages.forEach((image) => deleteJournalImage(profileId, image.filename));

  const availableSlots = Math.max(0, maxImages - keptImages.length);
  const collectedImages = [...keptImages];

  if (Array.isArray(images) && availableSlots > 0) {
    images.slice(0, availableSlots).forEach((image) => {
      const payload = parseDataUrl(image?.dataUrl);
      if (!payload) {
        return;
      }
      if (payload.buffer.length > maxImageBytes) {
        return;
      }
      const imageId = uuid();
      const ext = resolveImageExtension(payload.mimeType) || path.extname(image?.name || '');
      const filename = `${imageId}${ext || ''}`;
      const saved = saveJournalImage(profileId, {
        id: imageId,
        filename,
        buffer: payload.buffer
      });
      collectedImages.push({
        id: imageId,
        filename: saved.filename,
        originalName: image?.name || saved.filename,
        mimeType: payload.mimeType,
        uploadedAt: new Date().toISOString()
      });
    });
  }

  const pickupDateChanged = existing.pickupDate !== pickupDate;
  const reminderChanged = JSON.stringify(existing.reminder || {}) !== JSON.stringify(normalizedReminder);
  const updated = {
    ...existing,
    storeId: String(storeId),
    storeName: storeName || existing.storeName || `Store ${storeId}`,
    pickupDate,
    note: note || '',
    reminder: normalizedReminder,
    images: collectedImages,
    updatedAt: new Date().toISOString(),
    lastReminderAt: pickupDateChanged || reminderChanged ? null : existing.lastReminderAt
  };

  entries[index] = updated;
  writeJournal(profileId, entries);

  res.json({
    ...updated,
    images: collectedImages.map((image) => ({
      ...image,
      url: `/api/journal/images/${image.id}`
    }))
  });
});

app.delete('/api/journal/:id', requireAuth, requireAdmin, (req, res) => {
  const profileId = req.session.profile.id;
  const entries = readJournal(profileId);
  const filtered = entries.filter((entry) => entry.id !== req.params.id);
  if (filtered.length === entries.length) {
    return res.status(404).json({ error: 'Eintrag nicht gefunden' });
  }
  const removed = entries.find((entry) => entry.id === req.params.id);
  if (removed?.images) {
    removed.images.forEach((image) => deleteJournalImage(profileId, image.filename));
  }
  writeJournal(profileId, filtered);
  res.json({ success: true });
});

app.get('/api/journal/images/:imageId', requireAuth, requireAdmin, (req, res) => {
  const profileId = req.session.profile.id;
  const entries = readJournal(profileId);
  const imageEntry = entries
    .flatMap((entry) => (Array.isArray(entry.images) ? entry.images : []))
    .find((image) => image.id === req.params.imageId);
  if (!imageEntry?.filename) {
    return res.status(404).json({ error: 'Bild nicht gefunden' });
  }
  const baseDir = getProfileImageDir(profileId);
  const filePath = path.join(baseDir, imageEntry.filename);
  if (!filePath || !path.resolve(filePath).startsWith(path.resolve(baseDir))) {
    return res.status(404).json({ error: 'Bild nicht gefunden' });
  }
  res.sendFile(filePath);
});

app.get('/api/config', requireAuth, (req, res) => {
  const config = readConfig(req.session.profile.id);
  res.json(config);
});

app.post('/api/config', requireAuth, (req, res) => {
  if (!Array.isArray(req.body)) {
    return res.status(400).json({ error: 'Konfiguration muss ein Array sein' });
  }
  const currentConfig = readConfig(req.session.profile.id);
  if (req.body.length === 0 && currentConfig.length > 0) {
    return res.status(400).json({
      error: 'Leere Konfiguration wurde nicht gespeichert, weil bereits Einträge vorhanden sind.'
    });
  }
  writeConfig(req.session.profile.id, req.body);
  scheduleWithCurrentSettings(req.session.id, req.body);
  res.json({ success: true });
});

app.post('/api/config/check', requireAuth, (req, res) => {
  if (maintenanceMode.isActive()) {
    return res.status(503).json({
      error: 'Sofortprüfung ist während des Wartungsmodus deaktiviert.',
      maintenance: maintenanceSummary()
    });
  }
  const config = readConfig(req.session.profile.id);
  const settings = adminConfig.readSettings();
  runImmediatePickupCheck(req.session.id, config, settings).catch((error) => {
    console.error('[PICKUP] Sofortprüfung fehlgeschlagen:', error.message);
  });
  res.json({ success: true });
});

app.post('/api/config/last-pickup/refresh', requireAuth, async (req, res) => {
  try {
    const result = await refreshStaleLastPickupInfo(req.session.id);
    res.json({ success: true, ...result });
  } catch (error) {
    console.error('[PICKUP] Letzte Abholung konnte nicht aktualisiert werden:', error.message);
    res.status(502).json({ error: 'Letzte Abholung konnte nicht aktualisiert werden.' });
  }
});

app.get('/api/pickups/registered', requireAuth, async (req, res) => {
  try {
    const pickups = await withSessionRetry(
      req.session,
      () => foodsharingClient.fetchRegisteredPickups('current', req.session.cookieHeader, req.session),
      { label: 'fetchRegisteredPickups' }
    );
    res.json({
      pickups: (Array.isArray(pickups) ? pickups : []).map(normalizePickupEntry).filter(Boolean)
    });
  } catch (error) {
    console.error('[PICKUP] Eingetragene Pickups konnten nicht geladen werden:', error.message);
    res.status(502).json({ error: 'Eingetragene Foodsharing-Slots konnten nicht geladen werden.' });
  }
});

app.get('/api/notifications/settings', requireAuth, (req, res) => {
  const userSettings = readNotificationSettings(req.session.profile.id);
  const adminSettings = adminConfig.readSettings();
  res.json({
    settings: userSettings.notifications,
    capabilities: {
      ntfy: {
        enabled: !!(
          adminSettings.notifications?.ntfy?.enabled && adminSettings.notifications?.ntfy?.serverUrl
        ),
        serverUrl: adminSettings.notifications?.ntfy?.serverUrl || '',
        topicPrefix: adminSettings.notifications?.ntfy?.topicPrefix || ''
      },
      telegram: {
        enabled: !!(
          adminSettings.notifications?.telegram?.enabled && adminSettings.notifications?.telegram?.botToken
        )
      }
    }
  });
});

app.post('/api/notifications/settings', requireAuth, (req, res) => {
  const payload = {
    notifications: {
      ntfy: {
        enabled: !!req.body?.notifications?.ntfy?.enabled,
        topic: req.body?.notifications?.ntfy?.topic || '',
        serverUrl: req.body?.notifications?.ntfy?.serverUrl || ''
      },
      telegram: {
        enabled: !!req.body?.notifications?.telegram?.enabled,
        chatId: req.body?.notifications?.telegram?.chatId || ''
      }
    }
  };
  const updated = writeNotificationSettings(req.session.profile.id, payload);
  res.json(updated.notifications);
});

app.post('/api/notifications/test', requireAuth, async (req, res) => {
  try {
    await notificationService.sendTestNotification(req.session.profile.id, req.body?.channel);
    res.json({ success: true });
  } catch (error) {
    res.status(400).json({ error: error.message || 'Testbenachrichtigung fehlgeschlagen' });
  }
});

app.get('/api/stores/:storeId/regular-pickup', requireAuth, async (req, res) => {
  const { storeId } = req.params;
  if (!storeId) {
    return res.status(400).json({ error: 'Store-ID fehlt' });
  }
  try {
    const result = await getRegularPickupSchedule(req.session, storeId);
    res.json(result);
  } catch (error) {
    const message = error?.message || 'Regular-Pickup konnte nicht geladen werden';
    return res.json({ rules: [], error: message });
  }
});

app.get('/api/stores', requireAuth, async (req, res) => {
  const stores = req.session.storesCache?.data || [];
  const config = readConfig(req.session.profile.id);
  const regularPickupMap = await buildRegularPickupMapForConfig(req.session, config);
  res.json({ stores, regularPickupMap });
});

app.post('/api/stores/refresh', requireAuth, (req, res) => {
  if (maintenanceMode.isActive()) {
    return res.status(503).json({
      started: false,
      error: 'Store-Refresh ist während des Wartungsmodus deaktiviert.',
      maintenance: maintenanceSummary()
    });
  }
  const force = req.body?.force !== undefined ? !!req.body.force : true;
  const reason = req.body?.reason || 'manual';
  const result = triggerStoreRefresh(req.session, { force, reason });
  res.json({
    started: !!result.started,
    storesFresh: isStoreCacheFresh(req.session),
    job: summarizeJob(result.job),
    maintenance: result.maintenanceStatus || null
  });
});

app.get('/api/stores/refresh/status', requireAuth, (req, res) => {
  const job = getStoreRefreshJob(req.session.id);
  res.json({
    job: summarizeJob(job),
    storesFresh: isStoreCacheFresh(req.session),
    cacheUpdatedAt: req.session.storesCache?.fetchedAt || null
  });
});

app.get('/api/admin/settings', requireAuth, requireAdmin, (_req, res) => {
  res.json(adminConfig.readSettings());
});

app.post('/api/admin/settings', requireAuth, requireAdmin, (req, res) => {
  const updated = adminConfig.writeSettings(req.body || {});
  rescheduleAllSessions();
  res.json(updated);
});

app.get('/api/admin/background-jobs', requireAuth, requireAdmin, (_req, res) => {
  res.json(backgroundJobOrchestrator.getStatus());
});

app.get('/api/debug/requests', requireAuth, requireAdmin, (req, res) => {
  const limit = req.query?.limit ? Number(req.query.limit) : undefined;
  const logs = requestLogStore.list(limit);
  res.json({ logs });
});

app.get('/api/health', (_req, res) => {
  const background = backgroundJobOrchestrator.getStatus();
  res.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    backgroundJobs: {
      running: background.running,
      profiles: background.profiles.length
    }
  });
});

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

async function startServer() {
  try {
    await restoreSessionsFromDisk();
  } catch (error) {
    console.error('[RESTORE] Fehler bei der Session-Wiederherstellung:', error.message);
  }
  scheduleRegularPickupRefresh(adminConfig.readSettings());
  backgroundJobOrchestrator.start();
  startBackgroundStoreRefreshTicker();
  app.listen(port, () => {
    console.log(`Server läuft auf Port ${port}`);
  });
}

startServer().catch((error) => {
  console.error('[STARTUP] Unerwarteter Fehler:', error);
  process.exit(1);
});
