import { createHash } from "node:crypto"
import { ExecArgs } from "@medusajs/framework/types"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"
import { updateProductsWorkflow } from "@medusajs/medusa/core-flows"

type ProductImage = {
  id?: string
  url?: string
}

type ProductRecord = {
  id: string
  title?: string
  thumbnail?: string | null
  images?: ProductImage[]
  variants?: Array<{
    id: string
    thumbnail?: string | null
  }>
}

function sanitizeSegment(value: string) {
  return value
    .toLowerCase()
    .replace(/[^a-z0-9-_]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 60) || "item"
}

function getExtensionFromMime(contentType: string) {
  const value = contentType.toLowerCase()
  if (value.includes("png")) return "png"
  if (value.includes("jpeg") || value.includes("jpg")) return "jpg"
  if (value.includes("webp")) return "webp"
  if (value.includes("gif")) return "gif"
  return "bin"
}

function getExtensionFromUrl(url: string) {
  const lower = url.toLowerCase()
  if (lower.includes(".png")) return "png"
  if (lower.includes(".jpg") || lower.includes(".jpeg")) return "jpg"
  if (lower.includes(".webp")) return "webp"
  if (lower.includes(".gif")) return "gif"
  return ""
}

function joinUrl(base: string, key: string) {
  const cleanBase = base.replace(/\/+$/, "")
  return `${cleanBase}/${key.split("/").map(encodeURIComponent).join("/")}`
}

function chunk<T>(items: T[], size: number): T[][] {
  const out: T[][] = []
  for (let i = 0; i < items.length; i += size) {
    out.push(items.slice(i, i + size))
  }
  return out
}

async function listProducts(query: any) {
  const all: ProductRecord[] = []
  const take = 200
  let skip = 0

  while (true) {
    const { data } = await query.graph({
      entity: "product",
      fields: ["id", "title", "thumbnail", "images.id", "images.url", "variants.id", "variants.thumbnail"],
      pagination: { take, skip },
    })
    const rows = (data || []) as ProductRecord[]
    all.push(...rows)
    if (rows.length < take) {
      break
    }
    skip += take
  }

  return all
}

export default async function syncMedusaProductImagesToS3({ container, args }: ExecArgs) {
  const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
  const query = container.resolve(ContainerRegistrationKeys.QUERY)
  const dryRun = process.env.DRY_RUN === "1" || (args || []).includes("dry-run")

  const s3Region = process.env.S3_REGION || ""
  const s3Bucket = process.env.S3_BUCKET || ""
  const s3FileUrl = process.env.S3_FILE_URL || ""
  const s3Prefix = process.env.S3_PREFIX || "uploads/"
  const s3AccessKeyId = process.env.S3_ACCESS_KEY_ID || ""
  const s3SecretAccessKey = process.env.S3_SECRET_ACCESS_KEY || ""

  if (!s3Region || !s3Bucket || !s3FileUrl || !s3AccessKeyId || !s3SecretAccessKey) {
    throw new Error("Missing S3 configuration in environment variables.")
  }

  const s3 = new S3Client({
    region: s3Region,
    credentials: {
      accessKeyId: s3AccessKeyId,
      secretAccessKey: s3SecretAccessKey,
    },
  })

  const products = await listProducts(query)
  logger.info(`Scanning ${products.length} Medusa products...`)

  const uploadedCache = new Map<string, string>()
  const updates: Array<{
    id: string
    thumbnail?: string | null
    images?: Array<{ url: string }>
    variants?: Array<{ id: string; thumbnail: string }>
  }> = []
  let uploadedFiles = 0

  const migrateUrl = async (sourceUrl: string, keyPathPrefix: string) => {
    if (!sourceUrl) {
      return null
    }

    if (sourceUrl.startsWith(s3FileUrl)) {
      return sourceUrl
    }

    const cached = uploadedCache.get(sourceUrl)
    if (cached) {
      return cached
    }

    const response = await fetch(sourceUrl)
    if (!response.ok) {
      logger.warn(`Skip image (download failed ${response.status}): ${sourceUrl}`)
      return null
    }

    const bytes = new Uint8Array(await response.arrayBuffer())
    const contentType = response.headers.get("content-type") || "application/octet-stream"
    const ext = getExtensionFromUrl(sourceUrl) || getExtensionFromMime(contentType)
    const digest = createHash("sha1").update(bytes).digest("hex").slice(0, 16)
    const key = `${s3Prefix.replace(/\/?$/, "/")}${keyPathPrefix}/${digest}.${ext}`

    if (!dryRun) {
      await s3.send(
        new PutObjectCommand({
          Bucket: s3Bucket,
          Key: key,
          Body: bytes,
          ContentType: contentType,
          CacheControl: "public, max-age=31536000",
        })
      )
    }

    const migratedUrl = joinUrl(s3FileUrl, key)
    uploadedCache.set(sourceUrl, migratedUrl)
    uploadedFiles += 1
    return migratedUrl
  }

  for (const product of products) {
    const title = product.title || "product"
    const originalImages = Array.isArray(product.images) ? product.images : []
    const originalVariants = Array.isArray(product.variants) ? product.variants : []
    let nextThumbnail = product.thumbnail || null
    let changedProduct = false
    let changedVariants = false

    const migratedImages: Array<{ url: string }> = []
    const migratedVariants: Array<{ id: string; thumbnail: string }> = []
    const keyRoot = `medusa-products/${sanitizeSegment(title)}-${sanitizeSegment(product.id)}`

    for (const image of originalImages) {
      const sourceUrl = (image?.url || "").trim()
      if (!sourceUrl) continue

      if (sourceUrl.startsWith(s3FileUrl)) {
        migratedImages.push({ url: sourceUrl })
        continue
      }

      const migratedUrl = await migrateUrl(sourceUrl, keyRoot)
      if (!migratedUrl) continue

      migratedImages.push({ url: migratedUrl })
      changedProduct = true

      if (nextThumbnail === sourceUrl) {
        nextThumbnail = migratedUrl
      }
    }

    if (nextThumbnail && !nextThumbnail.startsWith(s3FileUrl)) {
      const migratedThumb = await migrateUrl(nextThumbnail, `${keyRoot}/thumb`)
      if (migratedThumb) {
        nextThumbnail = migratedThumb
        changedProduct = true
      }
    }

    for (const variant of originalVariants) {
      const sourceThumb = (variant.thumbnail || "").trim()
      if (!sourceThumb || sourceThumb.startsWith(s3FileUrl)) {
        continue
      }

      const migratedVariantThumb = await migrateUrl(sourceThumb, `${keyRoot}/variants/${sanitizeSegment(variant.id)}`)
      if (!migratedVariantThumb) {
        continue
      }

      migratedVariants.push({
        id: variant.id,
        thumbnail: migratedVariantThumb,
      })
      changedVariants = true
    }

    if (!changedProduct && !changedVariants) {
      continue
    }

    const payload: { id: string; thumbnail?: string | null; images?: Array<{ url: string }>; variants?: Array<{ id: string; thumbnail: string }> } = {
      id: product.id,
    }
    if (nextThumbnail && changedProduct) {
      payload.thumbnail = nextThumbnail
    }
    if (migratedImages.length && changedProduct) {
      payload.images = migratedImages
    }
    if (migratedVariants.length && changedVariants) {
      payload.variants = migratedVariants
    }

    updates.push(payload)
  }

  logger.info(`Products to update: ${updates.length}. Files uploaded: ${uploadedFiles}. Dry-run: ${dryRun ? "yes" : "no"}`)

  if (!dryRun && updates.length) {
    for (const batch of chunk(updates, 20)) {
      await updateProductsWorkflow(container).run({
        input: {
          products: batch,
        },
      })
    }
  }
}
