import "server-only";

import { connectDB } from "@/lib/db";
import { getSettings } from "@/models";
import { normalizeContentPagesSettings } from "@/lib/content-pages-config";
import { normalizeHeaderSettings } from "@/lib/header-config";
import { normalizeFooterSettings } from "@/lib/footer-config";
import { getCredentialEnvSources, maskSecretHint } from "@/lib/credentials";
import { DEMO_MODE_MESSAGE, isDemoModeEnabled } from "@/lib/demo-mode";
import {
  CREDENTIAL_FIELD_PATHS,
  deleteCredentialPath,
  detectKeyMode,
  readCredentialPath,
  type CredentialMetaMap,
} from "@/lib/settings/credential-fields";

export function isPlainObject(value: unknown): value is Record<string, unknown> {
  return (
    typeof value === "object" &&
    value !== null &&
    !Array.isArray(value) &&
    Object.prototype.toString.call(value) === "[object Object]"
  );
}

/**
 * Strip credentials from a Settings document and attach the `_meta` block the
 * admin UI relies on (masked previews + "is set" flags, derived gateway key
 * modes, env-source hints, demo mode).
 *
 * Shared by the `GET /api/admin/settings` handler and the server-side settings
 * layout so both emit an identical payload — the layout can seed the client
 * store without a follow-up client fetch. Credential values never leave the
 * server: only presence flags and non-reversible masked previews are surfaced.
 */
export function sanitizeSettings(settings: unknown): Record<string, unknown> {
  if (typeof settings !== "object" || settings === null) return {};
  // Demo instances ship a publicly known admin login, so masked previews of
  // real secrets must never be served there — only the boolean "set" flags.
  const revealHints = !isDemoModeEnabled();
  const secretHint = (value: unknown): string | undefined =>
    revealHints ? maskSecretHint(value as string) : undefined;
  const doc = settings as { toObject?: () => Record<string, unknown> } & Record<
    string,
    unknown
  >;
  const safe = doc.toObject ? doc.toObject() : { ...doc };
  const objectSections = [
    "general",
    "appearance",
    "payment",
    "email",
    "orders",
    "shipping",
    "seo",
    "social",
    "analytics",
    "maintenance",
    "security",
    "pos",
    "multiVendorMode",
    "vendorConfig",
    "notifications",
    "storage",
    "aiSalesAgent",
    "aiAuthoring",
    "header",
    "footer",
    "contentPages",
  ] as const;

  // Old/corrupted records may contain null for sections that the UI expects as objects.
  for (const section of objectSections) {
    if (!isPlainObject(safe[section])) {
      safe[section] = {};
    }
  }
  safe.header = normalizeHeaderSettings(safe.header);
  safe.footer = normalizeFooterSettings(safe.footer);
  safe.contentPages = normalizeContentPagesSettings(safe.contentPages);

  // Every credential is replaced by a masked preview keyed on its dot-path, so
  // the admin UI can confirm which value is stored without receiving it.
  const credentials: CredentialMetaMap = {};
  for (const path of CREDENTIAL_FIELD_PATHS) {
    const value = readCredentialPath(safe, path);
    const isSet = typeof value === "string" && value.trim() !== "";
    const hint = isSet ? secretHint(value) : undefined;
    credentials[path] = hint ? { set: true, hint } : { set: isSet };
  }

  // Test/live is derived from the key prefix here because the raw gateway keys
  // are stripped below and never reach the browser.
  const keyModes = {
    stripe: detectKeyMode(readCredentialPath(safe, "payment.stripe.publishableKey")),
    razorpay: detectKeyMode(readCredentialPath(safe, "payment.razorpay.keyId")),
    paystack: detectKeyMode(readCredentialPath(safe, "payment.paystack.publicKey")),
  };

  for (const path of CREDENTIAL_FIELD_PATHS) {
    deleteCredentialPath(safe, path);
  }

  const security = safe.security;
  if (isPlainObject(security)) {
    delete security.smtpVerificationFingerprint;
  }

  safe._meta = {
    credentials,
    keyModes,
    demoMode: {
      enabled: isDemoModeEnabled(),
      message: DEMO_MODE_MESSAGE,
    },
    // Per-field flags reporting whether a `.env` fallback value is present.
    // Drives the read-only "Set via environment" hint in the admin UI.
    envSources: getCredentialEnvSources(),
  };
  return safe;
}

/**
 * Server-side settings loader for the admin settings layout. Returns the exact
 * same sanitized shape the client would otherwise fetch from
 * `GET /api/admin/settings`, JSON-normalized (Dates → ISO strings, ObjectIds →
 * strings) so it is safe to pass as a Server → Client component prop.
 *
 * Returns null on any failure so the client store can fall back to its own
 * fetch instead of rendering an error.
 */
export async function getSanitizedSettings(): Promise<Record<
  string,
  unknown
> | null> {
  try {
    await connectDB();
    const settings = await getSettings();
    return JSON.parse(JSON.stringify(sanitizeSettings(settings))) as Record<
      string,
      unknown
    >;
  } catch {
    return null;
  }
}
