import { resolveUploadFileName } from "@/lib/media-manager/file-names"
import {
  generateArticlePrefix,
  generateDoctorPrefix,
  generateHospitalPrefix,
  generateProcedurePrefix,
} from "@/lib/media-manager/paths"
import { assertPersistedImageUrl, validateLocalImageFile } from "@/lib/media-manager/local-upload-policy"
import { canonicalPublicMediaUrl } from "@/lib/r2-media-url"
import type {
  CommitImageInput,
  CommittedImage,
  StagedImage,
  ArticleMediaSection,
  DoctorMediaSection,
  HospitalMediaSection,
  ProcedureMediaSection,
} from "@/lib/media-manager/types"

const MAX_DOCTOR_IMAGE_SIZE_BYTES = 2 * 1024 * 1024
const MAX_ENTITY_IMAGE_SIZE_BYTES = 5 * 1024 * 1024
const MAX_ARTICLE_IMAGE_SIZE_BYTES = 1 * 1024 * 1024

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)
  })
}

export interface CommitImageApiOptions {
  uploadUrl?: string
}

function isHospitalCommit(input: CommitImageInput): input is Extract<CommitImageInput, { entity: "hospital" }> {
  return input.entity === "hospital"
}

function isProcedureCommit(input: CommitImageInput): input is Extract<CommitImageInput, { entity: "procedure" }> {
  return input.entity === "procedure"
}

function isArticleCommit(input: CommitImageInput): input is Extract<CommitImageInput, { entity: "article" }> {
  return input.entity === "article"
}

function resolveCommitUploadFileName(input: CommitImageInput): string {
  return resolveUploadFileName({
    section: String(input.section),
    fileBaseName: input.fileBaseName,
    index: input.fileIndex,
    part: input.filePart,
  })
}

async function postUpload(
  uploadUrl: string,
  body: Record<string, unknown>
): Promise<CommittedImage> {
  const res = await fetch(uploadUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  })

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

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

  const publicId = json.publicId.trim()
  const url = canonicalPublicMediaUrl(json.url, publicId)
  assertPersistedImageUrl(url, { fieldPath: "upload response url", mode: "persist" })

  return {
    url,
    publicId,
    mediaKey: publicId,
    objectKey: publicId,
  }
}

/** Upload a single staged image to R2 via the doctors, hospitals, or procedures upload API. */
export async function commitImage(
  input: CommitImageInput,
  options: CommitImageApiOptions = {}
): Promise<CommittedImage> {
  if (isHospitalCommit(input)) {
    const hospitalSlug = assertHospitalSlugForMedia(input.hospitalSlug)
    const fileError = validateLocalImageFile(input.staged.file, MAX_ENTITY_IMAGE_SIZE_BYTES)
    if (fileError) throw new Error(fileError)

    const dataUri = await fileToDataUri(input.staged.file)
    const uploadUrl = options.uploadUrl ?? "/api/admin/hospitals/upload-image"

    return postUpload(uploadUrl, {
      dataUri,
      section: input.section,
      hospitalSlug,
      hospitalId: input.hospitalId,
      fileName: resolveCommitUploadFileName(input),
      fileBaseName: input.fileBaseName,
      fileIndex: input.fileIndex,
      filePart: input.filePart,
    })
  }

  if (isProcedureCommit(input)) {
    const procedureSlug = assertProcedureSlugForMedia(input.procedureSlug)
    const fileError = validateLocalImageFile(input.staged.file, MAX_ENTITY_IMAGE_SIZE_BYTES)
    if (fileError) throw new Error(fileError)

    const dataUri = await fileToDataUri(input.staged.file)
    const uploadUrl = options.uploadUrl ?? "/api/admin/procedures/upload-image"

    return postUpload(uploadUrl, {
      dataUri,
      section: input.section,
      procedureSlug,
      procedureId: input.procedureId,
      fileName: resolveCommitUploadFileName(input),
      fileBaseName: input.fileBaseName,
      fileIndex: input.fileIndex,
      filePart: input.filePart,
    })
  }

  if (isArticleCommit(input)) {
    const articleSlug = assertArticleSlugForMedia(input.articleSlug)
    const fileError = validateLocalImageFile(input.staged.file, MAX_ARTICLE_IMAGE_SIZE_BYTES)
    if (fileError) throw new Error(fileError)

    const dataUri = await fileToDataUri(input.staged.file)
    const uploadUrl = options.uploadUrl ?? "/api/admin/articles/upload-image"

    return postUpload(uploadUrl, {
      dataUri,
      section: input.section,
      articleSlug,
      articleId: input.articleId,
      fileName: resolveCommitUploadFileName(input),
      fileBaseName: input.fileBaseName,
      fileIndex: input.fileIndex,
      filePart: input.filePart,
    })
  }

  const doctorName = input.doctorName.trim()
  if (!doctorName) {
    throw new Error("Doctor name is required before uploading images.")
  }

  const fileError = validateLocalImageFile(input.staged.file, MAX_DOCTOR_IMAGE_SIZE_BYTES)
  if (fileError) throw new Error(fileError)

  const dataUri = await fileToDataUri(input.staged.file)
  const uploadUrl = options.uploadUrl ?? "/api/admin/doctors/upload-image"

  return postUpload(uploadUrl, {
    dataUri,
    section: input.section,
    doctorName,
    doctorId: input.doctorId,
    fileName: resolveCommitUploadFileName(input),
    fileBaseName: input.fileBaseName,
    fileIndex: input.fileIndex,
    filePart: input.filePart,
  })
}

/** Upload multiple staged images sequentially (stable ordering). */
export async function commitImages(
  items: CommitImageInput[],
  options: CommitImageApiOptions = {}
): Promise<CommittedImage[]> {
  const results: CommittedImage[] = []
  for (const item of items) {
    results.push(await commitImage(item, options))
  }
  return results
}

export function assertDoctorNameForMedia(doctorName: string | undefined | null): string {
  const name = typeof doctorName === "string" ? doctorName.trim() : ""
  if (!name) {
    throw new Error("Please enter the doctor name before uploading or saving images.")
  }
  return name
}

export function assertHospitalSlugForMedia(hospitalSlug: string | undefined | null): string {
  const slug = typeof hospitalSlug === "string" ? hospitalSlug.trim() : ""
  if (!slug) {
    throw new Error("Please enter the hospital slug before uploading or saving images.")
  }
  return slug
}

export function assertProcedureSlugForMedia(procedureSlug: string | undefined | null): string {
  const slug = typeof procedureSlug === "string" ? procedureSlug.trim() : ""
  if (!slug) {
    throw new Error("Please enter the procedure slug before uploading or saving images.")
  }
  return slug
}

export function assertArticleSlugForMedia(articleSlug: string | undefined | null): string {
  const slug = typeof articleSlug === "string" ? articleSlug.trim() : ""
  if (!slug) {
    throw new Error("Please enter the article slug before uploading or saving images.")
  }
  return slug
}

/** Resolve expected prefix for a doctor upload (client preview / validation). */
export function resolveDoctorUploadPrefix(doctorName: string, section: DoctorMediaSection): string {
  return generateDoctorPrefix(doctorName, section)
}

/** Resolve expected prefix for a hospital upload (client preview / validation). */
export function resolveHospitalUploadPrefix(hospitalSlug: string, section: HospitalMediaSection): string {
  return generateHospitalPrefix(hospitalSlug, section)
}

/** Resolve expected prefix for a procedure upload (client preview / validation). */
export function resolveProcedureUploadPrefix(
  procedureSlug: string,
  section: ProcedureMediaSection
): string {
  return generateProcedurePrefix(procedureSlug, section)
}

/** Resolve expected prefix for an article upload (client preview / validation). */
export function resolveArticleUploadPrefix(articleSlug: string, section: ArticleMediaSection): string {
  return generateArticlePrefix(articleSlug, section)
}

export type { StagedImage }
