import fs from "fs/promises";
import { createHmac } from "node:crypto";
import fsSync from "fs";
import path from "path";
import { defaultAdminState } from "./default-admin-state";
import { secureEqual } from "./security";
import { isMysqlEnabled, readMysqlState, recordMysqlDownloadAudit, writeMysqlState } from "./mysql-store";
import type { AdminState, AdminProduct, AnalyticsEvent, DownloadAccess, ImageSlot, LeadEvent, SalesDeskOrder } from "./mpc-types";
import { publicSupplierForRender, publicSupplierIsVisible } from "./public-supplier";
import { filterVisibleImageSlots, isRetiredImageSlotKey, migrateRetiredImageSlotUploads } from "./image-slot-aliases";
import { hardenImageSlotsForPublic, repairIndependentImageSlots } from "./image-slot-integrity";
import { ensureRuntimeStorage, runtimeStateDir, runtimeStateFile } from "./runtime-storage";

const runtimeDir = runtimeStateDir;
const runtimeFile = runtimeStateFile;
const cloneDefault = (): AdminState => JSON.parse(JSON.stringify(defaultAdminState));

const FREE_GUIDE_SET_ID = "mpc-free-explorer-pack";
const FREE_GUIDE_SET_TITLE = "Free MPC Marloth & Kruger Visitor Guide Set";
const FREE_GUIDE_SET_LABEL = "Guide Set";
const COMPLETE_GUIDE_SET_ID = "marloth-kruger-visitor-guide-set";
const COMPLETE_GUIDE_SET_TITLE = "MPC Marloth & Kruger Visitor Guide Set";
const PROTECTED_GATED_BUNDLES: Record<string, { title: string; shortTitle: string }> = {
  "mpc-premium-kruger-visitor-map-free": { title: "MPC Premium Kruger Visitor Map", shortTitle: "Kruger Visitor Map" },
  "marloth-park-international-visitor-checklist": { title: "Marloth Park International Visitor Checklist Pack", shortTitle: "International Visitor Checklist" },
};

const legacyProductTitlePattern = /founder safari vault|starter pack|starter bundle|comfort pack|comfort bundle|morning bundle|morning starter|lower sabie|safe exit|gate-time|exit buffer|sighting etiquette|weekend in marloth|weekend safari|kids safari|kidz safari|arrival pack/i;

function canonicalProductCatalogue(): AdminProduct[] {
  return cloneDefault().productCatalogue.map((product) => ({ ...product }));
}

function normalizeLaunchProductCatalogue(_: unknown): AdminProduct[] {
  return canonicalProductCatalogue();
}

function isFreeGuideSetReference(value: unknown) {
  const text = String(value || "").toLowerCase();
  return text.includes("free") || text.includes("complete visitor guide") || text.includes(FREE_GUIDE_SET_ID);
}

function isCompleteGuideSetReference(value: unknown) {
  const text = String(value || "").toLowerCase();
  return text.includes(COMPLETE_GUIDE_SET_ID) || text.includes("complete visitor guide") || text.includes("marloth & kruger visitor guide") || text.includes("marloth and kruger visitor guide");
}

function hasProductReference(lead: LeadEvent) {
  return Boolean(
    lead.routePackId ||
    lead.routePackTitle ||
    lead.selectedBundleSlug ||
    lead.selectedBundleTitle ||
    lead.guideTitle ||
    lead.recommendedProduct
  );
}

function normalizeLeadProductReference(lead: LeadEvent): LeadEvent {
  if (!hasProductReference(lead)) return lead;
  const protectedBundle = lead.selectedBundleSlug ? PROTECTED_GATED_BUNDLES[lead.selectedBundleSlug] : undefined;
  if (lead.source === "gated-email-delivery-request" && protectedBundle) {
    return {
      ...lead,
      routePackId: lead.selectedBundleSlug,
      routePackTitle: protectedBundle.shortTitle,
      guideTitle: protectedBundle.title,
      selectedBundleTitle: protectedBundle.title,
      recommendedProduct: lead.recommendedProduct ? protectedBundle.title : lead.recommendedProduct,
    };
  }

  const joined = [
    lead.routePackId,
    lead.routePackTitle,
    lead.selectedBundleSlug,
    lead.selectedBundleTitle,
    lead.guideTitle,
    lead.recommendedProduct,
    lead.source,
    lead.buttonType,
    lead.category,
  ].filter(Boolean).join(" ");

  if (lead.selectedBundleSlug === FREE_GUIDE_SET_ID || lead.source === "gated-email-delivery-request" || isFreeGuideSetReference(joined)) {
    return {
      ...lead,
      routePackId: lead.routePackId && lead.routePackId !== FREE_GUIDE_SET_ID ? FREE_GUIDE_SET_ID : lead.routePackId,
      routePackTitle: FREE_GUIDE_SET_LABEL,
      guideTitle: FREE_GUIDE_SET_TITLE,
      selectedBundleSlug: FREE_GUIDE_SET_ID,
      selectedBundleTitle: FREE_GUIDE_SET_TITLE,
      recommendedProduct: lead.recommendedProduct ? FREE_GUIDE_SET_TITLE : lead.recommendedProduct,
    };
  }

  if (isCompleteGuideSetReference(joined) || legacyProductTitlePattern.test(joined) || lead.routePackId || lead.routePackTitle || lead.selectedBundleTitle) {
    return {
      ...lead,
      routePackId: COMPLETE_GUIDE_SET_ID,
      routePackTitle: COMPLETE_GUIDE_SET_TITLE,
      selectedBundleSlug: lead.selectedBundleSlug && lead.selectedBundleSlug !== FREE_GUIDE_SET_ID ? COMPLETE_GUIDE_SET_ID : lead.selectedBundleSlug,
      selectedBundleTitle: lead.selectedBundleTitle ? COMPLETE_GUIDE_SET_TITLE : lead.selectedBundleTitle,
      guideTitle: lead.guideTitle && !isFreeGuideSetReference(lead.guideTitle) ? COMPLETE_GUIDE_SET_TITLE : lead.guideTitle,
      recommendedProduct: lead.recommendedProduct ? COMPLETE_GUIDE_SET_TITLE : lead.recommendedProduct,
    };
  }

  return lead;
}

function normalizeLaunchLeadEvents(leads?: LeadEvent[]) {
  return (leads || []).map(normalizeLeadProductReference);
}

function normalizeDownloadAccessProducts(accessItems?: DownloadAccess[]) {
  return (accessItems || []).map((access) => ({
    ...access,
    products: (access.products || []).map((product) => {
      if (product.productId && PROTECTED_GATED_BUNDLES[product.productId]) return product;
      if (product.productId === FREE_GUIDE_SET_ID || isFreeGuideSetReference(product.productId)) return { ...product, productId: FREE_GUIDE_SET_ID };
      return { ...product, productId: COMPLETE_GUIDE_SET_ID };
    }),
  }));
}

function normalizeSalesDeskOrders(orders?: SalesDeskOrder[]) {
  return (orders || []).map((order) => {
    const joined = [order.productId, order.productTitle].filter(Boolean).join(" ");
    if (isFreeGuideSetReference(joined)) return { ...order, productId: FREE_GUIDE_SET_ID, productTitle: FREE_GUIDE_SET_TITLE, amount: 0 };
    if (order.productId || order.productTitle || legacyProductTitlePattern.test(joined)) {
      return { ...order, productId: COMPLETE_GUIDE_SET_ID, productTitle: COMPLETE_GUIDE_SET_TITLE };
    }
    return order;
  });
}

function fileExistsSync(publicPath: string) {
  if (!publicPath.startsWith("/") || publicPath.includes("..") || publicPath.startsWith("//")) return false;
  try {
    fsSync.accessSync(path.join(process.cwd(), "public", publicPath.replace(/^\/+/, "")));
    return true;
  } catch {
    return false;
  }
}

function preferModernPackagedImage(url?: string) {
  if (!url || !url.startsWith("/images/") || !/\.(jpe?g|png)$/i.test(url)) return url;
  const modern = url.replace(/\.(jpe?g|png)$/i, ".webp");
  return fileExistsSync(modern) ? modern : url;
}

function upgradePackagedImageSlot(slot: ImageSlot): ImageSlot {
  return {
    ...slot,
    url: preferModernPackagedImage(slot.url) || slot.url,
    fallbackUrl: preferModernPackagedImage(slot.fallbackUrl) || slot.fallbackUrl,
  };
}

async function ensureRuntimeFile() {
  await ensureRuntimeStorage();
  await fs.mkdir(runtimeDir, { recursive: true });
  try {
    await fs.access(runtimeFile);
  } catch {
    await fs.writeFile(runtimeFile, JSON.stringify(cloneDefault(), null, 2), "utf8");
  }
}

function mergeImageSlots(baseSlots: ImageSlot[], parsedSlots?: ImageSlot[]) {
  if (!parsedSlots?.length) return repairIndependentImageSlots(hardenImageSlotsForPublic(filterVisibleImageSlots(baseSlots)), baseSlots);
  const parsedByKey = migrateRetiredImageSlotUploads(baseSlots, parsedSlots);
  const merged = baseSlots.map((baseSlot) => upgradePackagedImageSlot({ ...baseSlot, ...(parsedByKey.get(baseSlot.key) || {}) }));
  const baseKeys = new Set(baseSlots.map((slot) => slot.key));
  for (const slot of parsedSlots) {
    const isCustomUpload = /^\/uploads\/(admin|accommodation|local-listings)\//.test(String(slot.url || "")) || /^https?:\/\//i.test(String(slot.url || ""));
    if (!baseKeys.has(slot.key) && !isRetiredImageSlotKey(slot.key) && isCustomUpload && !slot.key.startsWith("img_optimized_")) {
      merged.push(upgradePackagedImageSlot(slot));
    }
  }
  return repairIndependentImageSlots(hardenImageSlotsForPublic(filterVisibleImageSlots(merged)), baseSlots);
}

const launchSupplierSlugs = new Set([
  "km-electrical",
  "mr-t-gas",
  "marloth-park-central-local-delivery",
  "kruger-pride-safaris",
  "kruger-unlocked",
  "kruger-africa-safaris",
  "wild-wheels-workshop",
  "bhubesi-management-services",
  "green-laundry-and-maintenance",
  "home-based-care-by-mel",
  "gasz-towing-mobile-mechanic",
  "caroline-frozen-meals",
  "nkosi-mobile-spa",
  "ingwe-supermarket-liquor-store",
  "at-wild-safaris",
  "busy-bee-shuttle",
  "marloth-dining-partner",
]);

function mergeLaunchSuppliers(baseSuppliers: AdminState["suppliers"], parsedSuppliers?: AdminState["suppliers"]) {
  const parsedList = Array.isArray(parsedSuppliers) ? parsedSuppliers : [];
  const parsedByKey = new Map<string, AdminState["suppliers"][number]>();
  for (const supplier of parsedList) {
    if (supplier.id) parsedByKey.set(supplier.id, supplier);
    if (supplier.slug) parsedByKey.set(supplier.slug, supplier);
  }

  // Launch listings keep their packaged defaults, but every admin-managed field
  // (especially imageSrc) must survive state reloads and sign-out. The previous
  // implementation returned the base records unchanged, which silently restored
  // the original listing image after a successful upload.
  const canonicalLaunchListings = baseSuppliers
    .filter((supplier) => launchSupplierSlugs.has(supplier.slug) || launchSupplierSlugs.has(supplier.id))
    .map((supplier) => {
      const merged = { ...supplier, ...(parsedByKey.get(supplier.id) || parsedByKey.get(supplier.slug) || {}) };
      if (merged.slug === "marloth-dining-partner") {
        return { ...merged, status: "live" as const, package: "featured" as const, homepageSponsorEligible: true, categorySponsorEligible: true };
      }
      return merged;
    });
  const canonicalKeys = new Set(canonicalLaunchListings.flatMap((supplier) => [supplier.slug, supplier.id].filter(Boolean)));
  const nonLaunchParsed = parsedList.filter((supplier) => !canonicalKeys.has(supplier.slug) && !canonicalKeys.has(supplier.id));
  const nonLaunchBase = baseSuppliers.filter((supplier) => !canonicalKeys.has(supplier.slug) && !canonicalKeys.has(supplier.id));
  const parsedKeys = new Set(nonLaunchParsed.flatMap((supplier) => [supplier.slug, supplier.id].filter(Boolean)));
  const missingNonLaunchBase = nonLaunchBase.filter((supplier) => !parsedKeys.has(supplier.slug) && !parsedKeys.has(supplier.id));
  return [...canonicalLaunchListings, ...nonLaunchParsed, ...missingNonLaunchBase];
}


function mergeAccommodationListings(baseListings: AdminState["accommodationListings"], parsedListings?: AdminState["accommodationListings"]) {
  const parsedList = Array.isArray(parsedListings) ? parsedListings : [];
  const parsedByKey = new Map<string, AdminState["accommodationListings"][number]>();
  for (const listing of parsedList) {
    if (listing.id) parsedByKey.set(listing.id, listing);
    if (listing.slug) parsedByKey.set(listing.slug, listing);
  }

  const preparedSlugs = new Set(
    baseListings
      .filter((listing) => (listing.categoryTags || []).some((tag) => String(tag).trim().toLowerCase() === "prelaunch-slot"))
      .map((listing) => listing.slug),
  );

  const prepared = baseListings
    .filter((listing) => preparedSlugs.has(listing.slug))
    .map((baseListing) => {
      const parsed = parsedByKey.get(baseListing.id) || parsedByKey.get(baseListing.slug);
      if (!parsed) return baseListing;

      const parsedImages = Array.isArray(parsed.images) ? parsed.images : [];
      const usedImageIds = new Set<string>();
      const images = (baseListing.images || []).map((baseImage, index) => {
        const match = parsedImages.find((image) => image.id === baseImage.id)
          || parsedImages.find((image) => image.sortOrder === baseImage.sortOrder)
          || parsedImages[index];
        if (match?.id) usedImageIds.add(match.id);
        return { ...baseImage, ...(match || {}) };
      });
      for (const image of parsedImages) {
        if (!usedImageIds.has(image.id) && !images.some((item) => item.id === image.id)) images.push(image);
      }

      const merged = { ...baseListing, ...parsed, images };
      const homepageTier = merged.tier === "top-featured" || merged.tier === "partner";
      return {
        ...merged,
        homepageEligible: homepageTier,
        minimumImagesRequired: Math.max(Number(baseListing.minimumImagesRequired || 0), Number(parsed.minimumImagesRequired || 0)),
      };
    });

  const preparedKeys = new Set(prepared.flatMap((listing) => [listing.id, listing.slug].filter(Boolean)));
  const customParsed = parsedList.filter((listing) => !preparedKeys.has(listing.id) && !preparedKeys.has(listing.slug));
  const remainingBase = baseListings.filter((listing) => !preparedKeys.has(listing.id) && !preparedKeys.has(listing.slug));
  const customKeys = new Set(customParsed.flatMap((listing) => [listing.id, listing.slug].filter(Boolean)));
  return [...prepared, ...customParsed, ...remainingBase.filter((listing) => !customKeys.has(listing.id) && !customKeys.has(listing.slug))];
}

function mergeState(parsed: Partial<AdminState>): AdminState {
  const base = cloneDefault();
  return {
    ...base,
    ...parsed,
    siteSettings: { ...base.siteSettings, ...(parsed.siteSettings || {}) },
    integrations: { ...base.integrations, ...(parsed.integrations || {}) },
    productCatalogue: normalizeLaunchProductCatalogue(parsed.productCatalogue || base.productCatalogue),
    downloadAccess: normalizeDownloadAccessProducts(parsed.downloadAccess || base.downloadAccess),
    salesDeskOrders: normalizeSalesDeskOrders(parsed.salesDeskOrders || base.salesDeskOrders || []),
    partnerBillingRecords: parsed.partnerBillingRecords || base.partnerBillingRecords || [],
    prelaunchProspects: parsed.prelaunchProspects || base.prelaunchProspects || [],
    prelaunchSettings: { ...base.prelaunchSettings, ...(parsed.prelaunchSettings || {}) },
    contentBlocks: parsed.contentBlocks || base.contentBlocks,
    featureFlags: parsed.featureFlags || base.featureFlags,
    launchChecklist: parsed.launchChecklist || base.launchChecklist,
    imageSlots: mergeImageSlots(base.imageSlots, parsed.imageSlots),
    homepageHeroMedia: { ...base.homepageHeroMedia, ...(parsed.homepageHeroMedia || {}) },
    suppliers: mergeLaunchSuppliers(base.suppliers, parsed.suppliers),
    accommodationListings: mergeAccommodationListings(base.accommodationListings, parsed.accommodationListings),
    gateInfo: parsed.gateInfo || base.gateInfo,
    emergencyContacts: parsed.emergencyContacts || base.emergencyContacts,
    legalPages: parsed.legalPages || base.legalPages,
    leadEvents: normalizeLaunchLeadEvents(parsed.leadEvents || base.leadEvents),
    analyticsEvents: parsed.analyticsEvents || base.analyticsEvents || [],
    partnerReportSchedules: parsed.partnerReportSchedules || base.partnerReportSchedules || [],
    partnerReportDeliveryLog: parsed.partnerReportDeliveryLog || base.partnerReportDeliveryLog || [],
  };
}

async function readFileStateOrNull(): Promise<AdminState | null> {
  try {
    const raw = await fs.readFile(runtimeFile, "utf8");
    const parsed = JSON.parse(raw) as Partial<AdminState>;
    return mergeState(parsed);
  } catch (error) {
    const code = error && typeof error === "object" && "code" in error ? String((error as NodeJS.ErrnoException).code || "") : "";
    if (code !== "ENOENT") console.warn("MPC runtime state mirror read failed:", error);
    return null;
  }
}

async function readFileState(): Promise<AdminState> {
  await ensureRuntimeFile();
  return (await readFileStateOrNull()) || mergeState(cloneDefault());
}

async function writeFileState(state: AdminState, preserveUpdatedAt = false) {
  await fs.mkdir(runtimeDir, { recursive: true, mode: 0o700 });
  const clean = {
    ...state,
    updatedAt: preserveUpdatedAt && state.updatedAt ? state.updatedAt : new Date().toISOString(),
    version: state.version || 1,
  };
  const temporary = `${runtimeFile}.${process.pid}.${Date.now()}.tmp`;
  try {
    await fs.writeFile(temporary, JSON.stringify(clean, null, 2), { encoding: "utf8", mode: 0o600, flag: "wx" });
    await fs.rename(temporary, runtimeFile);
    await fs.chmod(runtimeFile, 0o600).catch(() => undefined);
  } catch (error) {
    await fs.rm(temporary, { force: true }).catch(() => undefined);
    throw error;
  }
  return clean;
}

function stateUpdatedAt(state: Partial<AdminState> | null | undefined) {
  const value = new Date(String(state?.updatedAt || "")).getTime();
  return Number.isFinite(value) ? value : 0;
}

let adminStateWriteQueue: Promise<unknown> = Promise.resolve();

function selectedDriver() {
  return (process.env.MPC_DB_DRIVER || "").toLowerCase();
}

export async function getAdminState() {
  if (selectedDriver() === "supabase") {
    console.warn("MPC_DB_DRIVER=supabase is ignored in this cPanel-only build. Use MPC_DB_DRIVER=mysql or leave MPC_DB_DRIVER unset for local JSON state.");
  }
  if (!isMysqlEnabled()) return readFileState();

  let mysqlState: AdminState | null = null;
  let fileState: AdminState | null = null;
  try {
    const rawMysqlState = await readMysqlState();
    mysqlState = rawMysqlState ? mergeState(rawMysqlState) : null;
  } catch (error) {
    console.warn("MPC MySQL state read failed; using the persistent runtime mirror:", error);
  }
  fileState = await readFileStateOrNull();

  const selected: AdminState = stateUpdatedAt(fileState) > stateUpdatedAt(mysqlState) && fileState
    ? fileState
    : mysqlState || fileState || mergeState(cloneDefault());
  const merged = mergeState(selected);

  if (fileState !== selected) {
    void writeFileState(merged, true).catch((error) => console.warn("MPC runtime state mirror sync failed:", error));
  }
  if (mysqlState !== selected) {
    void writeMysqlState(merged).catch((error) => console.warn("MPC MySQL state resync deferred:", error));
  }
  return merged;
}

export async function saveAdminState(state: AdminState) {
  const operation = async () => {
    const merged = mergeState({ ...state, updatedAt: new Date().toISOString() });
    if (selectedDriver() === "supabase") {
      console.warn("MPC_DB_DRIVER=supabase is ignored in this cPanel-only build. Saving to local JSON unless MySQL is enabled.");
    }

    // Always commit an atomic file mirror before acknowledging the save. This
    // preserves image-slot changes across logout, Passenger restarts and short
    // MySQL outages. MySQL is then synchronized when enabled.
    const fileSaved = await writeFileState(merged, true);
    if (!isMysqlEnabled()) return fileSaved;
    try {
      const mysqlSaved = await writeMysqlState(fileSaved);
      if (!mysqlSaved) throw new Error("MySQL driver is enabled but no MySQL state was saved.");
      await writeFileState(mysqlSaved, true);
      return mysqlSaved;
    } catch (error) {
      console.warn("MPC MySQL state write failed; the persistent runtime mirror remains authoritative until MySQL recovers:", error);
      return fileSaved;
    }
  };

  const queued = adminStateWriteQueue.then(operation, operation);
  adminStateWriteQueue = queued.then(() => undefined, () => undefined);
  return queued;
}

export async function resetAdminState() {
  return saveAdminState(cloneDefault());
}

export async function addLeadEvent(event: Omit<LeadEvent, "id" | "receivedAt"> & { receivedAt?: string }) {
  const state = await getAdminState();
  const stamp = event.receivedAt || new Date().toISOString();
  const lead: LeadEvent = {
    id: `LEAD-${Date.now().toString(36).toUpperCase()}-${Math.random().toString(36).slice(2, 7).toUpperCase()}`,
    receivedAt: stamp,
    updatedAt: stamp,
    status: "new",
    priority: "normal",
    ...event,
  };
  state.leadEvents = [lead, ...(state.leadEvents || [])].slice(0, 5000);
  await saveAdminState(state);
  return lead;
}

function cleanAnalyticsString(value: unknown, max = 260) {
  return String(value || "").replace(/[\r\n\t]+/g, " ").trim().slice(0, max);
}

let analyticsMutationQueue: Promise<unknown> = Promise.resolve();

export async function addAnalyticsEvent(event: Omit<AnalyticsEvent, "id" | "createdAt"> & { createdAt?: string }) {
  const writeEvent = async () => {
    const eventType = cleanAnalyticsString(event.eventType, 80);
    const page = cleanAnalyticsString(event.page || event.path || "/", 260) || "/";
    if (!eventType || page.startsWith("/api") || page.startsWith("/admin")) return null;
    const state = await getAdminState();
    const stamp = event.createdAt || new Date().toISOString();
    const analyticsEvent: AnalyticsEvent = {
      id: `AN-${Date.now().toString(36).toUpperCase()}-${Math.random().toString(36).slice(2, 8).toUpperCase()}`,
      createdAt: stamp,
      eventType,
      page,
      path: cleanAnalyticsString(event.path || page, 320),
      label: cleanAnalyticsString(event.label, 220),
      target: cleanAnalyticsString(event.target, 420),
      source: cleanAnalyticsString(event.source, 120) || null,
      referrer: cleanAnalyticsString(event.referrer, 420) || null,
      utmSource: cleanAnalyticsString(event.utmSource, 120) || null,
      utmMedium: cleanAnalyticsString(event.utmMedium, 120) || null,
      utmCampaign: cleanAnalyticsString(event.utmCampaign, 160) || null,
      utmContent: cleanAnalyticsString(event.utmContent, 160) || null,
      utmTerm: cleanAnalyticsString(event.utmTerm, 160) || null,
      productId: cleanAnalyticsString(event.productId, 160) || null,
      supplierId: cleanAnalyticsString(event.supplierId, 160) || null,
      supplierName: cleanAnalyticsString(event.supplierName, 220) || null,
      accommodationId: cleanAnalyticsString(event.accommodationId, 160) || null,
      accommodationName: cleanAnalyticsString(event.accommodationName, 220) || null,
      sessionId: cleanAnalyticsString(event.sessionId, 120) || null,
      userAgent: cleanAnalyticsString(event.userAgent, 240) || null,
      metadata: event.metadata && typeof event.metadata === "object" ? event.metadata : null,
    };
    state.analyticsEvents = [analyticsEvent, ...(state.analyticsEvents || [])].slice(0, 20000);
    await saveAdminState(state);
    return analyticsEvent;
  };

  if (isMysqlEnabled()) return writeEvent();
  const queued = analyticsMutationQueue.then(writeEvent, writeEvent);
  analyticsMutationQueue = queued.then(() => undefined, () => undefined);
  return queued;
}

export async function updateLeadEvent(id: string, patch: Partial<LeadEvent>) {
  const state = await getAdminState();
  let updated: LeadEvent | null = null;
  state.leadEvents = (state.leadEvents || []).map((event) => {
    if (event.id !== id) return event;
    updated = { ...event, ...patch, updatedAt: new Date().toISOString() };
    return updated;
  });
  await saveAdminState(state);
  return updated;
}

export async function addDownloadAuditEvent(params: {
  accessToken: string;
  downloadAccessId?: string | null;
  productId?: string | null;
  eventType: string;
  ipAddress?: string | null;
  userAgent?: string | null;
  details?: Record<string, unknown> | null;
}) {
  if (!isMysqlEnabled()) return;
  await recordMysqlDownloadAudit(params);
}

export function publicState(state: AdminState) {
  return {
    siteSettings: state.siteSettings,
    imageSlots: state.imageSlots,
    homepageHeroMedia: state.homepageHeroMedia,
    suppliers: state.suppliers.filter(publicSupplierIsVisible).sort((a, b) => a.sortOrder - b.sortOrder).map(publicSupplierForRender),
    accommodationListings: (state.accommodationListings || []).filter((item) => item.status === "active").sort((a, b) => a.sortOrder - b.sortOrder),
    gateInfo: state.gateInfo.filter((g) => g.active),
    emergencyContacts: state.emergencyContacts.filter((e) => e.active).sort((a, b) => a.sortOrder - b.sortOrder),
    legalPages: state.legalPages.filter((p) => p.published),
    contentBlocks: state.contentBlocks,
    featureFlags: state.featureFlags,
    launchChecklist: state.launchChecklist,
    productCatalogue: (state.productCatalogue || []).filter((p) => p.active).sort((a, b) => a.sortOrder - b.sortOrder),
  };
}

function expectedAdminPassword() {
  return String(process.env.MPC_ADMIN_PASSWORD || "").trim();
}

function adminSessionSecret() {
  return String(process.env.MPC_ADMIN_SESSION_SECRET || process.env.MPC_ADMIN_PASSWORD || "").trim();
}

export function createAdminSessionToken(password: string | null) {
  const expected = expectedAdminPassword();
  const secret = adminSessionSecret();
  if (!expected || !secret || !password || !secureEqual(password, expected)) return null;
  return createHmac("sha256", secret).update(`mpc-admin-v2:${expected}`).digest("hex");
}

export function checkAdminPassword(passwordOrToken: string | null) {
  const expected = expectedAdminPassword();
  if (!expected || !passwordOrToken) return false;
  if (secureEqual(passwordOrToken, expected)) return true;
  const secret = adminSessionSecret();
  if (!secret) return false;
  const token = createHmac("sha256", secret).update(`mpc-admin-v2:${expected}`).digest("hex");
  return secureEqual(passwordOrToken, token);
}
