import { useState, type ReactNode } from 'react';
import { LayoutGroup } from 'framer-motion';
import { ArrowDown, ArrowUp, CheckCircle2, Loader2 } from 'lucide-react';
import { toast } from 'sonner';
import { api } from '@/api';
import { cn, fmt, errMessage } from '@/lib/utils';
import { asDollars } from '@/lib/money';
import {
  Table, TableHeader, TableBody, TableHead, TableRow, TableCell,
} from '@/components/ui/table';
import {
  AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle,
  AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction,
} from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import {
  TRACKER_SORT_ASC, TRACKER_SORT_DEFAULT, moveInArray, rowIsPaid, paymentDateForTrackerMonth,
} from '@/lib/trackerUtils';
import { DEFAULT_TRACKER_TABLE_COLUMNS } from '@/lib/trackerTableColumns';
import { TrackerRow as Row } from '@/components/tracker/TrackerRow';
import { MobileTrackerRow, type DragProps, type MoveControls, type RowSelection } from '@/components/tracker/MobileTrackerRow';
import type { Payment, TrackerRow } from '@/types';

interface SortableHeadProps {
  sortKey: string;
  activeSortKey?: string;
  sortDir?: string;
  onSort?: (key: string) => void;
  children?: ReactNode;
  className?: string;
}

function SortableHead({ sortKey, activeSortKey, sortDir, onSort, children, className }: SortableHeadProps) {
  const active = activeSortKey === sortKey;
  const Icon = sortDir === TRACKER_SORT_ASC ? ArrowUp : ArrowDown;
  return (
    <TableHead
      aria-sort={active ? (sortDir === TRACKER_SORT_ASC ? 'ascending' : 'descending') : 'none'}
      className={cn('h-11 py-2.5', className)}
    >
      <button
        type="button"
        onClick={() => onSort?.(sortKey)}
        className={cn(
          'group inline-flex h-8 items-center gap-1.5 rounded-md px-2 text-[11px] font-semibold uppercase tracking-[0.08em] transition-all',
          'hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60',
          active ? 'bg-accent/70 text-foreground shadow-sm' : 'text-muted-foreground'
        )}
      >
        <span>{children}</span>
        <Icon
          className={cn(
            'h-3.5 w-3.5 transition-opacity',
            active ? 'opacity-100' : 'opacity-0 group-hover:opacity-55'
          )}
          aria-hidden="true"
        />
      </button>
    </TableHead>
  );
}

interface TrackerBucketProps {
  label: string;
  rows: TrackerRow[];
  year: number;
  month: number;
  refresh: () => void;
  onEditBill?: (row: TrackerRow) => void;
  loading?: boolean;
  onReorderRows?: (rows: TrackerRow[]) => void;
  reorderEnabled?: boolean;
  movingBillId?: number | null;
  sortKey?: string;
  sortDir?: string;
  onSort?: (key: string) => void;
  driftedIds?: Set<number>;
  visibleColumns?: string[];
  selectedIds?: Set<number>;
  onToggleSelect?: (id: number, next: boolean) => void;
}

export function TrackerBucket({
  label,
  rows,
  year,
  month,
  refresh,
  onEditBill,
  loading,
  onReorderRows,
  reorderEnabled,
  movingBillId,
  sortKey = TRACKER_SORT_DEFAULT,
  sortDir = TRACKER_SORT_ASC,
  onSort,
  driftedIds = new Set<number>(),
  visibleColumns = DEFAULT_TRACKER_TABLE_COLUMNS,
  selectedIds,
  onToggleSelect,
}: TrackerBucketProps) {
  const [draggingId, setDraggingId] = useState<number | null>(null);
  const [dropTargetId, setDropTargetId] = useState<number | null>(null);
  // Build the per-row selection prop only when the page provides a toggle handler.
  const selectionFor = (r: TrackerRow): RowSelection | undefined =>
    onToggleSelect ? { selected: selectedIds?.has(r.id) ?? false, onToggle: (next) => onToggleSelect(r.id, next) } : undefined;
  const visibleColumnSet = new Set(visibleColumns);
  const showColumn = (key: string) => visibleColumnSet.has(key);
  const tableColSpan = 1 + visibleColumns.length;
  // Use actual_amount (if set) as the per-row threshold; exclude skipped rows from totals
  const activeRows    = rows.filter(r => !r.is_skipped);
  const totalThreshold = activeRows.reduce((s, r) => s + (r.actual_amount ?? r.expected_amount ?? 0), 0);
  const totalPaid      = activeRows.reduce((s, r) => s + (r.total_paid || 0), 0);
  const totalPaidTowardDue = activeRows.reduce((s, r) => {
    const threshold = Number(r.actual_amount ?? r.expected_amount ?? 0) || 0;
    const cappedPaid = Number(r.paid_toward_due);
    return s + (Number.isFinite(cappedPaid) ? cappedPaid : Math.min(Number(r.total_paid) || 0, threshold));
  }, 0);
  const totalOverpaid   = Math.max(totalPaid - totalPaidTowardDue, 0);
  const totalRemaining  = Math.max(totalThreshold - totalPaidTowardDue, 0);
  const skippedCount    = rows.length - activeRows.length;
  const pct             = totalThreshold > 0 ? Math.min((totalPaidTowardDue / totalThreshold) * 100, 100) : 0;
  const allPaid         = pct >= 100;

  // "Pay all due" — every non-skipped, not-yet-paid row in this bucket, paid for
  // its remaining balance in one bulk call. Rows are already occurrence-gated by
  // the server, so they're all genuinely due this month.
  const [payAllOpen, setPayAllOpen] = useState(false);
  const [payingAll, setPayingAll]   = useState(false);
  const unpaidRows = activeRows.filter(r => !rowIsPaid(r));
  const payAllItems = unpaidRows.map(r => {
    const threshold = Number(r.actual_amount ?? r.expected_amount ?? 0) || 0;
    const remaining = Math.max(threshold - (Number(r.total_paid) || 0), 0);
    return {
      bill_id: r.id,
      name: r.name,
      amount: remaining > 0 ? remaining : threshold,
      paid_date: paymentDateForTrackerMonth(year, month, r.due_day),
    };
  }).filter(item => item.amount > 0);
  const payAllTotal = payAllItems.reduce((s, i) => s + i.amount, 0);

  async function handlePayAllDue() {
    setPayingAll(true);
    try {
      const result = await api.bulkPay(payAllItems.map(({ name, ...item }) => item)) as { created?: Payment[] };
      const created = result.created || [];
      setPayAllOpen(false);
      if (created.length === 0) {
        toast.info('Those bills were already paid.');
      } else {
        toast.success(`Paid ${created.length} bill${created.length === 1 ? '' : 's'} — ${fmt(asDollars(payAllTotal))}`, {
          action: {
            label: 'Undo',
            onClick: async () => {
              try {
                await Promise.all(created.map(p => api.deletePayment(p.id)));
                toast.success('Payments removed');
                refresh?.();
              } catch (err) {
                toast.error(errMessage(err, 'Failed to undo payments.'));
              }
            },
          },
        });
      }
      refresh?.();
    } catch (err) {
      toast.error(errMessage(err, 'Failed to pay bills.'));
    } finally {
      setPayingAll(false);
    }
  }

  function reorderByIndex(fromIndex: number, toIndex: number) {
    if (!reorderEnabled || fromIndex === toIndex || fromIndex < 0 || toIndex < 0) return;
    onReorderRows?.(moveInArray(rows, fromIndex, toIndex));
  }

  function dragPropsFor(row: TrackerRow, index: number): DragProps {
    if (!reorderEnabled) return { draggable: false };
    return {
      draggable: true,
      isDragging: draggingId === row.id,
      isDropTarget: dropTargetId === row.id && draggingId !== row.id,
      onDragStart: (event) => {
        setDraggingId(row.id);
        event.dataTransfer.effectAllowed = 'move';
        event.dataTransfer.setData('text/plain', String(row.id));
      },
      onDragEnter: () => {
        if (draggingId && draggingId !== row.id) setDropTargetId(row.id);
      },
      onDragOver: (event) => {
        event.preventDefault();
        event.dataTransfer.dropEffect = 'move';
        if (draggingId && draggingId !== row.id) setDropTargetId(row.id);
      },
      onDrop: (event) => {
        event.preventDefault();
        const sourceId = Number(event.dataTransfer.getData('text/plain') || draggingId);
        const fromIndex = rows.findIndex(item => item.id === sourceId);
        reorderByIndex(fromIndex, index);
        setDraggingId(null);
        setDropTargetId(null);
      },
      onDragEnd: () => {
        setDraggingId(null);
        setDropTargetId(null);
      },
    };
  }

  function moveControlsFor(row: TrackerRow, index: number): MoveControls {
    return {
      enabled: !!reorderEnabled,
      moving: movingBillId === row.id,
      canMoveUp: index > 0,
      canMoveDown: index < rows.length - 1,
      onMoveUp: () => reorderByIndex(index, index - 1),
      onMoveDown: () => reorderByIndex(index, index + 1),
    };
  }

  return (
    <div className="rounded-xl border border-border/80 overflow-hidden bg-card/95 shadow-sm shadow-black/15">

      {/* Bucket header */}
      <div className="flex flex-col gap-2 bg-muted/35 px-3 py-3 border-b border-border/80 sm:px-5 md:flex-row md:items-center md:justify-between">
        <div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1.5">
          <div className="flex min-w-0 items-center gap-2">
            <span className="truncate text-[11px] font-bold uppercase tracking-[0.12em] text-muted-foreground">
              {label}
            </span>
            {skippedCount > 0 && (
              <span className="shrink-0 text-[10px] text-muted-foreground/60">
                ({skippedCount} skipped)
              </span>
            )}
          </div>
          <div className="flex min-w-[7rem] items-center gap-2">
            <div className="h-1.5 w-16 rounded-full bg-border overflow-hidden sm:w-24">
              <div
                className={cn(
                  'h-full rounded-full transition-all duration-700',
                  allPaid ? 'bg-emerald-500' : 'bg-emerald-400/70',
                )}
                style={{ width: `${pct}%` }}
              />
            </div>
            <span className="text-[11px] font-mono text-muted-foreground/70">
              {Math.round(pct)}%
            </span>
          </div>
        </div>
        <div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs font-mono text-muted-foreground md:justify-end">
          <span className="whitespace-nowrap">
            <span className={cn(allPaid ? 'text-emerald-500' : 'text-foreground')}>
              {fmt(asDollars(totalPaidTowardDue))}
            </span>
            <span className="text-muted-foreground/50 mx-1">/</span>
            {fmt(asDollars(totalThreshold))}
            {totalOverpaid > 0 && (
              <span className="ml-1 text-emerald-500">+{fmt(asDollars(totalOverpaid))}</span>
            )}
          </span>
          {!allPaid && totalRemaining > 0 && (
            <span className="text-[11px] text-muted-foreground/70">{fmt(asDollars(totalRemaining))} left</span>
          )}
          {allPaid && (
            <span className="text-[11px] text-emerald-500">Done</span>
          )}
          {!reorderEnabled && rows.length > 1 && (
            <span className="hidden text-[11px] text-muted-foreground/60 xl:inline">Clear filters to reorder</span>
          )}
          {payAllItems.length > 0 && !loading && (
            <Button
              size="sm"
              variant="outline"
              className="h-7 gap-1.5 text-[11px] font-sans"
              disabled={payingAll}
              onClick={() => setPayAllOpen(true)}
            >
              {payingAll
                ? <><Loader2 className="h-3.5 w-3.5 animate-spin" />Paying…</>
                : <><CheckCircle2 className="h-3.5 w-3.5" />Pay all due ({payAllItems.length})</>}
            </Button>
          )}
        </div>
      </div>

      <AlertDialog open={payAllOpen} onOpenChange={setPayAllOpen}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Pay all due in {label}?</AlertDialogTitle>
            <AlertDialogDescription>
              This records a payment for {payAllItems.length} unpaid bill{payAllItems.length === 1 ? '' : 's'} in this
              bucket, totalling {fmt(asDollars(payAllTotal))}. You can undo it right after.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel disabled={payingAll}>Cancel</AlertDialogCancel>
            <AlertDialogAction onClick={(e) => { e.preventDefault(); handlePayAllDue(); }} disabled={payingAll}>
              {payingAll ? 'Paying…' : `Pay ${fmt(asDollars(payAllTotal))}`}
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>

      <LayoutGroup id={`tracker-bucket-mobile-${label}`}>
      <div className="grid gap-3 p-3 lg:hidden" aria-busy={loading ? 'true' : 'false'}>
        {loading ? (
          Array.from({ length: 3 }).map((_, i) => (
            <div key={i} className="rounded-lg border border-border/60 bg-background/60 p-3 animate-pulse">
              <div className="flex min-w-0 items-start justify-between gap-3">
                <div className="min-w-0">
                  <div className="flex min-w-0 items-center gap-2">
                    <div className="h-1.5 w-1.5 shrink-0 rounded-full bg-muted" />
                    <div className="h-4 w-32 rounded-md bg-muted" />
                  </div>
                </div>
                <div className="h-5 w-20 rounded-md bg-muted" />
              </div>
              <div className="grid grid-cols-2 gap-x-3 gap-y-2 text-xs text-muted-foreground mt-2">
                <div>
                  <p className="uppercase tracking-wide text-muted-foreground/60">Expected</p>
                  <div className="h-3 w-20 rounded-md bg-muted mt-0.5" />
                </div>
                <div>
                  <p className="uppercase tracking-wide text-muted-foreground/60">Remaining</p>
                  <div className="h-3 w-20 rounded-md bg-muted mt-0.5" />
                </div>
              </div>
            </div>
          ))
        ) : rows.length === 0 ? (
          <div className="rounded-lg bg-muted/15 px-4 py-8 text-center text-sm text-muted-foreground">
            No bills match — try adjusting your filters.
          </div>
        ) : (
          rows.map((r, i) => (
            <MobileTrackerRow
              key={r.id}
              row={r}
              year={year}
              month={month}
              refresh={refresh}
              index={i}
              onEditBill={onEditBill}
              moveControls={moveControlsFor(r, i)}
              dragProps={dragPropsFor(r, i)}
              isDrifted={driftedIds.has(r.id)}
              selection={selectionFor(r)}
            />
          ))
        )}
      </div>
      </LayoutGroup>

      <div className="hidden lg:block" aria-busy={loading ? 'true' : 'false'}>
        <div className="overflow-x-auto">
          <Table className={cn(visibleColumns.length <= 4 ? 'min-w-[720px]' : 'min-w-[1120px]')}>
          <TableHeader>
            <TableRow className="border-border/80 bg-background/30 hover:bg-background/30">
              <SortableHead sortKey="name" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[18%]">Bill</SortableHead>
              {showColumn('due') && (
                <SortableHead sortKey="due" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%]">Due</SortableHead>
              )}
              {showColumn('expected') && (
                <SortableHead sortKey="expected" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%] text-center">Expected</SortableHead>
              )}
              {showColumn('previous') && (
                <SortableHead sortKey="previous" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%] text-center">Last Month</SortableHead>
              )}
              {showColumn('paid') && (
                <SortableHead sortKey="paid" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%] text-center">Paid</SortableHead>
              )}
              {showColumn('paid_date') && (
                <SortableHead sortKey="paid_date" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[10%]">Paid Date</SortableHead>
              )}
              {showColumn('status') && (
                <SortableHead sortKey="status" activeSortKey={sortKey} sortDir={sortDir} onSort={onSort} className="w-[9%] text-center">Status</SortableHead>
              )}
              {showColumn('action') && (
                <TableHead className="h-11 w-[10%] py-2.5 text-center text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
                  Action
                </TableHead>
              )}
              {showColumn('notes') && (
                <TableHead className="h-11 w-[23%] py-2.5 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground border-l border-border/80 pl-4">
                  Notes
                </TableHead>
              )}
            </TableRow>
          </TableHeader>
          <TableBody>
            {loading ? (
              Array.from({ length: 5 }).map((_, i) => (
                <TableRow key={i} className="border-border/50">
                  <TableCell className="w-[18%] py-3">
                    <div className="flex items-center gap-2.5">
                      <div className="h-1.5 w-1.5 rounded-full bg-muted" />
                      <div className="h-4 w-48 rounded-md bg-muted" />
                    </div>
                  </TableCell>
                  {showColumn('due') && <TableCell className="w-[10%] py-3"><div className="h-3 w-20 rounded-md bg-muted" /></TableCell>}
                  {showColumn('expected') && <TableCell className="w-[10%] py-3 text-center"><div className="mx-auto h-3 w-20 rounded-md bg-muted" /></TableCell>}
                  {showColumn('previous') && <TableCell className="w-[10%] py-3 text-center"><div className="mx-auto h-3 w-20 rounded-md bg-muted" /></TableCell>}
                  {showColumn('paid') && <TableCell className="w-[10%] py-3"><div className="mx-auto h-7 w-24 rounded-md bg-muted" /></TableCell>}
                  {showColumn('paid_date') && <TableCell className="w-[10%] py-3"><div className="h-7 w-24 rounded-md bg-muted" /></TableCell>}
                  {showColumn('status') && <TableCell className="w-[9%] py-3"><div className="mx-auto h-5 w-20 rounded-md bg-muted" /></TableCell>}
                  {showColumn('action') && (
                    <TableCell className="w-[10%] py-3 text-center">
                      <div className="flex items-center justify-center gap-1">
                        <div className="h-7 w-20 rounded-md bg-muted" />
                        <div className="h-7 w-7 rounded-md bg-muted" />
                      </div>
                    </TableCell>
                  )}
                  {showColumn('notes') && (
                    <TableCell className="w-[23%] py-3 border-l border-border pl-4">
                      <div className="h-4 w-full rounded-md bg-muted" />
                    </TableCell>
                  )}
                </TableRow>
              ))
            ) : rows.length === 0 ? (
              <TableRow className="border-border/50">
                <TableCell colSpan={tableColSpan} className="py-10 text-center text-sm text-muted-foreground">
                  No bills match — try adjusting your filters.
                </TableCell>
              </TableRow>
            ) : (
              <LayoutGroup id={`tracker-bucket-table-${label}`}>
                {rows.map((r, i) => (
                  <Row
                    key={r.id}
                    row={r}
                    year={year}
                    month={month}
                    refresh={refresh}
                    index={i}
                    onEditBill={onEditBill}
                    moveControls={moveControlsFor(r, i)}
                    dragProps={dragPropsFor(r, i)}
                    isDrifted={driftedIds.has(r.id)}
                    visibleColumns={visibleColumns}
                    selection={selectionFor(r)}
                  />
                ))}
              </LayoutGroup>
            )}
          </TableBody>
        </Table>
        </div>
      </div>
    </div>
  );
}
