"use client"

import * as React from "react"
import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { ArrowLeft, Loader2 } from "lucide-react"
import { toast } from "sonner"

import {
  collectUnmappedErrors,
  getOcrCodeForSlot,
  getUnmappedSlots,
  isRealIsin,
  rowHasUnmapped,
  suggestAliasSourceFromBankName,
  underlyingSlotHasData,
  UNDERLYING_SLOTS,
  type StructureQueueRow,
} from "@/components/deepdex/utils/structure-underlying-alias"
import {
  accumulatorNeedsMapping,
  accumulatorRowHasUnmapped,
  collectAccumulatorUnmappedErrors,
  getOcrCodeForAccumulator,
  type AccumulatorQueueRow,
} from "@/components/deepdex/utils/accumulator-underlying-alias"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"

import {
  fetchAliasSourceSuggestions,
  fetchUploadQueueReviewData,
  formatCreateBulkError,
  postUploadQueueAddUnderlyingAlias,
  postUploadQueueCreateBulk,
  postUploadQueueUpdateRow,
} from "./deepdex-upload-queue-review-api"
import { DeepdexUploadQueueReviewBulkActions } from "./deepdex-upload-queue-review-bulk-actions"
import { reviewPageTitle } from "./deepdex-upload-queue-review-cells"
import { DeepdexUploadQueueReviewEditSheet } from "./deepdex-upload-queue-review-edit-sheet"
import { DeepdexUploadQueueReviewMapDialog } from "./deepdex-upload-queue-review-map-dialog"
import { DeepdexUploadQueueReviewTable } from "./deepdex-upload-queue-review-table"
import type { MapModalState, ReviewData, ReviewResponse } from "./deepdex-upload-queue-review-types"
import { DeepdexUploadQueueReviewViewSheet } from "./deepdex-upload-queue-review-view-sheet"

export function DeepdexUploadQueueReviewPage() {
  const router = useRouter()
  const searchParams = useSearchParams()
  const moduleId = searchParams.get("module") ?? ""
  const id = searchParams.get("id") ?? ""

  const [isLoading, setIsLoading] = React.useState(true)
  const [isSubmitting, setIsSubmitting] = React.useState(false)
  const [isEditOpen, setIsEditOpen] = React.useState(false)
  const [isViewOpen, setIsViewOpen] = React.useState(false)
  const [editingIndex, setEditingIndex] = React.useState<number | null>(null)
  const [editingExisting, setEditingExisting] = React.useState<Record<string, unknown> | null>(null)
  const [viewingIndex, setViewingIndex] = React.useState<number | null>(null)
  const [editRow, setEditRow] = React.useState<Record<string, string>>({})
  const [editObs, setEditObs] = React.useState<Array<{ date: string; nonCall: string }>>([])
  const [editCoupons, setEditCoupons] = React.useState<Array<{ date: string }>>([])
  const [editAccRows, setEditAccRows] = React.useState<Array<{ date: string; quantityPerPeriod: string }>>([])
  const [selected, setSelected] = React.useState<number[]>([])
  const [error, setError] = React.useState<string | null>(null)
  const [payload, setPayload] = React.useState<ReviewResponse | null>(null)
  const [mapModal, setMapModal] = React.useState<MapModalState | null>(null)
  const [mapError, setMapError] = React.useState<string | null>(null)
  const [aliasSourceSuggestions, setAliasSourceSuggestions] = React.useState<string[]>([])

  const loadReview = React.useCallback(async (removeIndex?: number) => {
    setIsLoading(true)
    setError(null)
    try {
      const { response, data } = await fetchUploadQueueReviewData(moduleId, id, removeIndex)
      if (!response.ok || !data) {
        setError(data?.message ?? `Request failed (HTTP ${response.status || 500}).`)
        setPayload(data)
      } else {
        setPayload(data)
      }
    } catch {
      setError("Could not load review data.")
    } finally {
      setIsLoading(false)
    }
  }, [id, moduleId])

  React.useEffect(() => {
    void loadReview()
  }, [loadReview])

  const reviewData = React.useMemo((): ReviewData | null => {
    if (!payload || !payload.data || typeof payload.data !== "object") return null
    return payload.data as ReviewData
  }, [payload])

  const queueRows = Array.isArray(reviewData?.queue) ? reviewData.queue : []
  const queueId = Number(reviewData?.queue_id ?? id)
  const queueHasUnmapped =
    (moduleId === "structure" && queueRows.some((row) => rowHasUnmapped(row))) ||
    (moduleId === "accumulator" && queueRows.some((row) => accumulatorRowHasUnmapped(row)))

  const createBulk = React.useCallback(async (createSelected: boolean) => {
    const indices =
      createSelected
        ? selected
        : queueRows.map((_, index) => index)

    if (moduleId === "structure") {
      const mapErrors = collectUnmappedErrors(queueRows, indices)
      if (mapErrors.length > 0) {
        toast.error(`Cannot save until all OCR underlyings are mapped to real ISINs:\n${mapErrors.join("\n")}`)
        return
      }
    } else if (moduleId === "accumulator") {
      const mapErrors = collectAccumulatorUnmappedErrors(queueRows, indices)
      if (mapErrors.length > 0) {
        toast.error(`Cannot save until all OCR underlyings are mapped to real ISINs:\n${mapErrors.join("\n")}`)
        return
      }
    }

    setIsSubmitting(true)
    try {
      const { response, data } = await postUploadQueueCreateBulk({
        module: moduleId,
        queue_id: Number.isFinite(queueId) ? queueId : undefined,
        queue: queueRows,
        create_selected: createSelected,
        selected: createSelected ? selected : [],
      })
      if (!response.ok || data?.status === "error") {
        throw new Error(formatCreateBulkError(data))
      }
      toast.success(data?.message ?? "Create operation completed.")
      const publishResults = Array.isArray(data?.data?.publishResults) ? data.data.publishResults : []
      const duplicateCount = publishResults.filter((item) => item.duplicate).length
      if (duplicateCount > 0) {
        toast.message(
          duplicateCount === publishResults.length
            ? "File already existed in Deepdex; linked to existing record."
            : `${duplicateCount} file(s) matched existing Deepdex content (deduplicated).`,
        )
      }

      const remainingQueueCount =
        typeof data?.data?.remainingQueueCount === "number" ? data.data.remainingQueueCount : 0
      const localRemainingRows = createSelected
        ? queueRows.filter((_, index) => !indices.includes(index))
        : []

      if (createSelected && (remainingQueueCount > 0 || localRemainingRows.length > 0)) {
        setSelected([])
        setPayload((current) => {
          if (!current?.data || typeof current.data !== "object") return current
          const nextQueue = localRemainingRows
          return {
            ...current,
            message: "Ready",
            data: {
              ...(current.data as ReviewData),
              queue: nextQueue,
              queueCount: nextQueue.length,
            },
          }
        })
        return
      }

      router.push("/dashboard/deepdex/upload-queue")
    } catch (e) {
      toast.error(e instanceof Error ? e.message : "Create operation failed.")
    } finally {
      setIsSubmitting(false)
    }
  }, [moduleId, queueId, queueRows, router, selected])

  const removeRow = React.useCallback((index: number) => {
    setSelected((prev) =>
      prev
        .filter((value) => value !== index)
        .map((value) => (value > index ? value - 1 : value)),
    )
    setPayload((current) => {
      if (!current?.data || typeof current.data !== "object") return current
      const currentData = current.data as ReviewData
      const queue = Array.isArray(currentData.queue) ? [...currentData.queue] : []
      if (index < 0 || index >= queue.length) return current
      queue.splice(index, 1)
      return {
        ...current,
        data: {
          ...currentData,
          queue,
          queueCount: queue.length,
        },
      }
    })
    if (editingIndex === index) {
      setIsEditOpen(false)
      setEditingIndex(null)
    } else if (editingIndex != null && editingIndex > index) {
      setEditingIndex(editingIndex - 1)
    }
    if (viewingIndex === index) {
      setIsViewOpen(false)
      setViewingIndex(null)
    } else if (viewingIndex != null && viewingIndex > index) {
      setViewingIndex(viewingIndex - 1)
    }
  }, [editingIndex, viewingIndex])

  const openEdit = React.useCallback((row: Record<string, unknown>, index: number) => {
    const normalized: Record<string, string> = {}
    Object.entries(row).forEach(([key, value]) => {
      if (key.startsWith("_")) return
      normalized[key] = value == null ? "" : String(value)
    })
    setEditRow(normalized)
    setEditingIndex(index)
    const existing = Array.isArray(reviewData?.existingCurrentData)
      ? reviewData?.existingCurrentData[index]
      : null
    setEditingExisting(existing && typeof existing === "object" ? existing : null)
    const child = (row._child ?? null) as
      | {
          observations?: Array<{ date?: string; nonCall?: string }>
          coupons?: Array<{ date?: string }>
          rows?: Array<{ date?: string; quantityPerPeriod?: string }>
        }
      | null
    setEditObs(
      Array.isArray(child?.observations)
        ? child!.observations!.map((o) => ({ date: o.date ?? "", nonCall: o.nonCall ?? "N" }))
        : [],
    )
    setEditCoupons(
      Array.isArray(child?.coupons)
        ? child!.coupons!.map((c) => ({ date: c.date ?? "" }))
        : [],
    )
    setEditAccRows(
      Array.isArray(child?.rows)
        ? child!.rows!.map((r) => ({ date: r.date ?? "", quantityPerPeriod: r.quantityPerPeriod ?? "" }))
        : [],
    )
    setIsEditOpen(true)
  }, [reviewData?.existingCurrentData])

  const openView = React.useCallback((index: number) => {
    setViewingIndex(index)
    setIsViewOpen(true)
  }, [])

  const openMapModal = React.useCallback((
    queueIndex: number,
    slot: number,
    row: StructureQueueRow,
    isNewMapping = false,
  ) => {
    const aliasCode = isNewMapping ? "" : getOcrCodeForSlot(row, slot)
    const existingIsin = String(row[`underlying${slot}_isin`] ?? "").trim()
    const bankName = String(row._bankName ?? "").trim()
    setMapModal({
      queueIndex,
      slot,
      mode: "structure",
      aliasCode,
      aliasIsin: isRealIsin(existingIsin) ? existingIsin.toUpperCase() : "",
      aliasSource: suggestAliasSourceFromBankName(bankName),
      underlyingName: String(row[`underlying${slot}_name`] ?? ""),
      underlyingCurrency: String(row[`underlying${slot}_currency`] ?? ""),
      underlyingSpot: String(row[`underlying${slot}_spot`] ?? ""),
      isNewMapping,
      bankName,
      showDetails: isNewMapping || !!(row[`underlying${slot}_name`] || row[`underlying${slot}_currency`] || row[`underlying${slot}_spot`]),
    })
    setMapError(null)
  }, [])

  const openAccumulatorMapModal = React.useCallback((
    queueIndex: number,
    row: AccumulatorQueueRow,
    isNewMapping = false,
  ) => {
    const aliasCode = isNewMapping ? "" : getOcrCodeForAccumulator(row)
    const existingIsin = String(row.UnderlyingISIN ?? "").trim()
    const bankName = String(row._bankName ?? "").trim()
    setMapModal({
      queueIndex,
      mode: "accumulator",
      aliasCode,
      aliasIsin: isRealIsin(existingIsin) ? existingIsin.toUpperCase() : "",
      aliasSource: suggestAliasSourceFromBankName(bankName),
      underlyingName: String(row.UnderlyingName ?? ""),
      underlyingCurrency: "",
      underlyingSpot: "",
      isNewMapping,
      bankName,
      showDetails: isNewMapping || !!row.UnderlyingName,
    })
    setMapError(null)
  }, [])

  const addUnderlying = React.useCallback((row: Record<string, unknown>, index: number) => {
    for (const slot of UNDERLYING_SLOTS) {
      if (!underlyingSlotHasData(row, slot)) {
        openMapModal(index, slot, row, true)
        return
      }
    }
    toast.error("All 5 underlying slots are already in use.")
  }, [openMapModal])

  const saveEdit = React.useCallback(async () => {
    if (editingIndex == null) return

    if (moduleId === "structure") {
      const draft: StructureQueueRow = { ...editRow }
      const unmapped = getUnmappedSlots(draft)
      if (unmapped.length > 0) {
        const messages = unmapped.map((slot) => {
          const ocr = getOcrCodeForSlot(draft, slot)
          return `Underlying ${slot}${ocr ? ` (${ocr})` : ""}`
        })
        toast.error(`Cannot save until all OCR underlyings are mapped to real ISINs:\n${messages.join("\n")}`)
        return
      }
    } else if (moduleId === "accumulator") {
      const draft: AccumulatorQueueRow = {
        ...(editingIndex != null ? queueRows[editingIndex] : {}),
        ...editRow,
      }
      if (accumulatorNeedsMapping(draft)) {
        const ocr = getOcrCodeForAccumulator(draft)
        toast.error(`Cannot save until the OCR underlying is mapped to a real ISIN${ocr ? `: ${ocr}` : ""}`)
        return
      }
    }

    setIsSubmitting(true)
    try {
      const body: Record<string, unknown> = {
        module: moduleId,
        queue_index: editingIndex,
        queue_id: Number.isFinite(queueId) ? queueId : undefined,
        queue: queueRows,
        row: editRow,
      }
      if (moduleId === "structure") {
        body.child_observations = editObs
        body.child_coupons = editCoupons
      } else if (moduleId === "accumulator") {
        body.child_rows = editAccRows
        body.child_section_present = true
      }

      const { response, data } = await postUploadQueueUpdateRow(body)
      if (!response.ok || data?.status === "error") {
        throw new Error(data?.message || "Failed to update row.")
      }
      toast.success(data?.message || "Row updated.")
      if (Array.isArray(data?.data?.queue)) {
        setPayload((current) => {
          if (!current) return current
          return {
            ...current,
            data: {
              ...(typeof current.data === "object" && current.data ? current.data : {}),
              queue: data.data!.queue,
              queueCount: data.data!.queue!.length,
            },
          }
        })
      } else {
        await loadReview()
      }
      setIsEditOpen(false)
    } catch (e) {
      toast.error(e instanceof Error ? e.message : "Failed to update row.")
    } finally {
      setIsSubmitting(false)
    }
  }, [editAccRows, editCoupons, editObs, editRow, editingIndex, loadReview, moduleId, queueId, queueRows])

  const saveMapModal = React.useCallback(async () => {
    if (!mapModal) return
    setIsSubmitting(true)
    setMapError(null)
    try {
      const { response, data } = await postUploadQueueAddUnderlyingAlias({
        module: mapModal.mode,
        alias_code: mapModal.aliasCode.trim(),
        alias_isin: mapModal.aliasIsin.trim(),
        alias_source: mapModal.aliasSource.trim(),
        underlying_name: mapModal.showDetails ? mapModal.underlyingName.trim() : "",
        underlying_currency: mapModal.mode === "structure" && mapModal.showDetails ? mapModal.underlyingCurrency.trim() : "",
        underlying_spot: mapModal.mode === "structure" && mapModal.showDetails ? mapModal.underlyingSpot.trim() : "",
        queue_index: mapModal.queueIndex,
        queue_id: Number.isFinite(queueId) ? queueId : undefined,
        queue: queueRows,
        slot: mapModal.mode === "structure" ? mapModal.slot : undefined,
      })
      if (!response.ok || !data?.success) {
        throw new Error(data?.error || "Could not save mapping.")
      }
      toast.success("Underlying mapping saved.")
      setMapModal(null)
      if (data.row && typeof data.row === "object") {
        setPayload((current) => {
          if (!current?.data || typeof current.data !== "object") return current
          const currentData = current.data as ReviewData
          const queue = Array.isArray(currentData.queue) ? [...currentData.queue] : []
          if (mapModal.queueIndex < 0 || mapModal.queueIndex >= queue.length) return current
          queue[mapModal.queueIndex] = data.row as Record<string, unknown>
          return {
            ...current,
            data: {
              ...currentData,
              queue,
            },
          }
        })
      } else {
        await loadReview()
      }
      if (isEditOpen && editingIndex === mapModal.queueIndex && data.row) {
        const normalized: Record<string, string> = {}
        Object.entries(data.row).forEach(([key, value]) => {
          if (key.startsWith("_")) return
          normalized[key] = value == null ? "" : String(value)
        })
        setEditRow(normalized)
      }
    } catch (e) {
      setMapError(e instanceof Error ? e.message : "Could not save mapping.")
    } finally {
      setIsSubmitting(false)
    }
  }, [editingIndex, isEditOpen, loadReview, mapModal, queueId, queueRows])

  React.useEffect(() => {
    if (!mapModal) return
    const term = mapModal.aliasSource.trim()
    const timer = window.setTimeout(() => {
      void fetchAliasSourceSuggestions(moduleId, term)
        .then(setAliasSourceSuggestions)
        .catch(() => setAliasSourceSuggestions([]))
    }, 200)
    return () => window.clearTimeout(timer)
  }, [mapModal, moduleId])

  const selectedHasUnmapped =
    (moduleId === "structure" &&
      selected.some((index) => queueRows[index] && rowHasUnmapped(queueRows[index]))) ||
    (moduleId === "accumulator" &&
      selected.some((index) => queueRows[index] && accumulatorRowHasUnmapped(queueRows[index])))

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <h1 className="text-lg font-semibold">{reviewPageTitle(moduleId)}</h1>
        <Button variant="outline" size="sm" asChild>
          <Link href="/dashboard/deepdex/upload-queue">
            <ArrowLeft className="size-4" />
            Back to queue
          </Link>
        </Button>
      </div>

      <Card>
        <CardHeader>
          <CardTitle>
            {moduleId || "module"} #{id || "?"}
          </CardTitle>
        </CardHeader>
        <CardContent>
          {isLoading ? (
            <div className="flex items-center gap-2 text-sm text-muted-foreground">
              <Loader2 className="size-4 animate-spin" />
              Loading review data...
            </div>
          ) : error ? (
            <p className="text-sm text-destructive">{error}</p>
          ) : (
            <div className="space-y-2">
              <div className="flex flex-wrap items-center gap-2">
                <Badge variant="outline">Queue: {String(reviewData?.queueCount ?? queueRows.length)}</Badge>
                <Badge variant="outline">Queue ID: {String(reviewData?.queue_id ?? id)}</Badge>
                <Badge variant="secondary">{String(payload?.message ?? "Ready")}</Badge>
              </div>
              {moduleId === "structure" || moduleId === "accumulator" ? (
                <p className="text-xs text-muted-foreground">
                  Unknown OCR underlyings show an orange badge. Use Map ISIN to add the OCR code to real ISIN mapping.
                  Create is blocked until every OCR underlying is mapped to a real ISIN.
                </p>
              ) : (
                <p className="text-xs text-muted-foreground">
                  Review extracted rows, edit fields, then use Create selected or Create all.
                </p>
              )}
            </div>
          )}
        </CardContent>
      </Card>

      {!isLoading && !error && queueRows.length > 0 ? (
        <DeepdexUploadQueueReviewBulkActions
          isSubmitting={isSubmitting}
          queueHasUnmapped={queueHasUnmapped}
          selectedCount={selected.length}
          selectedHasUnmapped={selectedHasUnmapped}
          onCreateAll={() => void createBulk(false)}
          onCreateSelected={() => void createBulk(true)}
        />
      ) : null}

      {!isLoading && !error && queueRows.length > 0 ? (
        <DeepdexUploadQueueReviewTable
          module={moduleId}
          queueRows={queueRows}
          reviewData={reviewData}
          selected={selected}
          onSelectedChange={setSelected}
          onEdit={openEdit}
          onView={openView}
          onRemove={removeRow}
          onAddUnderlying={addUnderlying}
          onMapStructure={openMapModal}
          onMapAccumulator={openAccumulatorMapModal}
        />
      ) : null}

      <DeepdexUploadQueueReviewEditSheet
        open={isEditOpen}
        onOpenChange={setIsEditOpen}
        module={moduleId}
        editingIndex={editingIndex}
        editRow={editRow}
        onEditRowChange={setEditRow}
        editingExisting={editingExisting}
        queueRows={queueRows}
        editObs={editObs}
        onEditObsChange={setEditObs}
        editCoupons={editCoupons}
        onEditCouponsChange={setEditCoupons}
        editAccRows={editAccRows}
        onEditAccRowsChange={setEditAccRows}
        isSubmitting={isSubmitting}
        onSave={() => void saveEdit()}
        onMapStructure={openMapModal}
        onMapAccumulator={openAccumulatorMapModal}
      />

      <DeepdexUploadQueueReviewViewSheet
        open={isViewOpen}
        onOpenChange={setIsViewOpen}
        module={moduleId}
        viewingIndex={viewingIndex}
        queueRows={queueRows}
      />

      <DeepdexUploadQueueReviewMapDialog
        mapModal={mapModal}
        onMapModalChange={setMapModal}
        aliasSourceSuggestions={aliasSourceSuggestions}
        mapError={mapError}
        isSubmitting={isSubmitting}
        onSave={() => void saveMapModal()}
        onClose={() => setMapModal(null)}
      />
    </div>
  )
}
