import { appendCmsPageUploadQuery } from "@/lib/cms-page-upload-query"
import { revokeStagedImage } from "@/lib/media-manager/stage"
import { findFirstBlobImageFieldPath } from "@/lib/media-manager/validate-persisted-payload"
import type { StagedImage } from "@/lib/media-manager/types"
import { canonicalPublicMediaUrl } from "@/lib/r2-media-url"
import type { Language } from "@/components/dashboard/shared/LanguageTabs"
import type { HomePageFormStateShape } from "@/lib/pages/home/form-types"
import type { ServicesSectionData } from "@/components/dashboard/home/ServicesSection"
import type { ProcessSectionData } from "@/components/dashboard/home/ProcessSection"
import type { ReviewsSectionData } from "@/components/dashboard/home/ReviewsSection"
import type { HospitalsSectionData } from "@/components/dashboard/home/HospitalsSection"
import type { CertificationsSectionData } from "@/components/dashboard/home/CertificationsSection"

const MAX_CMS_PAGE_IMAGE_SIZE_BYTES = 2 * 1024 * 1024

export interface CmsPendingSlotMeta {
  uploadEndpoint: string
  fileIndex?: number
  slot?: string
}

export interface CmsPendingEntry {
  staged: StagedImage
  meta: CmsPendingSlotMeta
}

export interface CmsPagePendingImages {
  slots: Map<string, CmsPendingEntry>
}

export function createCmsPagePendingImages(): CmsPagePendingImages {
  return { slots: new Map() }
}

/** Stable key: `{locale}:{section}:{index}:{slot}` */
export function cmsPendingKey(
  locale: Language,
  section: string,
  index?: number,
  slot?: string
): string {
  const parts = [locale, section.trim().toLowerCase()]
  if (typeof index === "number" && index >= 0) parts.push(String(index))
  if (slot?.trim()) parts.push(slot.trim().toLowerCase())
  return parts.join(":")
}

export function cmsPendingHasAny(pending: CmsPagePendingImages): boolean {
  return pending.slots.size > 0
}

export function setCmsPendingSlot(
  pending: CmsPagePendingImages,
  key: string,
  staged: StagedImage,
  meta: CmsPendingSlotMeta
): void {
  const existing = pending.slots.get(key)
  if (existing) revokeStagedImage(existing.staged)
  pending.slots.set(key, { staged, meta })
}

export function clearCmsPendingSlot(pending: CmsPagePendingImages, key: string): void {
  const existing = pending.slots.get(key)
  if (existing) revokeStagedImage(existing.staged)
  pending.slots.delete(key)
}

export function clearAllCmsPending(pending: CmsPagePendingImages): void {
  for (const entry of pending.slots.values()) {
    revokeStagedImage(entry.staged)
  }
  pending.slots.clear()
}

function fileToDataUri(file: File): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => {
      if (typeof reader.result === "string") resolve(reader.result)
      else reject(new Error("Failed to read image"))
    }
    reader.onerror = () => reject(new Error("Failed to read image"))
    reader.readAsDataURL(file)
  })
}

/** Upload one staged file to R2 via a CMS page upload-image route (on save only). */
export async function commitCmsPageImage(
  staged: StagedImage,
  meta: CmsPendingSlotMeta
): Promise<{ url: string; publicId: string }> {
  const dataUri = await fileToDataUri(staged.file)
  const uploadUrl = appendCmsPageUploadQuery(meta.uploadEndpoint, {
    fileIndex: meta.fileIndex,
    slot: meta.slot,
  })

  const res = await fetch(uploadUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ dataUri }),
  })

  const json = (await res.json().catch(() => ({}))) as {
    url?: string
    publicId?: string
    public_id?: string
    error?: string
  }

  if (!res.ok || !json.url || !(json.publicId || json.public_id)) {
    throw new Error(typeof json.error === "string" ? json.error : "Image upload failed.")
  }

  const publicId = (json.publicId ?? json.public_id ?? "").trim()
  const url = canonicalPublicMediaUrl(json.url, publicId)

  return { url, publicId }
}

async function commitAllSlots(pending: CmsPagePendingImages): Promise<void> {
  const keys = [...pending.slots.keys()]
  for (const key of keys) {
    const entry = pending.slots.get(key)
    if (!entry) continue
    await commitCmsPageImage(entry.staged, entry.meta)
    revokeStagedImage(entry.staged)
    pending.slots.delete(key)
  }
}

/** Commit every pending home-page image and merge URLs into locale form slices. */
export async function commitPendingHomePageLocales(
  locales: Record<Language, HomePageFormStateShape>,
  pending: CmsPagePendingImages
): Promise<Record<Language, HomePageFormStateShape>> {
  const out: Record<Language, HomePageFormStateShape> = {
    en: { ...locales.en },
    tr: { ...locales.tr },
    ar: { ...locales.ar },
  }

  const entries = [...pending.slots.entries()]
  for (const [key, entry] of entries) {
    const { url } = await commitCmsPageImage(entry.staged, entry.meta)
    revokeStagedImage(entry.staged)
    pending.slots.delete(key)

    const [locale, section, indexStr, slot] = key.split(":")
    const localeKey = locale as Language
    if (!out[localeKey]) continue
    const index = indexStr !== undefined && indexStr !== "" ? Number(indexStr) : undefined

    if (section === "services" && typeof index === "number") {
      out[localeKey] = {
        ...out[localeKey],
        services: applyServicesImage(out[localeKey].services, index, url),
      }
    } else if (section === "process" && typeof index === "number") {
      out[localeKey] = {
        ...out[localeKey],
        process: applyProcessImage(out[localeKey].process, index, url),
      }
    } else if (section === "reviews" && typeof index === "number") {
      out[localeKey] = {
        ...out[localeKey],
        reviews: applyReviewsAvatar(out[localeKey].reviews, index, url),
      }
    } else if (section === "hospitals" && typeof index === "number") {
      out[localeKey] = {
        ...out[localeKey],
        hospitals: applyHospitalsImage(out[localeKey].hospitals, index, url),
      }
    } else if (section === "certifications" && typeof index === "number") {
      out[localeKey] = {
        ...out[localeKey],
        certifications: applyCertificationsImage(out[localeKey].certifications, index, url),
      }
    }
  }

  const blobPath = findFirstBlobImageFieldPath(out)
  if (blobPath) {
    throw new Error(
      `Image (${blobPath}): please select the image again, then save. The staged file was not attached to the form.`
    )
  }

  return out
}

function applyServicesImage(data: ServicesSectionData, index: number, url: string): ServicesSectionData {
  const serviceDetails = data.serviceDetails.map((item, i) =>
    i === index ? { ...item, image: url } : item
  )
  return { ...data, serviceDetails }
}

function applyProcessImage(data: ProcessSectionData, index: number, url: string): ProcessSectionData {
  const steps = data.steps.map((item, i) => (i === index ? { ...item, image: url } : item))
  return { ...data, steps }
}

function applyReviewsAvatar(data: ReviewsSectionData, index: number, url: string): ReviewsSectionData {
  const reviews = data.reviews.map((item, i) => (i === index ? { ...item, avatar: url } : item))
  return { ...data, reviews }
}

function applyHospitalsImage(data: HospitalsSectionData, index: number, url: string): HospitalsSectionData {
  const hospitals = data.hospitals.map((item, i) => (i === index ? { ...item, image: url } : item))
  return { ...data, hospitals }
}

function applyCertificationsImage(
  data: CertificationsSectionData,
  index: number,
  url: string
): CertificationsSectionData {
  const certifications = data.certifications.map((item, i) =>
    i === index ? { ...item, image: url } : item
  )
  return { ...data, certifications }
}

export interface HeroImageSlice {
  image: string
  imagePublicId?: string
}

/** Commit pending hero images for a CMS page (procedures-page, blog-page, about-us, contact-us). */
export async function commitPendingHeroLocales<T extends { hero: HeroImageSlice }>(
  locales: Record<Language, T>,
  pending: CmsPagePendingImages
): Promise<Record<Language, T>> {
  const out: Record<Language, T> = {
    en: { ...locales.en, hero: { ...locales.en.hero } },
    tr: { ...locales.tr, hero: { ...locales.tr.hero } },
    ar: { ...locales.ar, hero: { ...locales.ar.hero } },
  }

  for (const [key, entry] of [...pending.slots.entries()]) {
    if (!key.endsWith(":hero")) continue
    const locale = key.split(":")[0] as Language
    const { url, publicId } = await commitCmsPageImage(entry.staged, entry.meta)
    revokeStagedImage(entry.staged)
    pending.slots.delete(key)
    if (!out[locale]) continue
    out[locale] = {
      ...out[locale],
      hero: { ...out[locale].hero, image: url, imagePublicId: publicId },
    }
  }

  const blobPath = findFirstBlobImageFieldPath(out)
  if (blobPath) {
    throw new Error(
      `Image (${blobPath}): please select the image again, then save. The staged file was not attached to the form.`
    )
  }

  return out
}

export interface HospitalsPageHeroSlice {
  image: string
  imagePublicId: string
  certifications: Array<{ logo?: string; image: string }>
}

export interface HospitalsPageFormSlice {
  hero: HospitalsPageHeroSlice
}

/** Commit hospitals-page hero + certification images. */
export async function commitPendingHospitalsPageLocales<T extends HospitalsPageFormSlice>(
  locales: Record<Language, T>,
  pending: CmsPagePendingImages
): Promise<Record<Language, T>> {
  const out: Record<Language, T> = {
    en: {
      ...locales.en,
      hero: {
        ...locales.en.hero,
        certifications: locales.en.hero.certifications.map((c) => ({ ...c })),
      },
    },
    tr: {
      ...locales.tr,
      hero: {
        ...locales.tr.hero,
        certifications: locales.tr.hero.certifications.map((c) => ({ ...c })),
      },
    },
    ar: {
      ...locales.ar,
      hero: {
        ...locales.ar.hero,
        certifications: locales.ar.hero.certifications.map((c) => ({ ...c })),
      },
    },
  }

  for (const [key, entry] of [...pending.slots.entries()]) {
    const parts = key.split(":")
    const locale = parts[0] as Language
    const section = parts[1]
    if (!out[locale]) continue

    const { url, publicId } = await commitCmsPageImage(entry.staged, entry.meta)
    revokeStagedImage(entry.staged)
    pending.slots.delete(key)

    if (section === "hero") {
      out[locale] = {
        ...out[locale],
        hero: { ...out[locale].hero, image: url, imagePublicId: publicId },
      }
      continue
    }

    if (section === "certifications") {
      const index = Number(parts[2])
      const slot = parts[3]
      if (!Number.isInteger(index) || index < 0) continue
      const certifications = out[locale].hero.certifications.map((cert, i) => {
        if (i !== index) return cert
        if (slot === "logo") return { ...cert, logo: url }
        return { ...cert, image: url }
      })
      out[locale] = {
        ...out[locale],
        hero: { ...out[locale].hero, certifications },
      }
    }
  }

  const blobPath = findFirstBlobImageFieldPath(out)
  if (blobPath) {
    throw new Error(
      `Image (${blobPath}): please select the image again, then save. The staged file was not attached to the form.`
    )
  }

  return out
}

export { MAX_CMS_PAGE_IMAGE_SIZE_BYTES }
