'use client';

import { useMemo, useState } from 'react';
import { useMutation, useQueryClient, type QueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { AlertCircle, Loader2 } from 'lucide-react';

import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
} from '@/components/ui/sheet';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import { SUPPORTED_CURRENCIES, formatCurrency } from '@/lib/utils/currency';
import type { BerthRow } from './berth-columns';

interface BulkPriceEditSheetProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  /** IDs of the rows the rep selected in the list. */
  ids: string[];
}

interface DraftRow {
  berthId: string;
  mooringNumber: string;
  area: string | null;
  currentPrice: string | null;
  currentCurrency: string;
  newPrice: string;
  newCurrency: string;
}

type BulkResponse = {
  data: { updated: number; unchanged: number; missing: number; missingIds: string[] };
};

const NO_CURRENCY_CHANGE = '__no_change__';

/**
 * Outer sheet shell + a body that's keyed on the selection so opening
 * with a fresh set of ids remounts the inner component. That lets us
 * seed `useState` from the React Query cache via the initializer
 * function instead of a setState-in-effect.
 */
export function BulkPriceEditSheet({ open, onOpenChange, ids }: BulkPriceEditSheetProps) {
  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent
        side="right"
        className="flex w-full flex-col gap-0 p-0 sm:max-w-lg lg:max-w-2xl"
      >
        {open ? (
          <BulkPriceEditBody key={ids.join(',')} ids={ids} onClose={() => onOpenChange(false)} />
        ) : null}
      </SheetContent>
    </Sheet>
  );
}

function resolveCachedBerths(qc: QueryClient, ids: string[]): BerthRow[] {
  const cached = qc.getQueriesData<{ data: BerthRow[]; total?: number }>({
    queryKey: ['berths'],
  });
  const set = new Map<string, BerthRow>();
  for (const [, payload] of cached) {
    const rows = (payload as { data?: BerthRow[] } | undefined)?.data;
    if (!rows) continue;
    for (const r of rows) set.set(r.id, r);
  }
  return ids.map((id) => set.get(id)).filter((r): r is BerthRow => Boolean(r));
}

function BulkPriceEditBody({ ids, onClose }: { ids: string[]; onClose: () => void }) {
  const qc = useQueryClient();

  // Resolve the selected rows from React Query's cache. The list endpoint
  // doesn't support an ids= filter, so we lean on the cache — the typical
  // bulk-select scenario is multi-select inside the currently-rendered
  // page, which is always cached. Any ids the cache can't resolve are
  // still sent to the backend; the bulk-update endpoint reports them as
  // `missing` in its response. Compute synchronously on every render —
  // this component is keyed on ids in its parent so the work is bounded.
  const cachedRows = useMemo(() => resolveCachedBerths(qc, ids), [qc, ids]);
  const unresolvedCount = ids.length - cachedRows.length;

  const [drafts, setDrafts] = useState<DraftRow[]>(() =>
    cachedRows.map((r) => ({
      berthId: r.id,
      mooringNumber: r.mooringNumber,
      area: r.area,
      currentPrice: r.price,
      currentCurrency: r.priceCurrency,
      newPrice: r.price ?? '',
      newCurrency: r.priceCurrency,
    })),
  );
  const [setAllPrice, setSetAllPrice] = useState('');
  const [setAllCurrency, setSetAllCurrency] = useState<string>(NO_CURRENCY_CHANGE);
  const [adjustPercent, setAdjustPercent] = useState('');

  function applySetAll() {
    if (setAllPrice.trim() === '') {
      toast.error('Enter an amount to apply to every selected berth.');
      return;
    }
    const n = Number(setAllPrice);
    if (!Number.isFinite(n) || n < 0) {
      toast.error('Amount must be a positive number.');
      return;
    }
    setDrafts((rows) =>
      rows.map((r) => ({
        ...r,
        newPrice: String(n),
        newCurrency: setAllCurrency === NO_CURRENCY_CHANGE ? r.newCurrency : setAllCurrency,
      })),
    );
  }

  function applyPercent() {
    if (adjustPercent.trim() === '') {
      toast.error('Enter a % to adjust by (positive or negative).');
      return;
    }
    const pct = Number(adjustPercent);
    if (!Number.isFinite(pct)) {
      toast.error('% must be a number.');
      return;
    }
    setDrafts((rows) =>
      rows.map((r) => {
        const base = r.currentPrice ? Number(r.currentPrice) : null;
        if (base === null || !Number.isFinite(base)) return r;
        const next = Math.max(0, Math.round(base * (1 + pct / 100)));
        return { ...r, newPrice: String(next) };
      }),
    );
  }

  function resetAll() {
    setDrafts((rows) =>
      rows.map((r) => ({
        ...r,
        newPrice: r.currentPrice ?? '',
        newCurrency: r.currentCurrency,
      })),
    );
    setSetAllPrice('');
    setSetAllCurrency(NO_CURRENCY_CHANGE);
    setAdjustPercent('');
  }

  // Build the diff payload — only rows whose price OR currency actually
  // changed from the loaded baseline. Stops us from billing the backend
  // (and the audit log) for no-op rows when the rep tweaks only a few.
  const dirtyDrafts = useMemo(
    () =>
      drafts.filter((r) => {
        const newPriceNum = r.newPrice.trim() === '' ? null : Number(r.newPrice);
        const currentPriceNum = r.currentPrice === null ? null : Number(r.currentPrice);
        const priceChanged = newPriceNum !== currentPriceNum;
        const currencyChanged = r.newCurrency !== r.currentCurrency;
        return priceChanged || currencyChanged;
      }),
    [drafts],
  );

  const mutation = useMutation<BulkResponse, Error, void>({
    mutationFn: async () => {
      const updates = dirtyDrafts.map((r) => {
        const trimmed = r.newPrice.trim();
        const parsed = trimmed === '' ? null : Number(trimmed);
        if (parsed !== null && (!Number.isFinite(parsed) || parsed < 0)) {
          throw new Error(`Berth ${r.mooringNumber}: price must be ≥ 0`);
        }
        return {
          berthId: r.berthId,
          price: parsed,
          ...(r.newCurrency !== r.currentCurrency ? { priceCurrency: r.newCurrency } : {}),
        };
      });
      return apiFetch<BulkResponse>('/api/v1/berths/bulk-update-prices', {
        method: 'POST',
        body: { updates },
      });
    },
    onSuccess: (res) => {
      const { updated, unchanged, missing } = res.data;
      const parts = [`${updated} updated`];
      if (unchanged > 0) parts.push(`${unchanged} unchanged`);
      if (missing > 0) parts.push(`${missing} not found`);
      toast.success(parts.join(' · '));
      void qc.invalidateQueries({ queryKey: ['berths'] });
      onClose();
    },
    onError: (err) => toastError(err),
  });

  return (
    <>
      <SheetHeader className="border-b p-6">
        <SheetTitle>Bulk update prices</SheetTitle>
        <SheetDescription>
          Editing prices for {ids.length} selected berth{ids.length === 1 ? '' : 's'}. Rows whose
          value is unchanged are skipped server-side.
        </SheetDescription>
      </SheetHeader>

      {/* Set-all + percent-adjust shortcuts */}
      <div className="grid grid-cols-1 gap-4 border-b p-6 sm:grid-cols-2">
        <div className="space-y-2">
          <Label className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
            Set all to
          </Label>
          <div className="flex gap-2">
            <Input
              inputMode="decimal"
              placeholder="Amount"
              value={setAllPrice}
              onChange={(e) => setSetAllPrice(e.target.value)}
              className="h-8 flex-1"
            />
            <Select value={setAllCurrency} onValueChange={setSetAllCurrency}>
              <SelectTrigger className="h-8 w-28">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value={NO_CURRENCY_CHANGE}>Keep</SelectItem>
                {SUPPORTED_CURRENCIES.map((c) => (
                  <SelectItem key={c.code} value={c.code}>
                    {c.code}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
            <Button type="button" size="sm" variant="secondary" onClick={applySetAll}>
              Apply
            </Button>
          </div>
        </div>

        <div className="space-y-2">
          <Label className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
            Adjust by %
          </Label>
          <div className="flex gap-2">
            <Input
              inputMode="decimal"
              placeholder="e.g. 5 or -10"
              value={adjustPercent}
              onChange={(e) => setAdjustPercent(e.target.value)}
              className="h-8 flex-1"
            />
            <Button type="button" size="sm" variant="secondary" onClick={applyPercent}>
              Apply
            </Button>
          </div>
          <p className="text-xs text-muted-foreground">
            Rounds to nearest whole unit; clamps at 0.
          </p>
        </div>
      </div>

      {/* Per-row diff list */}
      <div className="flex-1 overflow-y-auto p-6">
        {unresolvedCount > 0 ? (
          <div className="mb-3 flex items-start gap-2 rounded border border-amber-200 bg-amber-50 p-2.5 text-xs text-amber-900">
            <AlertCircle className="mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden />
            <span>
              {unresolvedCount} selected berth{unresolvedCount === 1 ? '' : 's'} not visible on the
              current page. Switch pages to load and edit them.
            </span>
          </div>
        ) : null}
        {drafts.length === 0 ? (
          <p className="text-sm text-muted-foreground">No berths to edit.</p>
        ) : (
          <ul className="divide-y">
            {drafts.map((r) => {
              const newNum = r.newPrice.trim() === '' ? null : Number(r.newPrice);
              const currentNum = r.currentPrice === null ? null : Number(r.currentPrice);
              const isDirty = newNum !== currentNum || r.newCurrency !== r.currentCurrency;
              return (
                <li key={r.berthId} className="flex items-center gap-3 py-2.5">
                  <div className="min-w-0 flex-1">
                    <p className="text-sm font-medium">
                      {r.mooringNumber}
                      {r.area ? (
                        <span className="ml-1.5 text-xs text-muted-foreground">· {r.area}</span>
                      ) : null}
                    </p>
                    <p className="text-xs text-muted-foreground">
                      Current:{' '}
                      {r.currentPrice
                        ? formatCurrency(r.currentPrice, r.currentCurrency, {
                            maxFractionDigits: 0,
                          })
                        : '—'}
                    </p>
                  </div>
                  <Input
                    inputMode="decimal"
                    value={r.newPrice}
                    onChange={(e) =>
                      setDrafts((rows) =>
                        rows.map((x) =>
                          x.berthId === r.berthId ? { ...x, newPrice: e.target.value } : x,
                        ),
                      )
                    }
                    placeholder="Price"
                    className={`h-8 w-32 ${isDirty ? 'border-primary' : ''}`}
                  />
                  <Select
                    value={r.newCurrency}
                    onValueChange={(v) =>
                      setDrafts((rows) =>
                        rows.map((x) => (x.berthId === r.berthId ? { ...x, newCurrency: v } : x)),
                      )
                    }
                  >
                    <SelectTrigger className="h-8 w-24">
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      {SUPPORTED_CURRENCIES.map((c) => (
                        <SelectItem key={c.code} value={c.code}>
                          {c.code}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </li>
              );
            })}
          </ul>
        )}
      </div>

      <Separator />
      <SheetFooter className="flex flex-row items-center justify-between gap-2 p-6">
        <div className="text-xs text-muted-foreground">
          {dirtyDrafts.length} of {drafts.length} row{drafts.length === 1 ? '' : 's'} changed
        </div>
        <div className="flex gap-2">
          <Button type="button" variant="ghost" onClick={resetAll} disabled={mutation.isPending}>
            Reset
          </Button>
          <Button type="button" variant="outline" onClick={onClose} disabled={mutation.isPending}>
            Cancel
          </Button>
          <Button
            type="button"
            onClick={() => mutation.mutate()}
            disabled={mutation.isPending || dirtyDrafts.length === 0}
          >
            {mutation.isPending ? (
              <>
                <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
                Applying…
              </>
            ) : (
              `Apply (${dirtyDrafts.length})`
            )}
          </Button>
        </div>
      </SheetFooter>
    </>
  );
}
