"use client";

import * as React from "react";

import {
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  useReactTable,
  type PaginationState,
  type RowSelectionState,
} from "@tanstack/react-table";
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Download, FileDown, Loader2 } from "lucide-react";
import { toast } from "sonner";

import { mutationCsrfHeaders } from "@/lib/mutation-csrf.client";
import { useDebouncedValue } from "@/app/dashboard/_components/data-table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";

import { createOrderBlotterColumns } from "./columns";
import type { OrderBlotterFilters, OrderBlotterRow } from "./schema";
import { emptyOrderBlotterFilters } from "./schema";

const BULK_PDF_MAX_ITEMS = 25;

function matchesFilter(value: string | number | null | undefined, filter: string) {
  if (!filter.trim()) return true;
  if (value == null) return false;
  return String(value).toLowerCase().includes(filter.trim().toLowerCase());
}

function filterRows(rows: OrderBlotterRow[], filters: OrderBlotterFilters) {
  return rows.filter(
    (row) =>
      matchesFilter(row.indexClientName, filters.indexClientName) &&
      matchesFilter(row.transactionId, filters.transactionId) &&
      matchesFilter(row.indexOrderNumber, filters.indexOrderNumber) &&
      matchesFilter(row.updatedAt, filters.updatedAt),
  );
}

function exportCsv(rows: OrderBlotterRow[]) {
  const headers = ["S/N", "Client", "Txn ID", "Order #", "Updated"];
  const lines = rows.map((row, index) => {
    const cells = [
      String(index + 1),
      row.indexClientName ?? "",
      row.transactionId != null ? String(row.transactionId) : "",
      row.indexOrderNumber ?? "",
      row.updatedAt,
    ];
    return cells.map((cell) => `"${cell.replace(/"/g, '""')}"`).join(",");
  });

  const csv = [headers.join(","), ...lines].join("\n");
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
  const url = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = url;
  link.download = `order-blotter-${new Date().toISOString().slice(0, 10)}.csv`;
  link.click();
  URL.revokeObjectURL(url);
}

function filenameFromContentDisposition(header: string | null): string | null {
  if (!header) return null;
  const utfMatch = /filename\*=UTF-8''([^;]+)/i.exec(header);
  if (utfMatch?.[1]) {
    try {
      return decodeURIComponent(utfMatch[1].trim().replace(/^"|"$/g, ""));
    } catch {
      return utfMatch[1].trim().replace(/^"|"$/g, "");
    }
  }
  const plainMatch = /filename="?([^";]+)"?/i.exec(header);
  return plainMatch?.[1]?.trim() || null;
}

function rowsToBulkPdfItems(rows: OrderBlotterRow[]) {
  return rows.map((row) => ({
    id: row.id > 0 ? row.id : null,
    ref: row.ref?.trim() || null,
    portfolio: row.portfolio?.trim() || null,
    corporate_db: row.portfolio?.trim() || null,
    federated_key: row.federatedKey?.trim() || null,
  }));
}

async function downloadBulkPdfs(bulkPdfPath: string, rows: OrderBlotterRow[]) {
  const response = await fetch(bulkPdfPath, {
    method: "POST",
    credentials: "same-origin",
    headers: {
      Accept: "application/zip,application/octet-stream",
      "Content-Type": "application/json",
      ...mutationCsrfHeaders(bulkPdfPath),
    },
    body: JSON.stringify({ items: rowsToBulkPdfItems(rows) }),
    cache: "no-store",
  });

  const buffer = await response.arrayBuffer();
  if (!response.ok) {
    let message = "Order blotter ZIP download failed.";
    try {
      const json = JSON.parse(new TextDecoder().decode(buffer)) as { message?: unknown };
      if (typeof json.message === "string" && json.message.trim()) {
        message = json.message.trim();
      }
    } catch {
      // keep fallback
    }
    throw new Error(message);
  }

  const bytes = new Uint8Array(buffer);
  const isZip = bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b;
  if (!isZip) {
    let message = "Download did not return a valid ZIP file.";
    try {
      const json = JSON.parse(new TextDecoder().decode(buffer)) as { message?: unknown };
      if (typeof json.message === "string" && json.message.trim()) {
        message = json.message.trim();
      }
    } catch {
      // keep fallback
    }
    throw new Error(message);
  }

  const blob = new Blob([buffer], { type: "application/zip" });
  const url = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = url;
  link.download =
    filenameFromContentDisposition(response.headers.get("content-disposition")) ||
    `order_blotters_${new Date().toISOString().slice(0, 10)}.zip`;
  link.click();
  URL.revokeObjectURL(url);
}

type OrderBlotterTableProps = {
  data: OrderBlotterRow[];
  showPortfolio?: boolean;
  basePath?: string;
  /** Next BFF route that returns a ZIP of PDFs. Defaults to `{basePath}/bulk-pdf`. */
  bulkPdfPath?: string;
};

export function OrderBlotterTable({
  data,
  basePath = "/dashboard/order-blotter",
  bulkPdfPath,
}: OrderBlotterTableProps) {
  const resolvedBulkPdfPath = bulkPdfPath ?? `${basePath.replace(/\/$/, "")}/bulk-pdf`;
  const [filters, setFilters] = React.useState<OrderBlotterFilters>(emptyOrderBlotterFilters);
  const [pagination, setPagination] = React.useState<PaginationState>({
    pageIndex: 0,
    pageSize: 20,
  });
  const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
  const [bulkDownloading, setBulkDownloading] = React.useState(false);
  const debouncedFilters = useDebouncedValue(filters);

  const filteredData = React.useMemo(
    () => filterRows(data, debouncedFilters),
    [data, debouncedFilters],
  );

  const columns = React.useMemo(
    () => createOrderBlotterColumns({ basePath }),
    [basePath],
  );

  const table = useReactTable({
    data: filteredData,
    columns,
    state: { pagination, rowSelection },
    onPaginationChange: setPagination,
    onRowSelectionChange: setRowSelection,
    enableRowSelection: true,
    getCoreRowModel: getCoreRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    // Prefer federatedKey (DB:id). Fall back to ref+index — refs can collide when
    // transaction_corporate_db differs from the row's actual database.
    getRowId: (row, index) => {
      const federatedKey = row.federatedKey?.trim();
      if (federatedKey) return federatedKey;
      const ref = row.ref?.trim();
      if (ref) return `${ref}#${row.portfolio ?? ""}#${row.transactionId ?? ""}#${index}`;
      return `${row.id}:${row.portfolio ?? ""}:${row.transactionId ?? ""}:${index}`;
    },
  });

  const selectedRows = table.getSelectedRowModel().rows.map((row) => row.original);
  const selectedCount = selectedRows.length;

  const handleBulkPdfDownload = React.useCallback(async () => {
    const rows = table.getSelectedRowModel().rows.map((row) => row.original);
    if (rows.length === 0) {
      toast.error("Select at least one order blotter.");
      return;
    }
    if (rows.length > BULK_PDF_MAX_ITEMS) {
      toast.error(`You can download at most ${BULK_PDF_MAX_ITEMS} PDFs at once.`);
      return;
    }

    setBulkDownloading(true);
    try {
      await downloadBulkPdfs(resolvedBulkPdfPath, rows);
      toast.success(
        rows.length === 1
          ? "Downloaded 1 order blotter PDF."
          : `Downloaded ZIP with ${rows.length} PDFs.`,
      );
    } catch (error) {
      toast.error(error instanceof Error ? error.message : "Order blotter ZIP download failed.");
    } finally {
      setBulkDownloading(false);
    }
  }, [resolvedBulkPdfPath, table]);

  const updateFilter = (key: keyof OrderBlotterFilters, value: string) => {
    setFilters((prev) => ({ ...prev, [key]: value }));
    setPagination((prev) => ({ ...prev, pageIndex: 0 }));
  };

  const filterFields: { key: keyof OrderBlotterFilters; placeholder: string }[] = [
    { key: "indexClientName", placeholder: "Client" },
    { key: "transactionId", placeholder: "Txn ID" },
    { key: "indexOrderNumber", placeholder: "Order #" },
    { key: "updatedAt", placeholder: "Updated" },
  ];

  return (
    <div className="space-y-4">
      {selectedCount > 0 ? (
        <div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border bg-muted/20 px-3 py-2">
          <p className="text-muted-foreground text-sm">
            {selectedCount} selected
            {selectedCount > BULK_PDF_MAX_ITEMS
              ? ` (max ${BULK_PDF_MAX_ITEMS} for PDF ZIP)`
              : null}
          </p>
          <div className="flex items-center gap-2">
            <Button
              type="button"
              variant="ghost"
              size="sm"
              onClick={() => setRowSelection({})}
              disabled={bulkDownloading}
            >
              Clear
            </Button>
            <Button
              type="button"
              variant="outline"
              size="sm"
              className="gap-2"
              onClick={() => void handleBulkPdfDownload()}
              disabled={bulkDownloading || selectedCount > BULK_PDF_MAX_ITEMS}
            >
              {bulkDownloading ? (
                <Loader2 className="size-4 animate-spin" />
              ) : (
                <FileDown className="size-4" />
              )}
              Download PDFs
            </Button>
          </div>
        </div>
      ) : null}

      <div className="overflow-hidden rounded-lg border bg-card">
        <Table>
          <TableHeader className="bg-muted/15">
            {table.getHeaderGroups().map((headerGroup) => (
              <TableRow key={headerGroup.id}>
                {headerGroup.headers.map((header) => (
                  <TableHead
                    key={header.id}
                    className={header.id === "select" ? "h-10 w-10 px-3" : "h-10 px-3 font-medium"}
                  >
                    {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
                  </TableHead>
                ))}
              </TableRow>
            ))}
            <TableRow className="bg-muted/5 hover:bg-muted/5">
              <TableCell className="p-2" />
              <TableCell className="p-2" />
              {filterFields.map((field) => (
                <TableCell key={field.key} className="p-2">
                  <Input
                    value={filters[field.key]}
                    onChange={(event) => updateFilter(field.key, event.target.value)}
                    placeholder={field.placeholder}
                    className="h-8 bg-background text-xs"
                    aria-label={`Filter by ${field.placeholder}`}
                  />
                </TableCell>
              ))}
              <TableCell className="p-2" />
            </TableRow>
          </TableHeader>
          <TableBody>
            {table.getRowModel().rows.length ? (
              table.getRowModel().rows.map((row) => (
                <TableRow
                  key={row.id}
                  data-state={row.getIsSelected() ? "selected" : undefined}
                  className="hover:bg-muted/30"
                >
                  {row.getVisibleCells().map((cell) => (
                    <TableCell
                      key={cell.id}
                      className={
                        cell.column.id === "select"
                          ? "w-10 px-3 py-2.5 align-middle"
                          : "px-3 py-2.5 align-middle text-sm"
                      }
                    >
                      {flexRender(cell.column.columnDef.cell, cell.getContext())}
                    </TableCell>
                  ))}
                </TableRow>
              ))
            ) : (
              <TableRow>
                <TableCell colSpan={columns.length} className="h-24 text-center text-muted-foreground">
                  No order blotter records match your filters.
                </TableCell>
              </TableRow>
            )}
          </TableBody>
        </Table>
      </div>

      <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
        <p className="text-muted-foreground text-sm">
          {filteredData.length} record{filteredData.length === 1 ? "" : "s"}
        </p>
        <div className="flex w-full flex-wrap items-center justify-end gap-4 sm:w-auto">
          <div className="flex items-center gap-2">
            <Label htmlFor="order-blotter-page-size" className="font-medium text-sm whitespace-nowrap">
              Rows per page
            </Label>
            <Select
              value={`${table.getState().pagination.pageSize}`}
              onValueChange={(value) => {
                table.setPageSize(Number(value));
              }}
            >
              <SelectTrigger size="sm" className="w-20" id="order-blotter-page-size">
                <SelectValue placeholder={table.getState().pagination.pageSize} />
              </SelectTrigger>
              <SelectContent side="top">
                <SelectGroup>
                  {[10, 20, 30, 50].map((pageSize) => (
                    <SelectItem key={pageSize} value={`${pageSize}`}>
                      {pageSize}
                    </SelectItem>
                  ))}
                </SelectGroup>
              </SelectContent>
            </Select>
          </div>
          <div className="font-medium text-sm tabular-nums">
            Page {table.getState().pagination.pageIndex + 1} of {Math.max(table.getPageCount(), 1)}
          </div>
          <div className="flex items-center gap-1">
            <Button
              variant="outline"
              size="icon"
              className="size-8"
              onClick={() => table.setPageIndex(0)}
              disabled={!table.getCanPreviousPage()}
            >
              <span className="sr-only">First page</span>
              <ChevronsLeft className="size-4" />
            </Button>
            <Button
              variant="outline"
              size="icon"
              className="size-8"
              onClick={() => table.previousPage()}
              disabled={!table.getCanPreviousPage()}
            >
              <span className="sr-only">Previous page</span>
              <ChevronLeft className="size-4" />
            </Button>
            <Button
              variant="outline"
              size="icon"
              className="size-8"
              onClick={() => table.nextPage()}
              disabled={!table.getCanNextPage()}
            >
              <span className="sr-only">Next page</span>
              <ChevronRight className="size-4" />
            </Button>
            <Button
              variant="outline"
              size="icon"
              className="size-8"
              onClick={() => table.setPageIndex(table.getPageCount() - 1)}
              disabled={!table.getCanNextPage()}
            >
              <span className="sr-only">Last page</span>
              <ChevronsRight className="size-4" />
            </Button>
          </div>
        </div>
      </div>
    </div>
  );
}

export function OrderBlotterExportButton({
  data,
}: {
  data: OrderBlotterRow[];
  showPortfolio?: boolean;
}) {
  return (
    <Button
      variant="outline"
      size="sm"
      onClick={() => exportCsv(data)}
      className="gap-2"
    >
      <Download className="size-4" />
      Export CSV
    </Button>
  );
}
