import type {
  AccommodationBillingCycle,
  AccommodationEnquiryRouting,
  AccommodationListing,
  AccommodationMonthlyFeeStatus,
  AccommodationStatus,
  AccommodationTier,
  AdminProduct,
  AdminState,
  DownloadAccess,
  FeatureFlag,
  ImageSlot,
  LeadEvent,
  LegalPage,
  Supplier,
  SupplierBillingCycle,
  SupplierBillingStatus,
  SupplierCategory,
  SupplierPackage,
  SupplierStatus,
} from "./mpc-types";
import { cleanString, safeEmail, safeExternalUrl, safePublicPathOrFallback } from "./security";

const supplierCategories = new Set<SupplierCategory>(["supplies", "food", "activities", "help", "accommodation", "cleaning", "gardening", "maintenance", "pool-service", "electrical", "plumbing", "security", "property-management", "visitor-services"]);
const supplierPackages = new Set<SupplierPackage>(["basic", "featured", "featured", "premium", "sponsor"]);
const supplierStatuses = new Set<SupplierStatus>(["draft", "pending", "live", "paused", "archived"]);
const supplierCycles = new Set<SupplierBillingCycle>(["free-basic", "monthly", "once-off-launch", "lead-fee", "sponsor"]);
const supplierBillingStatuses = new Set<SupplierBillingStatus>(["not-set", "not-invoiced", "awaiting-payment", "trial", "paid", "overdue", "comped", "cancelled"]);
const accommodationTiers = new Set<AccommodationTier>(["standard", "featured", "top-featured", "partner"]);
const accommodationStatuses = new Set<AccommodationStatus>(["draft", "active", "paused", "expired", "archived"]);
const accommodationFeeStatuses = new Set<AccommodationMonthlyFeeStatus>(["not-set", "not-invoiced", "awaiting-payment", "trial", "paid", "overdue", "comped", "cancelled"]);
const accommodationCycles = new Set<AccommodationBillingCycle>(["free-basic", "once-off-launch", "monthly", "annual", "lead-fee", "commission"]);
const accommodationRouting = new Set<AccommodationEnquiryRouting>(["mpc", "contact", "both"]);

function arr<T>(value: T[] | undefined): T[] {
  return Array.isArray(value) ? value : [];
}

function bool(value: unknown) {
  return Boolean(value);
}

function finiteNumber(value: unknown, fallback = 0, min = -999999, max = 999999) {
  const number = Number(value);
  if (!Number.isFinite(number)) return fallback;
  return Math.min(max, Math.max(min, number));
}

function safeSlug(value: unknown, fallback: string) {
  const slug = cleanString(value, 120)
    .toLowerCase()
    .replace(/&/g, "and")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 90);
  return slug || fallback;
}

function safeText(value: unknown, limit = 1200) {
  return cleanString(value, limit);
}

function safePublicOrHttpUrl(value: unknown, fallback = "") {
  const raw = safeText(value, 1200);
  if (!raw) return fallback;
  if (raw.startsWith("/")) return safePublicPathOrFallback(raw, fallback);
  return safeExternalUrl(raw) || fallback;
}

function safeImageUrl(value: unknown, fallback = "/images/slots/supplier-default.webp") {
  return safePublicOrHttpUrl(value, fallback);
}

function uniqueSlugErrors(items: Array<{ slug?: string; id?: string; name?: string }>, label: string) {
  const seen = new Map<string, string>();
  const errors: string[] = [];
  for (const item of items) {
    const slug = String(item.slug || "").trim();
    if (!slug) continue;
    const existing = seen.get(slug);
    if (existing && existing !== item.id) errors.push(`${label} duplicate slug: ${slug}`);
    seen.set(slug, item.id || item.name || slug);
  }
  return errors;
}

function sanitizeImageSlot(slot: ImageSlot): ImageSlot {
  const key = safeText(slot.key, 180);
  return {
    ...slot,
    key,
    label: safeText(slot.label, 200) || key,
    description: safeText(slot.description, 600),
    url: safeImageUrl(slot.url, slot.fallbackUrl || "/images/slots/supplier-default.webp"),
    fallbackUrl: safeImageUrl(slot.fallbackUrl, "/images/slots/supplier-default.webp"),
    alt: safeText(slot.alt, 220) || safeText(slot.label, 120) || key,
    updatedAt: safeText(slot.updatedAt, 60) || new Date().toISOString(),
    crop: slot.crop ? {
      desktopX: finiteNumber(slot.crop.desktopX, 50, 0, 100),
      desktopY: finiteNumber(slot.crop.desktopY, 50, 0, 100),
      mobileX: finiteNumber(slot.crop.mobileX, 50, 0, 100),
      mobileY: finiteNumber(slot.crop.mobileY, 50, 0, 100),
    } : undefined,
  };
}

function sanitizeSupplier(item: Supplier): Supplier {
  const id = safeText(item.id, 160) || `listing-${Date.now().toString(36)}`;
  const name = safeText(item.name, 180) || "Marloth listing";
  return {
    ...item,
    id,
    name,
    slug: safeSlug(item.slug || name, id),
    category: supplierCategories.has(item.category) ? item.category : "supplies",
    package: supplierPackages.has(item.package) ? item.package : "basic",
    status: supplierStatuses.has(item.status) ? item.status : "draft",
    subcategory: safeText(item.subcategory, 120),
    mpcCode: safeText(item.mpcCode, 80),
    description: safeText(item.description, 2200),
    serviceArea: safeText(item.serviceArea, 180),
    services: arr(item.services).map((value) => safeText(value, 120)).filter(Boolean),
    openLabel: safeText(item.openLabel, 180),
    deliveryLabel: safeText(item.deliveryLabel, 180),
    paymentOptions: safeText(item.paymentOptions, 200),
    whatsappNumber: safeText(item.whatsappNumber, 40),
    phoneNumber: safeText(item.phoneNumber, 40),
    email: safeEmail(item.email || ""),
    mapUrl: safePublicOrHttpUrl(item.mapUrl, ""),
    imageSrc: safeImageUrl(item.imageSrc),
    lastVerified: safeText(item.lastVerified, 120),
    supplierResponsibilityAccepted: bool(item.supplierResponsibilityAccepted),
    sortOrder: finiteNumber(item.sortOrder, 100, -10000, 100000),
    contactName: safeText(item.contactName, 160),
    contactEmail: safeEmail(item.contactEmail || item.email || ""),
    contactWhatsapp: safeText(item.contactWhatsapp || item.whatsappNumber, 50),
    billingCycle: item.billingCycle && supplierCycles.has(item.billingCycle) ? item.billingCycle : "free-basic",
    billingStatus: item.billingStatus && supplierBillingStatuses.has(item.billingStatus) ? item.billingStatus : "not-invoiced",
    packageName: safeText(item.packageName, 140),
    packagePrice: finiteNumber(item.packagePrice, 0, 0, 1000000),
    setupFeeAmount: finiteNumber(item.setupFeeAmount, 0, 0, 1000000),
    sponsorPackageName: safeText(item.sponsorPackageName, 140),
    sponsorStartDate: safeText(item.sponsorStartDate, 40),
    sponsorEndDate: safeText(item.sponsorEndDate, 40),
    nextRenewalDate: safeText(item.nextRenewalDate, 40),
    paymentReference: safeText(item.paymentReference, 180),
    paymentNotes: safeText(item.paymentNotes, 2000),
    verifiedLeadFeeAmount: finiteNumber(item.verifiedLeadFeeAmount, 0, 0, 100000),
    leadFeeStatus: item.leadFeeStatus && supplierBillingStatuses.has(item.leadFeeStatus) ? item.leadFeeStatus : "not-invoiced",
    homepageSponsorEligible: bool(item.homepageSponsorEligible),
    categorySponsorEligible: bool(item.categorySponsorEligible),
    lastMinuteBoostPrice: finiteNumber(item.lastMinuteBoostPrice, 0, 0, 100000),
    lastMinuteBoostStartDate: safeText(item.lastMinuteBoostStartDate, 40),
    lastMinuteBoostEndDate: safeText(item.lastMinuteBoostEndDate, 40),
    internalNotes: safeText(item.internalNotes, 6000),
  };
}

function sanitizeAccommodation(item: AccommodationListing): AccommodationListing {
  const id = safeText(item.id, 160) || `accommodation-${Date.now().toString(36)}`;
  const name = safeText(item.name, 180) || "Marloth holiday home";
  const tier = accommodationTiers.has(item.tier) ? item.tier : "standard";
  const defaultBillingCycle = tier === "standard" ? "free-basic" : tier === "featured" ? "once-off-launch" : "monthly";
  return {
    ...item,
    id,
    name,
    slug: safeSlug(item.slug || name, id),
    tagline: safeText(item.tagline, 240),
    locationLabel: safeText(item.locationLabel, 180),
    description: safeText(item.description, 4000),
    shortDescription: safeText(item.shortDescription, 600),
    tier,
    status: accommodationStatuses.has(item.status) ? item.status : "paused",
    monthlyFeeStatus: accommodationFeeStatuses.has(item.monthlyFeeStatus) ? item.monthlyFeeStatus : "not-set",
    billingCycle: item.billingCycle && accommodationCycles.has(item.billingCycle) ? item.billingCycle : defaultBillingCycle,
    featured: bool(item.featured),
    partnerBadge: bool(item.partnerBadge),
    ownerName: safeText(item.ownerName, 160),
    ownerEmail: safeEmail(item.ownerEmail || ""),
    ownerPhone: safeText(item.ownerPhone, 50),
    ownerWhatsapp: safeText(item.ownerWhatsapp, 50),
    enquiryRecipientEmail: safeEmail(item.enquiryRecipientEmail || ""),
    enquiryRouting: accommodationRouting.has(item.enquiryRouting) ? item.enquiryRouting : "mpc",
    whatsappNumber: safeText(item.whatsappNumber, 50),
    phoneNumber: safeText(item.phoneNumber, 50),
    email: safeEmail(item.email || ""),
    websiteUrl: safePublicOrHttpUrl(item.websiteUrl, ""),
    externalBookingUrl: safePublicOrHttpUrl(item.externalBookingUrl, ""),
    externalBookingLabel: safeText(item.externalBookingLabel, 80) || "Booking website",
    mapUrl: safePublicOrHttpUrl(item.mapUrl, ""),
    latitude: safeText(item.latitude, 40),
    longitude: safeText(item.longitude, 40),
    sleeps: finiteNumber(item.sleeps, 0, 0, 99),
    bedrooms: finiteNumber(item.bedrooms, 0, 0, 40),
    bathrooms: finiteNumber(item.bathrooms, 0, 0, 40),
    priceFromLabel: safeText(item.priceFromLabel, 120),
    amenities: arr(item.amenities).map((value) => safeText(value, 120)).filter(Boolean).slice(0, 60),
    bestFor: arr(item.bestFor).map((value) => safeText(value, 120)).filter(Boolean).slice(0, 40),
    houseRules: arr(item.houseRules).map((value) => safeText(value, 220)).filter(Boolean).slice(0, 40),
    images: arr(item.images).map((image, index) => ({
      id: safeText(image.id, 160) || `image-${index}`,
      url: safeImageUrl(image.url, "/images/stay/stay-exterior.webp"),
      alt: safeText(image.alt, 220) || name,
      caption: safeText(image.caption, 260),
      sortOrder: finiteNumber(image.sortOrder, (index + 1) * 10, -10000, 100000),
    })),
    sortOrder: finiteNumber(item.sortOrder, 100, -10000, 100000),
    createdAt: safeText(item.createdAt, 80) || new Date().toISOString(),
    updatedAt: safeText(item.updatedAt, 80) || new Date().toISOString(),
    internalNotes: safeText(item.internalNotes, 6000),
    launchPackagePrice: finiteNumber(item.launchPackagePrice, 0, 0, 1000000),
    setupFeeAmount: finiteNumber(item.setupFeeAmount, 0, 0, 1000000),
    listingStartDate: safeText(item.listingStartDate, 40),
    nextRenewalDate: safeText(item.nextRenewalDate, 40),
    paymentReference: safeText(item.paymentReference, 180),
    paymentNotes: safeText(item.paymentNotes, 2000),
    autoExpireDate: safeText(item.autoExpireDate, 40),
    packageName: safeText(item.packageName, 120),
    packageDescription: safeText(item.packageDescription, 800),
    packageStartDate: safeText(item.packageStartDate, 40),
    packageExpiryDate: safeText(item.packageExpiryDate, 40),
    homepageEligible: bool(item.homepageEligible),
    categoryTags: arr(item.categoryTags).map((value) => safeText(value, 80)).filter(Boolean).slice(0, 20),
    searchKeywords: arr(item.searchKeywords).map((value) => safeText(value, 80)).filter(Boolean).slice(0, 30),
    minimumImagesRequired: finiteNumber(item.minimumImagesRequired, 3, 0, 20),
    ownerReportNotes: safeText(item.ownerReportNotes, 2000),
    lastOwnerReportAt: safeText(item.lastOwnerReportAt, 80),
    ownerReportEmailEnabled: bool(item.ownerReportEmailEnabled),
    verifiedLeadFeeAmount: finiteNumber(item.verifiedLeadFeeAmount, 0, 0, 100000),
    leadFeeStatus: item.leadFeeStatus && accommodationFeeStatuses.has(item.leadFeeStatus) ? item.leadFeeStatus : "not-invoiced",
    lastMinuteBoostPrice: finiteNumber(item.lastMinuteBoostPrice, 0, 0, 100000),
    lastMinuteBoostStartDate: safeText(item.lastMinuteBoostStartDate, 40),
    lastMinuteBoostEndDate: safeText(item.lastMinuteBoostEndDate, 40),
    founderLaunchMonths: finiteNumber(item.founderLaunchMonths, 0, 0, 24),
    performanceModelEnabled: bool(item.performanceModelEnabled),
    commissionPercent: finiteNumber(item.commissionPercent, 0, 0, 100),
    revenueNotes: safeText(item.revenueNotes, 2000),
  };
}

function sanitizeProduct(item: AdminProduct): AdminProduct {
  const id = safeText(item.id, 160) || `product-${Date.now().toString(36)}`;
  const title = safeText(item.title, 220) || "MPC product";
  return {
    ...item,
    id,
    title,
    slug: safeSlug(item.slug || title, id),
    badge: safeText(item.badge, 80),
    description: safeText(item.description, 2600),
    orderHref: safePublicOrHttpUrl(item.orderHref, "/free-guides#free-guide-gate"),
    sampleHref: item.sampleHref ? safePublicOrHttpUrl(item.sampleHref, "") : undefined,
    previewHref: item.previewHref ? safePublicOrHttpUrl(item.previewHref, "") : undefined,
    publicFile: item.publicFile ? safePublicPathOrFallback(item.publicFile, "") : undefined,
    privateFile: item.privateFile ? safeText(item.privateFile, 260).replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.\./g, "") : undefined,
    imageSrc: item.imageSrc ? safeImageUrl(item.imageSrc, "") : undefined,
    coverSrc: item.coverSrc ? safeImageUrl(item.coverSrc, "") : undefined,
    normalPrice: finiteNumber(item.normalPrice, 0, 0, 1000000),
    prelaunchPrice: finiteNumber(item.prelaunchPrice, 0, 0, 1000000),
    priceLabel: safeText(item.priceLabel, 80),
    normalPriceLabel: safeText(item.normalPriceLabel, 80),
    active: bool(item.active),
    featured: bool(item.featured),
    prelaunchOnly: bool(item.prelaunchOnly),
    sortOrder: finiteNumber(item.sortOrder, 100, -10000, 100000),
  };
}

function sanitizeLead(event: LeadEvent): LeadEvent {
  return {
    ...event,
    id: safeText(event.id, 160),
    contactName: safeText(event.contactName, 180),
    contactValue: safeText(event.contactValue, 260),
    adminNotes: safeText(event.adminNotes, 5000),
    campaign: safeText(event.campaign, 5000),
    userAgent: safeText(event.userAgent, 700),
  };
}

function sanitizeLegal(page: LegalPage): LegalPage {
  const title = safeText(page.title, 220) || "Legal page";
  return {
    ...page,
    slug: safeSlug(page.slug || title, "legal-page"),
    title,
    body: safeText(page.body, 50000),
    version: safeText(page.version, 80),
    lastUpdated: safeText(page.lastUpdated, 40),
    published: bool(page.published),
  };
}

function sanitizeFeatureFlag(flag: FeatureFlag): FeatureFlag {
  const key = safeText(flag.key, 160);
  return {
    ...flag,
    key,
    label: safeText(flag.label, 180) || key,
    description: safeText(flag.description, 1000),
    category: safeText(flag.category, 120),
    enabled: bool(flag.enabled),
    updatedAt: safeText(flag.updatedAt, 80) || new Date().toISOString(),
  };
}

export function validateAdminStateBeforeSave(input: unknown): { ok: true; state: AdminState; warnings: string[] } | { ok: false; error: string } {
  if (!input || typeof input !== "object") return { ok: false, error: "Admin state payload must be an object." };
  const raw = input as AdminState;
  if (!raw.siteSettings || typeof raw.siteSettings !== "object") return { ok: false, error: "Admin state is missing siteSettings." };
  const errors: string[] = [];
  const sanitized: AdminState = {
    ...raw,
    version: Math.max(4, Math.floor(finiteNumber(raw.version, 4, 1, 999))),
    updatedAt: new Date().toISOString(),
    siteSettings: {
      ...raw.siteSettings,
      siteName: safeText(raw.siteSettings.siteName, 160) || "Marloth Park Central",
      tagline: safeText(raw.siteSettings.tagline, 240),
      siteUrl: safePublicOrHttpUrl(raw.siteSettings.siteUrl, ""),
      partnerEmail: safeEmail(raw.siteSettings.partnerEmail || ""),
      whatsappNumber: safeText(raw.siteSettings.whatsappNumber, 50),
      footerDisclaimer: safeText(raw.siteSettings.footerDisclaimer, 2000),
      facebookUrl: safePublicOrHttpUrl(raw.siteSettings.facebookUrl, ""),
      instagramUrl: safePublicOrHttpUrl(raw.siteSettings.instagramUrl, ""),
      shopBaseUrl: safePublicOrHttpUrl(raw.siteSettings.shopBaseUrl, ""),
      wooProductPathPrefix: safeText(raw.siteSettings.wooProductPathPrefix, 120),
    },
    integrations: {
      ...raw.integrations,
      leadWebhookUrl: safePublicOrHttpUrl(raw.integrations?.leadWebhookUrl, ""),
      supplierOnboardingWebhookUrl: safePublicOrHttpUrl(raw.integrations?.supplierOnboardingWebhookUrl, ""),
      notificationEmail: safeEmail(raw.integrations?.notificationEmail || ""),
      stayEnquiryRecipientEmail: safeEmail(raw.integrations?.stayEnquiryRecipientEmail || ""),
      guideDeliveryReplyToEmail: safeEmail(raw.integrations?.guideDeliveryReplyToEmail || ""),
      webhookSecret: safeText(raw.integrations?.webhookSecret, 500),
      lastEmailStatus: safeText(raw.integrations?.lastEmailStatus, 1000),
      lastLeadWebhookStatus: safeText(raw.integrations?.lastLeadWebhookStatus, 1000),
      lastSupplierWebhookStatus: safeText(raw.integrations?.lastSupplierWebhookStatus, 1000),
    },
    imageSlots: arr(raw.imageSlots).map(sanitizeImageSlot),
    homepageHeroMedia: {
      mode: raw.homepageHeroMedia?.mode === "video" && /^\/uploads\/hero-video\/[A-Za-z0-9._-]+\.mp4$/i.test(String(raw.homepageHeroMedia?.videoUrl || "")) ? "video" : "image",
      videoUrl: /^\/uploads\/hero-video\/[A-Za-z0-9._-]+\.mp4$/i.test(String(raw.homepageHeroMedia?.videoUrl || "")) ? String(raw.homepageHeroMedia.videoUrl) : "",
      posterSlotKey: "img_heroes_homepage_hero",
      originalFilename: safeText(raw.homepageHeroMedia?.originalFilename, 180),
      mimeType: raw.homepageHeroMedia?.videoUrl ? "video/mp4" : undefined,
      fileSizeBytes: raw.homepageHeroMedia?.fileSizeBytes ? finiteNumber(raw.homepageHeroMedia.fileSizeBytes, 0, 0, 80 * 1024 * 1024) : undefined,
      updatedAt: safeText(raw.homepageHeroMedia?.updatedAt, 80) || new Date().toISOString(),
    },
    suppliers: arr(raw.suppliers).map(sanitizeSupplier),
    accommodationListings: arr(raw.accommodationListings).map(sanitizeAccommodation),
    productCatalogue: arr(raw.productCatalogue).map(sanitizeProduct),
    downloadAccess: arr(raw.downloadAccess).map((item: DownloadAccess) => ({ ...item, customerEmail: safeEmail(item.customerEmail || ""), customerName: safeText(item.customerName, 180), internalNotes: safeText(item.internalNotes, 5000) })),
    leadEvents: arr(raw.leadEvents).map(sanitizeLead),
    legalPages: arr(raw.legalPages).map(sanitizeLegal),
    featureFlags: arr(raw.featureFlags).map(sanitizeFeatureFlag),
    contentBlocks: arr(raw.contentBlocks).map((item) => ({ ...item, key: safeText(item.key, 180), label: safeText(item.label, 200), value: safeText(item.value, 50000), updatedAt: safeText(item.updatedAt, 80) || new Date().toISOString() })),
    launchChecklist: arr(raw.launchChecklist).map((item) => ({ ...item, key: safeText(item.key, 180), label: safeText(item.label, 200), description: safeText(item.description, 2000), category: safeText(item.category, 120), notes: safeText(item.notes, 5000), blocking: bool(item.blocking), updatedAt: safeText(item.updatedAt, 80) || new Date().toISOString() })),
    gateInfo: arr(raw.gateInfo).map((item) => ({ ...item, sourceUrl: safePublicOrHttpUrl(item.sourceUrl, ""), notes: safeText(item.notes, 2000) })),
    emergencyContacts: arr(raw.emergencyContacts).map((item) => ({ ...item, phoneNumber: safeText(item.phoneNumber, 50), whatsappNumber: safeText(item.whatsappNumber, 50), description: safeText(item.description, 1600), active: bool(item.active), sortOrder: finiteNumber(item.sortOrder, 100, -10000, 100000) })),
  };

  errors.push(...uniqueSlugErrors(sanitized.productCatalogue, "Product"));
  errors.push(...uniqueSlugErrors(sanitized.suppliers, "Listing"));
  errors.push(...uniqueSlugErrors(sanitized.accommodationListings, "Accommodation"));
  errors.push(...uniqueSlugErrors(sanitized.legalPages, "Legal page"));

  if (!sanitized.imageSlots.length) errors.push("Admin state must contain image slots.");
  if (!sanitized.productCatalogue.length) errors.push("Admin state must contain product catalogue records.");

  const activePartners = sanitized.accommodationListings.filter((item) => item.status === "active" && (item.tier === "partner" || item.partnerBadge));
  const activeTopFeatured = sanitized.accommodationListings.filter((item) => item.status === "active" && item.tier === "featured");
  const activeFeatured = sanitized.accommodationListings.filter((item) => item.status === "active" && (item.tier === "featured" || item.tier === "top-featured" || item.featured));
  if (activePartners.length > 1) errors.push(`Only 1 active Featured accommodation listing is allowed. Currently ${activePartners.length}.`);
  if (activeTopFeatured.length > 6) errors.push(`Only 6 active Founder Featured accommodation listings are allowed. Currently ${activeTopFeatured.length}.`);
  if (activeFeatured.length > 24) errors.push(`Only 24 active paid featured accommodation listings are allowed. Currently ${activeFeatured.length}.`);

  if (errors.length) return { ok: false, error: errors.slice(0, 5).join(" ") };
  return { ok: true, state: sanitized, warnings: [] };
}
