import { randomUUID } from "crypto"
import { NextRequest, NextResponse } from "next/server"
import { prisma } from "@/lib/prisma"
import {
  buildContentAudit,
  contentAuditArticleInclude,
  contentAuditUpdateData,
  touchUpdatedAt,
  requireContentAuditActor,
} from "@/lib/content-audit"
import {
  deleteImage,
  deletePrefixIfEmpty,
  extractMediaKeysFromHtml,
  isManagedArticleInlineImageKey,
  purgePrefixExcept,
} from "@/lib/r2-storage"
import {
  applyArticleSlugMediaChange,
  articleMediaBaseForSlug,
} from "@/lib/article-media"
import {
  collectArticleMediaKeysFromPersistedArticle,
  normalizeArticleSavePayloadMediaToCurrentSlug,
  reconcileArticleMediaAfterSave,
} from "@/lib/media-manager/slug-media-reconcile"
import { sanitizeArticleHtml } from "@/lib/sanitize-article-html"
import { normalizePersistedMediaUrls } from "@/lib/normalize-persisted-media-urls"
import { parseArticleTagsFromBody } from "@/lib/article-tags"
import {
  ARTICLE_LOCALES,
  buildArticleTranslations,
  parseArticleLocale,
  parseArticlePayloadTranslations,
  parseArticleStoredJson,
  parseArticleTagsFromDb,
  resolveLocalizedArticle,
  serializeArticleFaqForDb,
  serializeArticleLongTextJsonField,
  serializeArticleOverviewForDb,
  serializeArticleTagsForDb,
} from "@/lib/article-db"
import { Prisma } from "@prisma/client"
import type { Article_status } from "@prisma/client"
import { validateUrlSlug } from "@/lib/slug-validation"
import { getPersistedMediaValidationError } from "@/lib/media-manager/validate-persisted-payload"

type RouteContext = { params: Promise<{ id: string }> }

export const dynamic = "force-dynamic"
export const revalidate = 0

const NO_CACHE_HEADERS = {
  "Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate",
  Pragma: "no-cache",
  Expires: "0",
}

function fail(message: string, status = 400) {
  return NextResponse.json({ success: false as const, error: message }, { status })
}

function toJsonArray(value: unknown): Prisma.InputJsonValue {
  if (Array.isArray(value)) return value as Prisma.InputJsonValue
  return []
}

function parseOverviewFromBody(value: unknown): { data: Prisma.InputJsonValue; error?: string } {
  if (value === null || value === undefined) {
    return { data: {} }
  }

  if (Array.isArray(value)) {
    const pairs = value.filter(
      (item): item is { label: string; value: string } =>
        !!item &&
        typeof item === "object" &&
        "label" in item &&
        "value" in item &&
        typeof (item as { label: unknown }).label === "string" &&
        typeof (item as { value: unknown }).value === "string"
    )
    if (pairs.length === 0) return { data: {} }
    return {
      data: {
        columns: ["Label", "Value", "Description", "Notes"],
        rows: pairs.map((p) => [p.label, p.value, "", ""]),
      } as Prisma.InputJsonValue,
    }
  }

  if (!value || typeof value !== "object") {
    return { data: {}, error: "Overview format is invalid" }
  }

  const source = value as { columns?: unknown; rows?: unknown }

  if (source.columns === undefined && source.rows === undefined) {
    return { data: {} }
  }

  // Accept flexible columns/rows. Normalize to strings and preserve structure.
  const cols = Array.isArray(source.columns) ? source.columns.map((c) => (c === null || c === undefined ? "" : String(c))) : []
  const rowsRaw = Array.isArray(source.rows) ? source.rows : []
  const normalizedRows: string[][] = []
  for (const row of rowsRaw) {
    if (!Array.isArray(row)) continue
    normalizedRows.push(row.map((cell) => (cell === null || cell === undefined ? "" : String(cell))))
  }

  return {
    data: {
      columns: cols,
      rows: normalizedRows,
    } as Prisma.InputJsonValue,
  }
}

function parseIsFeatured(value: unknown): boolean {
  if (typeof value === "boolean") return value
  if (value === "true" || value === 1) return true
  if (value === "false" || value === 0) return false
  return false
}

function collectInlineImagePublicIdsFromContents(contents: Array<string | null | undefined>): Set<string> {
  const ids = new Set<string>()
  for (const content of contents) {
    const extracted = extractMediaKeysFromHtml(content, { prefix: "articles" })
    for (const id of extracted) ids.add(id)
  }
  return ids
}

export async function GET(_req: NextRequest, context: RouteContext) {
  try {
    const locale = parseArticleLocale(_req.nextUrl.searchParams.get("locale"))
    const { id } = await context.params
    if (!id) {
      return fail("Missing id", 400)
    }

    let article = null as any
    try {
      article = await prisma.article.findUnique({
        where: { id },
        include: {
          Category: { select: { id: true, name: true, slug: true } },
          ...contentAuditArticleInclude,
        },
      })
    } catch (err) {
      console.warn("[articles] GET [id] primary query failed, trying compatibility fallback:", err)
      article = await prisma.article.findUnique({
        where: { id },
        include: {
          Category: { select: { id: true, name: true, slug: true } },
        },
      })
    }

    if (!article) {
      return fail("Article not found", 404)
    }
    const translationRows = await prisma.articleTranslation.findMany({
      where: { articleId: id },
      select: { locale: true, title: true, subTitle: true, author: true, content: true, faqHeading: true, overview: true, faq: true, tags: true, conversionCard: true, trustCard: true },
    })
    const translations = buildArticleTranslations(
      {
        title: article.title,
        subTitle: article.subTitle,
        author: article.author,
        content: article.content,
        faqHeading: article.faqHeading ?? "",
        overview: article.overview,
        faq: article.faq,
        tags: article.tags,
        conversionCard: article.conversionCard,
        trustCard: article.trustCard,
      },
      translationRows
    )
    const localized = resolveLocalizedArticle(translations, locale)

    return NextResponse.json({
      success: true as const,
      article: normalizePersistedMediaUrls({
        id: article.id,
        title: localized.title || article.title,
        subTitle: localized.subTitle ?? "",
        slug: article.slug,
        categoryId: article.categoryId,
        category: article.Category ?? article.category ?? null,
        image: article.image,
        imagePublicId: article.imagePublicId,
        contentImageFolder: article.contentImageFolder,
        author: localized.author || article.author,
        readingTime: article.readingTime,
        publishDate: article.publishDate.toISOString(),
        content: localized.content || article.content,
        overview: localized.overview ?? parseArticleStoredJson(article.overview, {}),
        faq: localized.faq ?? parseArticleStoredJson(article.faq, []),
        tags: localized.tags.length > 0 ? localized.tags : parseArticleTagsFromDb(article.tags),
        conversionCard: localized.conversionCard ?? parseArticleStoredJson(article.conversionCard, null),
        trustCard: localized.trustCard ?? parseArticleStoredJson(article.trustCard, null),
        isFeatured: article.isFeatured,
        status: article.status,
        translations,
        audit: buildContentAudit(article),
      }),
    }, { headers: NO_CACHE_HEADERS })
  } catch (err) {
    console.error("[articles] GET [id] error:", err)
    return NextResponse.json(
      { success: false as const, error: "Failed to load article" },
      { status: 500 }
    )
  }
}

export async function PUT(req: NextRequest, context: RouteContext) {
  try {
    const auth = await requireContentAuditActor()
    if (!auth.ok) return auth.response

    const { id } = await context.params
    if (!id) {
      return fail("Missing id", 400)
    }

    const existing = await prisma.article.findUnique({ where: { id } })
    if (!existing) {
      return fail("Article not found", 404)
    }
    const existingTranslations = await prisma.articleTranslation.findMany({
      where: { articleId: id },
      select: {
        locale: true,
        content: true,
        overview: true,
        faq: true,
        conversionCard: true,
        trustCard: true,
      },
    })

    const body = await req.json()
    const mediaError = getPersistedMediaValidationError(body)
    if (mediaError) {
      return NextResponse.json({ success: false as const, error: mediaError }, { status: 400 })
    }
    const payload = body as Record<string, unknown>
    const translations = parseArticlePayloadTranslations(payload)

    const status: Article_status =
      body.status === "draft" ? "draft" : "published"

    const categoryId = typeof body.categoryId === "string" ? body.categoryId.trim() : ""
    const image = typeof body.image === "string" ? body.image.trim() : ""
    const imagePublicId = typeof body.imagePublicId === "string" ? body.imagePublicId.trim() : ""
    const title = translations.en.title
    const subTitle = translations.en.subTitle
    const author = translations.en.author || (typeof body.author === "string" ? body.author.trim() : "")
    const readingTimeRaw = body.readingTime
    const readingTime =
      readingTimeRaw === "" || readingTimeRaw === undefined || readingTimeRaw === null
        ? ""
        : String(readingTimeRaw).trim()
    const publishDateRaw = body.publishDate
    const content = translations.en.content || (typeof body.content === "string" ? body.content : "")
    const overviewResult = parseOverviewFromBody(body.overview)
    if (overviewResult.error) {
      return fail(overviewResult.error)
    }
    const overview = (translations.en.overview ?? overviewResult.data) as Prisma.InputJsonValue
    const faq = (translations.en.faq ?? toJsonArray(body.faq)) as Prisma.InputJsonValue
    const tags = parseArticleTagsFromBody(body.tags)
    const isFeatured = parseIsFeatured(body.isFeatured)

    if (!categoryId) {
      return fail("Category is required")
    }

    const category = await prisma.category.findUnique({
      where: { id: categoryId },
      select: { id: true },
    })
    if (!category) {
      return fail("Selected category does not exist", 400)
    }

    if (status === "published") {
      if (!image) {
        return fail("Article image is required")
      }
      if (!imagePublicId) {
        return fail("Image public id is required")
      }
    }

    let publishDate: Date
    if (typeof publishDateRaw === "string" && publishDateRaw.trim() !== "") {
      const d = new Date(publishDateRaw)
      if (Number.isNaN(d.getTime())) {
        return fail("Invalid publish date")
      }
      publishDate = d
    } else {
      publishDate = existing.publishDate
    }

    // Require client-provided slug (do not auto-generate)
    const requestedSlug = typeof body.slug === "string" ? body.slug.trim() : ""
    const slugValidation = validateUrlSlug(requestedSlug)
    if (!slugValidation.valid) {
      return fail(slugValidation.error ?? "Invalid slug")
    }
    // Ensure uniqueness excluding current article
    const conflicting = await prisma.article.findFirst({
      where: { slug: requestedSlug, NOT: { id } },
      select: { id: true },
    })
    if (conflicting) {
      return NextResponse.json({ success: false as const, error: "Slug already in use" }, { status: 409 })
    }
    const newSlug = requestedSlug
    const slugChanging = existing.slug !== newSlug

    const previousMediaKeys = collectArticleMediaKeysFromPersistedArticle({
      image: existing.image,
      imagePublicId: existing.imagePublicId,
      content: existing.content,
      translations: {
        en: { content: existing.content ?? "" },
        ar: { content: existingTranslations.find((row) => row.locale === "ar")?.content ?? "" },
        tr: { content: existingTranslations.find((row) => row.locale === "tr")?.content ?? "" },
      },
      overview: existing.overview,
      faq: existing.faq,
      conversionCard: existing.conversionCard,
      trustCard: existing.trustCard,
    })

    let imageForSave = image || ""
    let imagePublicIdForSave = imagePublicId || ""
    let contentForSave = content
    let translationsForSave = translations
    let contentImageFolder = articleMediaBaseForSlug(newSlug)

    if (slugChanging) {
      try {
        // Relocate R2 objects first, then rewrite payload paths. Do not pre-normalize
        // URLs to the new slug — that makes delete-before-move wipe files before copy.
        const slugMedia = await applyArticleSlugMediaChange({
          existingSlug: existing.slug,
          existingContentImageFolder: existing.contentImageFolder,
          newSlug,
          payload: {
            image: imageForSave,
            imagePublicId: imagePublicIdForSave,
            content: contentForSave,
            translations: translationsForSave,
          },
        })
        contentImageFolder = slugMedia.newMediaBase
        const rewritten = slugMedia.payload as {
          image?: string
          imagePublicId?: string
          content?: string
          translations?: typeof translations
        }
        if (typeof rewritten.image === "string") imageForSave = rewritten.image
        if (typeof rewritten.imagePublicId === "string") imagePublicIdForSave = rewritten.imagePublicId
        if (typeof rewritten.content === "string") contentForSave = rewritten.content
        if (rewritten.translations) translationsForSave = rewritten.translations
      } catch (relocateErr) {
        console.error("[articles] slug media relocate failed:", relocateErr)
        return fail("Failed to move article images for the new slug", 500)
      }
    } else {
      const normalizedPayload = normalizeArticleSavePayloadMediaToCurrentSlug(payload, {
        persistedSlug: existing.slug,
        persistedContentImageFolder: existing.contentImageFolder,
        targetSlug: newSlug,
      }) as Record<string, unknown>
      const normalizedTranslations = parseArticlePayloadTranslations(normalizedPayload)
      imageForSave =
        typeof normalizedPayload.image === "string" ? normalizedPayload.image.trim() : imageForSave
      imagePublicIdForSave =
        typeof normalizedPayload.imagePublicId === "string"
          ? normalizedPayload.imagePublicId.trim()
          : imagePublicIdForSave
      contentForSave =
        normalizedTranslations.en.content ||
        (typeof normalizedPayload.content === "string" ? normalizedPayload.content : contentForSave)
      translationsForSave = normalizedTranslations
    }

    const canonicalArticleMedia = normalizePersistedMediaUrls({
      image: imageForSave,
      imagePublicId: imagePublicIdForSave,
      content: contentForSave,
      translations: translationsForSave,
    }) as {
      image: string
      imagePublicId: string
      content: string
      translations: typeof translationsForSave
    }
    const safeContent = sanitizeArticleHtml(
      typeof canonicalArticleMedia.content === "string" ? canonicalArticleMedia.content : contentForSave
    )
    const safeImage =
      typeof canonicalArticleMedia.image === "string" ? canonicalArticleMedia.image.trim() : ""
    const safePublicId =
      typeof canonicalArticleMedia.imagePublicId === "string"
        ? canonicalArticleMedia.imagePublicId.trim()
        : ""
    translationsForSave = canonicalArticleMedia.translations

    const nextContentByLocale: Record<string, string | null> = {
      en: safeContent,
      ar: null,
      tr: null,
    }

    const translationSelect = {
      locale: true,
      title: true,
      subTitle: true,
      author: true,
      content: true,
      faqHeading: true,
      overview: true,
      faq: true,
      tags: true,
      conversionCard: true,
      trustCard: true,
    } as const

    await prisma.$transaction(
      async (tx) => {
        const translationNow = new Date()
        await tx.article.update({
          where: { id },
          data: {
            title,
            subTitle,
            slug: newSlug,
            faqHeading: typeof body.faqHeading === "string" ? body.faqHeading : null,
            categoryId,
            image: safeImage,
            imagePublicId: safePublicId,
            author,
            readingTime: readingTime || "0",
            publishDate,
            content: safeContent,
            contentImageFolder,
            overview: serializeArticleOverviewForDb(overview),
            faq: serializeArticleFaqForDb(faq),
            conversionCard: serializeArticleLongTextJsonField(translationsForSave.en.conversionCard),
            trustCard: serializeArticleLongTextJsonField(translationsForSave.en.trustCard),
            tags: serializeArticleTagsForDb(tags),
            isFeatured,
            status,
            ...contentAuditUpdateData(auth.user.id),
          },
        })

        const payloadByLocale = {
          en: translationsForSave.en,
          ar: translationsForSave.ar,
          tr: translationsForSave.tr,
        }
        for (const localeCode of ARTICLE_LOCALES) {
          const trData = payloadByLocale[localeCode]
          const safeTranslationContent = trData.content ? sanitizeArticleHtml(trData.content) : null
          nextContentByLocale[localeCode] = safeTranslationContent
          if (
            localeCode !== "en" &&
            !trData.title &&
            !trData.subTitle &&
            !trData.author &&
            !safeTranslationContent &&
            trData.tags.length === 0 &&
            !trData.conversionCard &&
            !trData.trustCard
          ) {
            await tx.articleTranslation.deleteMany({
              where: { articleId: id, locale: localeCode },
            })
            continue
          }
          await tx.articleTranslation.upsert({
            where: {
              articleId_locale: {
                articleId: id,
                locale: localeCode,
              },
            },
            update: {
              ...touchUpdatedAt(),
              title: trData.title || null,
              faqHeading: trData.faqHeading || null,
              subTitle: trData.subTitle,
              author: trData.author || null,
              content: safeTranslationContent,
              overview: serializeArticleOverviewForDb(trData.overview),
              faq: serializeArticleFaqForDb(trData.faq),
              conversionCard: serializeArticleLongTextJsonField(trData.conversionCard),
              trustCard: serializeArticleLongTextJsonField(trData.trustCard),
              tags: serializeArticleTagsForDb(trData.tags),
            },
            create: {
              id: randomUUID(),
              updatedAt: translationNow,
              articleId: id,
              locale: localeCode,
              title: trData.title || null,
              faqHeading: trData.faqHeading || null,
              subTitle: trData.subTitle,
              author: trData.author || null,
              content: safeTranslationContent,
              overview: serializeArticleOverviewForDb(trData.overview),
              faq: serializeArticleFaqForDb(trData.faq),
              conversionCard: serializeArticleLongTextJsonField(trData.conversionCard),
              trustCard: serializeArticleLongTextJsonField(trData.trustCard),
              tags: serializeArticleTagsForDb(trData.tags),
            },
          })
        }
      },
      { maxWait: 15_000, timeout: 60_000 }
    )

    const result = {
      article: await prisma.article.findUnique({
        where: { id },
        include: {
          Category: { select: { id: true, name: true, slug: true } },
          ...contentAuditArticleInclude,
        },
      }),
      translations: await prisma.articleTranslation.findMany({
        where: { articleId: id },
        select: translationSelect,
      }),
    }

    if (!result.article) {
      return fail("Article not found", 404)
    }

    const slugChanged = existing.slug !== newSlug
    const savedArticle = result.article
    const responseTranslations = buildArticleTranslations(
      {
        title: savedArticle.title,
        subTitle: savedArticle.subTitle,
        author: savedArticle.author,
        content: savedArticle.content,
        faqHeading: savedArticle.faqHeading ?? "",
        overview: savedArticle.overview,
        faq: savedArticle.faq,
        tags: savedArticle.tags,
        conversionCard: savedArticle.conversionCard,
        trustCard: savedArticle.trustCard,
      },
      result.translations
    )

    await reconcileArticleMediaAfterSave({
      slugChanged,
      existingSlug: existing.slug,
      existingContentImageFolder: existing.contentImageFolder,
      newSlug,
      previousMediaKeys,
      savedPayload: {
        image: savedArticle.image,
        imagePublicId: savedArticle.imagePublicId,
        content: savedArticle.content,
        translations: responseTranslations,
        overview: parseArticleStoredJson(savedArticle.overview, {}),
        faq: parseArticleStoredJson(savedArticle.faq, []),
        conversionCard: responseTranslations.en.conversionCard,
        trustCard: responseTranslations.en.trustCard,
      },
    })

    return NextResponse.json({
      success: true as const,
      article: normalizePersistedMediaUrls({
        id: savedArticle.id,
        title: savedArticle.title,
        subTitle: savedArticle.subTitle,
        slug: savedArticle.slug,
        categoryId: savedArticle.categoryId,
        category: savedArticle.Category ?? null,
        image: savedArticle.image,
        imagePublicId: savedArticle.imagePublicId,
        contentImageFolder: savedArticle.contentImageFolder,
        author: savedArticle.author,
        readingTime: savedArticle.readingTime,
        publishDate: savedArticle.publishDate.toISOString(),
        content: savedArticle.content,
        overview: parseArticleStoredJson(savedArticle.overview, {}),
        faq: parseArticleStoredJson(savedArticle.faq, []),
        tags: (() => {
          const enTagsRaw = result.translations.find((t) => t.locale === "en")?.tags
          return enTagsRaw != null ? parseArticleTagsFromDb(enTagsRaw) : parseArticleTagsFromDb(savedArticle.tags)
        })(),
        conversionCard: parseArticleStoredJson(savedArticle.conversionCard, null),
        trustCard: parseArticleStoredJson(savedArticle.trustCard, null),
        isFeatured: savedArticle.isFeatured,
        status: savedArticle.status,
        translations: responseTranslations,
        audit: buildContentAudit(savedArticle),
      }),
    })
  } catch (err) {
    console.error("[articles] PUT error:", err)
    return NextResponse.json(
      { success: false as const, error: "Failed to update article" },
      { status: 500 }
    )
  }
}

export async function DELETE(_req: NextRequest, context: RouteContext) {
  try {
    const { id } = await context.params
    if (!id) {
      return NextResponse.json({ success: false as const, error: "Missing id" }, { status: 400 })
    }

    const existing = await prisma.article.findUnique({
      where: { id },
      select: {
        imagePublicId: true,
        content: true,
        contentImageFolder: true,
      },
    })
    if (!existing) {
      return NextResponse.json({ success: false as const, error: "Article not found" }, { status: 404 })
    }

    const existingTranslations = await prisma.articleTranslation.findMany({
      where: { articleId: id },
      select: { content: true },
    })
    await prisma.$transaction(async (tx) => {
      await tx.articleTranslation.deleteMany({
        where: { articleId: id },
      })
      await tx.article.delete({ where: { id } })
    })

    const publicId = (existing.imagePublicId ?? "").trim()
    if (publicId) {
      await deleteImage(publicId)
    }
    const inlineImageIds = collectInlineImagePublicIdsFromContents([
      existing.content,
      ...existingTranslations.map((t) => t.content),
    ])
    await Promise.allSettled(
      [...inlineImageIds]
        .filter((pid) => isManagedArticleInlineImageKey(pid))
        .map((pid) => deleteImage(pid))
    )
    if (existing.contentImageFolder) {
      await purgePrefixExcept(existing.contentImageFolder, [])
      await deletePrefixIfEmpty(existing.contentImageFolder)
    }

    return NextResponse.json({ success: true as const })
  } catch (err) {
    console.error("[articles] DELETE error:", err)
    return NextResponse.json(
      { success: false as const, error: "Failed to delete article" },
      { status: 500 }
    )
  }
}
