"use client"

import { POLL_INTERVAL_MS } from "@/config/timeouts"
import { DEEPDEX_PAGES } from "@/components/deepdex/data/deepdex-page-routes"
import { toastApiError, errorMessageOr } from "@/lib/toast-api-error";
import * as React from "react"
import { ChevronDown, Eye, Loader2, Plus, RefreshCw, RotateCcw, SearchCheck, Trash2, Upload } from "lucide-react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { toast } from "sonner"

import { FormPageHeader } from "@/app/dashboard/_components/form"
import {
  deleteDeepdexUploadQueueItem,
  fetchDeepdexUploadQueue,
  getDeepdexUploadQueueReviewUrl,
  getDeepdexUploadQueueViewUrl,
  moveDeepdexUploadQueueItem,
  reprocessDeepdexUploadQueue,
} from "@/components/deepdex/api/deepdex.api"
import { DeepdexUploadForm } from "@/components/deepdex/create/deepdex-upload-form"
import { getDeepdexDocTypeLabel } from "@/components/deepdex/data/deepdex-doc-types"
import {
  formatQueueTimestamp,
  QUEUE_MODULE_META,
  QueueEmptyState,
  QueueFileName,
  QueueStatusBadge,
  QueueStatusSummary,
  QueueTableSkeletonRows,
} from "@/components/deepdex/deepdex-upload-queue-parts"
import type { DeepdexQueueModule, DeepdexUploadQueueItem, ReviewQueueModule } from "@/components/deepdex/types"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
  XmlApiPaginatedRange,
  XmlApiServerPagination,
} from "@/components/xml-apis/shared/xml-api-server-pagination"

const PAGE_SIZE_OPTIONS = [20, 50, 100]

function isBulkReviewEligible(item: DeepdexUploadQueueItem, module: DeepdexQueueModule): boolean {
  if (module !== "structure" && module !== "accumulator") return false
  if (item.moduleType !== module) return false
  if (item.canReview) return true
  return item.status === "pending_review" || item.status === "completed"
}

function isFailedItem(item: DeepdexUploadQueueItem): boolean {
  return item.status === "failed"
}

async function sleep(ms: number): Promise<void> {
  await new Promise((resolve) => {
    window.setTimeout(resolve, ms)
  })
}

export function DeepdexUploadQueuePage() {
  const router = useRouter()
  const [items, setItems] = React.useState<DeepdexUploadQueueItem[]>([])
  const [module, setModule] = React.useState<DeepdexQueueModule>("deepdex")
  const [selectedIds, setSelectedIds] = React.useState<string[]>([])
  const [isLoading, setIsLoading] = React.useState(true)
  const [isRefreshing, setIsRefreshing] = React.useState(false)
  const [busyId, setBusyId] = React.useState<string | null>(null)
  const [isRetryingProcessing, setIsRetryingProcessing] = React.useState(false)
  const [isUploadOpen, setIsUploadOpen] = React.useState(false)
  const [page, setPage] = React.useState(1)
  const [pageSize, setPageSize] = React.useState(50)
  const [totalCount, setTotalCount] = React.useState(0)
  const [pageCount, setPageCount] = React.useState(1)
  // Every item loaded so far, so a selection made on one page still works after paging.
  const [knownItems, setKnownItems] = React.useState<Record<string, DeepdexUploadQueueItem>>({})

  const showBulkReview = module === "structure" || module === "accumulator"
  const processingItems = items.filter((item) => item.status === "processing")
  const hasProcessingItems = processingItems.length > 0
  const failedItems = items.filter(isFailedItem)
  const statusCounts = React.useMemo(() => {
    const counts: Record<string, number> = {}
    for (const item of items) counts[item.status] = (counts[item.status] ?? 0) + 1
    return counts
  }, [items])
  const hasFailedItems = failedItems.length > 0
  // Processing runs in the background and never blocks the page: only the poll below and the retry button's
  // own request depend on it, so tabs, uploads, reviews and other views stay usable.
  const shouldPoll = isRetryingProcessing || hasProcessingItems
  const showRetryProcessing = hasFailedItems || isRetryingProcessing
  const showSelectColumn = hasFailedItems
  const selectableItems = failedItems
  const bulkReviewItems = items.filter((item) => isBulkReviewEligible(item, module))
  const selectedFailedIds = selectedIds.filter((id) => {
    const item = knownItems[id]
    return item !== undefined && isFailedItem(item)
  })
  const allSelectableSelected =
    selectableItems.length > 0 && selectableItems.every((item) => selectedFailedIds.includes(item.id))
  const someSelectableSelected = selectableItems.some((item) => selectedFailedIds.includes(item.id))

  // A response may arrive after the user has switched tab or page; only the newest request for the tab on
  // screen may update the list, or the previous tab's rows would flash over the new one.
  const moduleRef = React.useRef(module)
  React.useEffect(() => {
    moduleRef.current = module
  }, [module])
  const loadSeq = React.useRef(0)

  const load = React.useCallback(async (activeModule: DeepdexQueueModule) => {
    const seq = ++loadSeq.current
    try {
      const data = await fetchDeepdexUploadQueue({
        page,
        pageSize,
        filters: { module: activeModule },
      })
      if (seq !== loadSeq.current || moduleRef.current !== activeModule) return data.items
      setItems(data.items)
      setTotalCount(data.totalCount)
      setPageCount(data.pageCount)
      setKnownItems((prev) => {
        const next = { ...prev }
        for (const item of data.items) next[item.id] = item
        return next
      })
      // Deleting the last item of the last page leaves us past the end; step back.
      if (data.items.length === 0 && page > 1 && data.pageCount > 0 && page > data.pageCount) {
        setPage(data.pageCount)
      }
      return data.items
    } catch (error) {
      const message = errorMessageOr(error, "Failed to load upload queue")
      toast.error(message)
      return null
    }
  }, [page, pageSize])

  React.useEffect(() => {
    void (async () => {
      setIsLoading(true)
      await load(module)
      setIsLoading(false)
    })()
  }, [load, module])

  // Poll while rows of this tab are processing (or a retry request is in flight).
  React.useEffect(() => {
    if (!shouldPoll) return

    const timer = window.setInterval(() => {
      void load(module)
    }, POLL_INTERVAL_MS.progress)

    return () => {
      window.clearInterval(timer)
    }
  }, [shouldPoll, load, module])

  const refresh = React.useCallback(async () => {
    setIsRefreshing(true)
    await load(module)
    setIsRefreshing(false)
  }, [load, module])

  const waitUntilProcessingClears = React.useCallback(
    async (activeModule: DeepdexQueueModule) => {
      for (let attempt = 0; attempt < 150; attempt++) {
        // The user moved to another tab: nothing here is theirs to wait for any more.
        if (moduleRef.current !== activeModule) return
        const latest = await load(activeModule)
        if (!latest?.some((item) => item.status === "processing")) {
          return
        }
        await sleep(2000)
      }
    },
    [load],
  )

  const handleDelete = async (id: string) => {
    setBusyId(id)
    try {
      await deleteDeepdexUploadQueueItem(id)
      toast.success("Queue item deleted")
      await load(module)
    } catch (error) {
      toastApiError(error, "Delete failed")
    } finally {
      setBusyId(null)
    }
  }

  const handleBulkReview = () => {
    if (!showBulkReview) return
    const ids = bulkReviewItems.map((item) => item.id)
    if (ids.length === 0) {
      toast.error(
        module === "structure"
          ? "There are no structure items to review."
          : "There are no accumulator items to review.",
      )
      return
    }
    router.push(getDeepdexUploadQueueReviewUrl(module, ids))
  }

  const handleRetryProcessing = async () => {
    const ids = selectedFailedIds
    if (ids.length === 0) {
      toast.error("Select at least one failed file to retry.")
      return
    }

    setIsRetryingProcessing(true)

    try {
      const result = await reprocessDeepdexUploadQueue(ids)
      // Long documents keep processing in the background and the page keeps polling for them,
      // so only wait here when everything was handled inside the request.
      if (result.inFlight === 0) {
        await waitUntilProcessingClears(module)
      }

      if (result.failed > 0 && result.succeeded === 0) {
        toast.error(result.errors[0] ?? result.message)
      } else if (result.failed > 0) {
        toast.warning(result.message)
      } else {
        toast.success(result.message)
      }
      setSelectedIds((prev) => prev.filter((id) => !ids.includes(id)))
      await load(module)
    } catch (error) {
      // Request may have timed out while the server kept working.
      await waitUntilProcessingClears(module)
      toastApiError(error, "Retry processing failed")
      await load(module)
    } finally {
      setIsRetryingProcessing(false)
    }
  }

  const handleMove = async (id: string, target: ReviewQueueModule) => {
    setBusyId(id)
    try {
      const message = await moveDeepdexUploadQueueItem(id, target)
      toast.success(message)
      await load(module)
    } catch (error) {
      toastApiError(error, "Move failed")
    } finally {
      setBusyId(null)
    }
  }

  return (
    <div className="flex flex-col">
      <FormPageHeader
        backHref={DEEPDEX_PAGES.viewAll}
        parentLabel="Deepdex"
        currentLabel="Upload queue"
        titleIcon={<Upload className="size-4 text-primary" />}
        title="Upload queue"
        description="Upload files, review queue status, then move processed items to Structure or Accumulator."
      />
      <Card className="mt-6">
        <CardHeader className="flex flex-row items-start justify-between gap-4">
          <div className="space-y-1">
            <CardTitle className="flex items-center gap-2">
              Queue items
              {!isLoading && totalCount > 0 ? <Badge variant="secondary">{totalCount}</Badge> : null}
            </CardTitle>
            <CardDescription>{QUEUE_MODULE_META[module].description}</CardDescription>
          </div>
          <div className="flex shrink-0 items-center gap-2">
            {showBulkReview ? (
              <Button
                size="sm"
                onClick={handleBulkReview}
                disabled={bulkReviewItems.length === 0}
                title={
                  module === "structure"
                    ? "Review all structure files awaiting review"
                    : "Review all accumulator files awaiting review"
                }
              >
                <SearchCheck className="size-4" />
                Bulk review
              </Button>
            ) : null}
            {showRetryProcessing ? (
              <Button
                size="sm"
                variant="outline"
                onClick={() => void handleRetryProcessing()}
                disabled={isRetryingProcessing || selectedFailedIds.length === 0}
                title="Retry processing for selected failed files"
              >
                {isRetryingProcessing ? (
                  <Loader2 className="size-4 animate-spin" />
                ) : (
                  <RotateCcw className="size-4" />
                )}
                {isRetryingProcessing ? "Submitting…" : "Retry processing"}
              </Button>
            ) : null}
            <Sheet open={isUploadOpen} onOpenChange={setIsUploadOpen}>
              <SheetTrigger asChild>
                <Button size="sm">
                  <Plus className="size-4" />
                  Add files
                </Button>
              </SheetTrigger>
              <SheetContent
                side="right"
                className="h-screen w-1/2 overflow-y-auto max-w-none border-0 data-[side=right]:w-1/2 data-[side=right]:sm:max-w-none"
              >
                <SheetHeader>
                  <SheetTitle>Upload files</SheetTitle>
                  <SheetDescription>Add files to the processing queue.</SheetDescription>
                </SheetHeader>
                <div className="p-4">
                  <DeepdexUploadForm
                    module={module}
                    onUploaded={() => {
                      setIsUploadOpen(false)
                      // New files are listed first: go back to page 1 (which reloads), else just reload.
                      if (page !== 1) setPage(1)
                      else void refresh()
                    }}
                  />
                </div>
              </SheetContent>
            </Sheet>
            <Button
              variant="outline"
              size="sm"
              onClick={() => void refresh()}
              disabled={isRefreshing || isRetryingProcessing}
            >
              {isRefreshing ? <Loader2 className="size-4 animate-spin" /> : <RefreshCw className="size-4" />}
              Refresh
            </Button>
          </div>
        </CardHeader>
        <CardContent className="space-y-4 p-0">
          <div className="px-4 pt-4">
            <Tabs
              value={module}
              onValueChange={(value) => {
                setSelectedIds([])
                setKnownItems({})
                setPage(1)
                setModule(value as typeof module)
              }}
            >
              <TabsList>
                <TabsTrigger value="deepdex">
                  Deepdex
                </TabsTrigger>
                <TabsTrigger value="structure">
                  Structure
                </TabsTrigger>
                <TabsTrigger value="accumulator">
                  Accumulator
                </TabsTrigger>
              </TabsList>
            </Tabs>
            {!isLoading && items.length > 0 && items.length === totalCount ? (
              <div className="mt-3">
                <QueueStatusSummary counts={statusCounts} />
              </div>
            ) : null}
            {showBulkReview || hasFailedItems || hasProcessingItems ? (
              <p className="mt-2 text-xs text-muted-foreground">
                {hasProcessingItems
                  ? `${processingItems.length} file${processingItems.length === 1 ? " is" : "s are"} processing in the background. This list refreshes by itself, so you can keep working or open other pages.`
                  : showBulkReview && hasFailedItems
                    ? "Use Bulk review to review all files, or select failed files to Retry processing."
                    : showBulkReview
                      ? "Use Bulk review to review all files."
                      : "Select failed files and use Retry processing."}
              </p>
            ) : null}
          </div>
          {isLoading ? (
            <Table>
              <TableBody>
                <QueueTableSkeletonRows columns={showSelectColumn ? 7 : 6} />
              </TableBody>
            </Table>
          ) : items.length === 0 ? (
            <QueueEmptyState module={module} onAddFiles={() => setIsUploadOpen(true)} />
          ) : (
            <Table>
              <TableHeader>
                <TableRow>
                  {showSelectColumn ? (
                    <TableHead className="w-10">
                      <Checkbox
                        aria-label="Select all"
                        disabled={selectableItems.length === 0}
                        checked={
                          allSelectableSelected ? true : someSelectableSelected ? "indeterminate" : false
                        }
                        onCheckedChange={(checked) => {
                          // Only this page's rows change; selections made on other pages stay.
                          const pageIds = new Set(selectableItems.map((item) => item.id))
                          setSelectedIds((prev) =>
                            checked === true
                              ? Array.from(new Set([...prev, ...pageIds]))
                              : prev.filter((id) => !pageIds.has(id)),
                          )
                        }}
                      />
                    </TableHead>
                  ) : null}
                  <TableHead className="w-16">ID</TableHead>
                  <TableHead>File name</TableHead>
                  <TableHead>Status</TableHead>
                  <TableHead>Doc type</TableHead>
                  <TableHead>Created</TableHead>
                  <TableHead className="text-right">Actions</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {items.map((item) => (
                  <TableRow key={item.id}>
                    {showSelectColumn ? (
                      <TableCell>
                        <Checkbox
                          aria-label={`Select queue item ${item.id}`}
                          checked={selectedFailedIds.includes(item.id)}
                          disabled={!isFailedItem(item)}
                          onCheckedChange={(checked) => {
                            if (!isFailedItem(item)) return
                            setSelectedIds((prev) =>
                              checked === true
                                ? prev.includes(item.id) ? prev : [...prev, item.id]
                                : prev.filter((id) => id !== item.id),
                            )
                          }}
                        />
                      </TableCell>
                    ) : null}
                    <TableCell className="font-mono text-xs text-muted-foreground">#{item.id}</TableCell>
                    <TableCell className="max-w-[26rem]">
                      <QueueFileName
                        name={item.originalName}
                        errorMessage={item.errorMessage}
                        reviewReasons={item.autoPublishReasons}
                      />
                    </TableCell>
                    <TableCell>
                      <QueueStatusBadge status={item.status} />
                    </TableCell>
                    <TableCell>
                      <Badge variant="outline" className="font-normal">
                        {getDeepdexDocTypeLabel(item.docType)}
                      </Badge>
                    </TableCell>
                    <TableCell className="whitespace-nowrap text-muted-foreground" title={item.createdAt || undefined}>
                      {formatQueueTimestamp(item.createdAt)}
                    </TableCell>
                    <TableCell className="text-right">
                      <div className="flex justify-end gap-2">
                        <Button variant="outline" size="sm" asChild>
                          <Link
                            href={getDeepdexUploadQueueViewUrl(item.viewToken)}
                            target="_blank"
                            aria-label="View file"
                            title="View file"
                          >
                            <Eye className="size-4" />
                          </Link>
                        </Button>
                        {(module === "structure" || module === "accumulator") ? (
                          <Button variant="outline" size="sm" asChild>
                            <Link
                              href={getDeepdexUploadQueueReviewUrl(module, item.id)}
                              target="_blank"
                              aria-label="Review extracted data"
                              title="Review extracted data"
                            >
                              <SearchCheck className="size-4" />
                            </Link>
                          </Button>
                        ) : null}
                        {/* Structures and accumulators are saved (and their file published) from the review form;
                            a file OCR left under Deepdex is handed to one of them from here. */}
                        {module === "deepdex" ? (
                          <DropdownMenu>
                            <DropdownMenuTrigger asChild>
                              <Button variant="default" size="sm" disabled={!item.canMove || busyId === item.id}>
                                {busyId === item.id ? <Loader2 className="size-4 animate-spin" /> : null}
                                Move to
                                <ChevronDown className="size-4" />
                              </Button>
                            </DropdownMenuTrigger>
                            <DropdownMenuContent align="end">
                              <DropdownMenuItem onSelect={() => void handleMove(item.id, "structure")}>
                                Structure
                              </DropdownMenuItem>
                              <DropdownMenuItem onSelect={() => void handleMove(item.id, "accumulator")}>
                                Accumulator
                              </DropdownMenuItem>
                            </DropdownMenuContent>
                          </DropdownMenu>
                        ) : null}
                        <Button
                          variant="outline"
                          size="sm"
                          onClick={() => void handleDelete(item.id)}
                          disabled={!item.canDelete || busyId === item.id}
                          aria-label="Delete from queue"
                          title="Delete from queue"
                        >
                          <Trash2 className="size-4" />
                        </Button>
                      </div>
                    </TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          )}
        </CardContent>
        {!isLoading && totalCount > 0 ? (
          <CardFooter className="flex flex-col gap-3 border-t pt-4 sm:flex-row sm:items-center sm:justify-between">
            <div className="flex flex-wrap items-center gap-3">
              <XmlApiPaginatedRange page={page} pageSize={pageSize} totalCount={totalCount} />
              {selectedIds.length > 0 ? (
                <Badge variant="secondary">{selectedIds.length} selected</Badge>
              ) : null}
            </div>
            <XmlApiServerPagination
              page={page}
              pageSize={pageSize}
              pageCount={pageCount}
              totalCount={totalCount}
              pageSizeOptions={PAGE_SIZE_OPTIONS}
              isLoading={isRefreshing || isRetryingProcessing}
              onPageChange={(next) => setPage(Math.min(Math.max(1, next), Math.max(1, pageCount)))}
              onPageSizeChange={(size) => {
                setPageSize(size)
                setPage(1)
              }}
            />
          </CardFooter>
        ) : null}
      </Card>
    </div>
  )
}
