"use client";

import { useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { BadgePercent, Building2, Plus, Trash2 } from "lucide-react";
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
import { EmptyState, ErrorState, TableSkeleton } from "@/components/shared/states";
import { StatusBadge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { useCan } from "@/features/auth/use-can";
import { useI18n, useT } from "@/i18n";
import { friendlyErrorMessage } from "@/lib/errors";
import { formatCurrency, formatShare } from "@/lib/format";
import { queryKeys } from "@/lib/query-keys";
import { useFieldErrors } from "@/lib/use-field-errors";
import {
  createDeliveryFeeTier,
  deleteDeliveryFeeTier,
  getDeliveryFeeTiers,
  groupLadders,
} from "@/services/cities";
import { getStoreClients } from "@/services/store-clients";
import type { DeliveryFeeTier } from "@/types/city";

/**
 * How much of a delivery a store pays for, by how much merchandise is in the order.
 *
 * **A band is a share, not a price**, and the screen says so in every row: the price belongs to the
 * area the parcel goes to — the Areas tab — and it is the same figure the captain is paid for going
 * there. A band at 0% is free delivery, which the company absorbs while the captain is still paid in
 * full.
 *
 * **This prices our invoice, not the customer's receipt** — said out loud in the banner, because it is
 * the one thing somebody could reasonably assume the opposite of. What the customer hands the captain
 * is the shop's own figure and is never touched here.
 *
 * Ladders are drawn per store with the company's first. A store with bands of its own is priced by
 * those **alone**, so its ladder is labelled as replacing the default rather than extending it — the
 * distinction decides every order that shop sends.
 */
export function FeeLadders({ currency }: { currency: string }) {
  const t = useT();
  const { locale } = useI18n();
  const { can } = useCan();
  const canManage = can("delivery_fees.manage");

  const queryClient = useQueryClient();

  const { data, isLoading, isError, error, refetch } = useQuery({
    queryKey: queryKeys.cities.feeTiers,
    queryFn: async () => (await getDeliveryFeeTiers()).Model ?? [],
  });

  const ladders = groupLadders(data ?? []);

  const [adding, setAdding] = useState(false);
  const [removing, setRemoving] = useState<DeliveryFeeTier | null>(null);

  const remove = useMutation({
    mutationFn: (tier: DeliveryFeeTier) => deleteDeliveryFeeTier(tier.uuid),
    onSuccess: (response) => {
      toast.success(response.Message ?? t("fees.removed"));
      setRemoving(null);
      void queryClient.invalidateQueries({ queryKey: queryKeys.cities.all });
    },
    onError: (err) => toast.error(friendlyErrorMessage(err, t)),
  });

  /** "300,000 and above", or "0 – 100,000". The open-ended band is where free delivery lives. */
  const range = (tier: DeliveryFeeTier) =>
    tier.max_goods_value === null
      ? t("fees.andAbove", { from: formatCurrency(tier.min_goods_value, currency, locale) })
      : t("fees.between", {
          from: formatCurrency(tier.min_goods_value, currency, locale),
          to: formatCurrency(tier.max_goods_value, currency, locale),
        });

  return (
    <div className="space-y-4">
      {/*
        Said before the table, not after it: an operator reading "free" needs to know whose money that
        is, and assuming the customer's would be the expensive mistake.
      */}
      <div className="flex items-start gap-2 rounded-lg border bg-muted/40 p-3 text-xs text-muted-foreground">
        <BadgePercent className="mt-0.5 h-4 w-4 shrink-0 text-primary" aria-hidden />
        <span>{t("fees.whoseMoney")}</span>
      </div>

      <Card>
        <div className="flex flex-wrap items-center justify-between gap-3 border-b p-4">
          <p className="text-sm text-muted-foreground">{t("fees.hint")}</p>
          {canManage ? (
            <Button size="sm" onClick={() => setAdding(true)}>
              <Plus className="me-1.5 h-4 w-4" aria-hidden />
              {t("fees.addBand")}
            </Button>
          ) : null}
        </div>

        {isLoading ? (
          <TableSkeleton rows={5} cols={4} />
        ) : isError ? (
          <ErrorState message={friendlyErrorMessage(error, t)} onRetry={() => refetch()} />
        ) : ladders.length === 0 ? (
          <EmptyState
            title={t("fees.empty")}
            description={t("fees.emptyHint")}
            icon={BadgePercent}
          />
        ) : (
          <div className="divide-y">
            {ladders.map((ladder) => (
              <div key={ladder.key} className="p-4">
                <p className="mb-2 flex items-center gap-2 text-sm font-medium">
                  <Building2 className="h-4 w-4 text-muted-foreground" aria-hidden />
                  {ladder.storeName ?? t("fees.companyLadder")}
                  {ladder.storeName ? (
                    // The sentence that decides every order this shop sends.
                    <span className="text-xs font-normal text-muted-foreground">
                      {t("fees.replacesDefault")}
                    </span>
                  ) : null}
                </p>

                <div className="overflow-x-auto">
                  <Table>
                    <TableHeader>
                      <TableRow>
                        <TableHead>{t("fees.orderValue")}</TableHead>
                        <TableHead className="text-end">{t("fees.shopPays")}</TableHead>
                        <TableHead>{t("common.status")}</TableHead>
                        <TableHead aria-label={t("common.actions")} />
                      </TableRow>
                    </TableHeader>
                    <TableBody>
                      {ladder.tiers.map((tier) => (
                        <TableRow key={tier.uuid} className={tier.is_active ? undefined : "opacity-60"}>
                          <TableCell className="text-sm">{range(tier)}</TableCell>
                          <TableCell className="numeric text-end font-medium">
                            {/*
                              A share, drawn as one. An amount here would become a lie the moment two
                              districts charge differently — which is the point of the design.
                            */}
                            {tier.is_free ? (
                              <StatusBadge variant="success">{t("fees.free")}</StatusBadge>
                            ) : tier.charge_share >= 1 ? (
                              <span className="text-muted-foreground">{t("fees.fullFee")}</span>
                            ) : (
                              t("fees.shareOfAreaFee", {
                                share: formatShare(tier.charge_share, locale),
                              })
                            )}
                          </TableCell>
                          <TableCell>
                            {tier.is_active ? (
                              <StatusBadge variant="info">{t("fees.active")}</StatusBadge>
                            ) : (
                              <StatusBadge variant="secondary">{t("fees.inactive")}</StatusBadge>
                            )}
                          </TableCell>
                          <TableCell>
                            <div className="flex items-center justify-end">
                              {canManage ? (
                                <Button
                                  size="sm"
                                  variant="ghost"
                                  aria-label={t("fees.remove")}
                                  onClick={() => setRemoving(tier)}
                                >
                                  <Trash2 className="h-4 w-4 text-destructive" aria-hidden />
                                </Button>
                              ) : null}
                            </div>
                          </TableCell>
                        </TableRow>
                      ))}
                    </TableBody>
                  </Table>
                </div>
              </div>
            ))}
          </div>
        )}
      </Card>

      {adding ? (
        <Dialog
          open
          onOpenChange={(open) => {
            if (!open) setAdding(false);
          }}
        >
          <DialogContent>
            <BandForm currency={currency} onDone={() => setAdding(false)} />
          </DialogContent>
        </Dialog>
      ) : null}

      {removing ? (
        <ConfirmDialog
          open
          onOpenChange={(open) => {
            if (!open) setRemoving(null);
          }}
          title={t("fees.removeTitle")}
          description={t("fees.removeBody", { range: range(removing) })}
          confirmLabel={t("fees.remove")}
          destructive
          loading={remove.isPending}
          onConfirm={() => remove.mutate(removing)}
        />
      ) : null}
    </div>
  );
}

/**
 * Add a band.
 *
 * The share is typed as a percentage and sent as a fraction: "50" is easier to mean than "0.5", and
 * there is no amount of money to type here at all — the price is the area's.
 *
 * Leaving the ceiling empty is how the top band is written, and the form says so — "and above" is
 * where free delivery lives, and a form that demanded a ceiling would make the common case the awkward
 * one. Overlaps are the server's to refuse: it knows every band, and its message names the one that
 * clashed.
 */
function BandForm({ currency, onDone }: { currency: string; onDone: () => void }) {
  const t = useT();
  const queryClient = useQueryClient();

  const [store, setStore] = useState("company");
  const [min, setMin] = useState("");
  const [max, setMax] = useState("");
  // Typed as a percentage, sent as a fraction: "50" is easier to mean than "0.5", and the API wants the
  // fraction. One conversion, in one place, at the moment of sending.
  const [percent, setPercent] = useState("100");

  // The shops a band could be negotiated with. One page is enough for a select, and the key is the
  // store list's own so this shares whatever the stores screen has already fetched.
  const storeFilters = { page: 1, rows: 25 as const };
  const stores = useQuery({
    queryKey: queryKeys.storeClients.list(storeFilters),
    queryFn: () => getStoreClients(storeFilters),
  });

  const fields = useFieldErrors({
    only: ["store_uuid", "min_goods_value", "max_goods_value", "charge_share"],
  });

  const num = (value: string) => (value.trim() === "" ? Number.NaN : Number(value));

  const invalid =
    Number.isNaN(num(min)) ||
    num(min) < 0 ||
    Number.isNaN(num(percent)) ||
    num(percent) < 0 ||
    // Above the whole fee would bill a shop more than the same trip pays the captain. The server
    // refuses it too; stopped here so nobody has to submit the form to hear that.
    num(percent) > 100 ||
    (max.trim() !== "" && (Number.isNaN(num(max)) || num(max) <= num(min)));

  const save = useMutation({
    mutationFn: () =>
      createDeliveryFeeTier({
        ...(store === "company" ? {} : { store_uuid: store }),
        min_goods_value: num(min),
        ...(max.trim() === "" ? {} : { max_goods_value: num(max) }),
        charge_share: num(percent) / 100,
      }),
    onMutate: fields.reset,
    onSuccess: (response) => {
      toast.success(response.Message ?? t("fees.added"));
      onDone();
      void queryClient.invalidateQueries({ queryKey: queryKeys.cities.all });
    },
    // A 409 naming the band it overlaps is the server's own sentence and the most useful thing the
    // operator can be told, so it is shown as it arrives.
    onError: (err) => {
      if (!fields.capture(err)) toast.error(friendlyErrorMessage(err, t));
    },
  });

  return (
    <>
      <DialogHeader>
        <DialogTitle>{t("fees.addBand")}</DialogTitle>
        <DialogDescription>{t("fees.addBandBody")}</DialogDescription>
      </DialogHeader>

      <form
        className="space-y-4"
        onSubmit={(event) => {
          event.preventDefault();
          if (invalid || save.isPending) return;
          save.mutate();
        }}
      >
        <div className="space-y-1.5">
          <Label htmlFor="band-store">{t("fees.ladder")}</Label>
          <Select value={store} onValueChange={setStore}>
            <SelectTrigger id="band-store">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="company">{t("fees.companyLadder")}</SelectItem>
              {(stores.data?.Model ?? []).map((option) => (
                <SelectItem key={option.uuid} value={option.uuid}>
                  {option.name}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
          <p className="text-xs text-muted-foreground">{t("fees.ladderHint")}</p>
        </div>

        <div className="grid grid-cols-2 gap-3">
          <div className="space-y-1.5">
            <Label htmlFor="band-min">
              {t("fees.from")} ({currency})
            </Label>
            <Input
              id="band-min"
              type="number"
              min="0"
              step="1000"
              value={min}
              onChange={(event) => setMin(event.target.value)}
              aria-invalid={fields.get("min_goods_value") !== undefined}
              autoFocus
            />
            {fields.get("min_goods_value") ? (
              <p className="text-xs text-destructive">{fields.get("min_goods_value")}</p>
            ) : null}
          </div>

          <div className="space-y-1.5">
            <Label htmlFor="band-max">
              {t("fees.to")} ({currency})
            </Label>
            <Input
              id="band-max"
              type="number"
              min="0"
              step="1000"
              value={max}
              onChange={(event) => setMax(event.target.value)}
              placeholder={t("fees.noCeiling")}
              aria-invalid={fields.get("max_goods_value") !== undefined}
            />
            {fields.get("max_goods_value") ? (
              <p className="text-xs text-destructive">{fields.get("max_goods_value")}</p>
            ) : (
              <p className="text-xs text-muted-foreground">{t("fees.toHint")}</p>
            )}
          </div>
        </div>

        <div className="space-y-1.5">
          <Label htmlFor="band-share">{t("fees.shopPaysPercent")}</Label>
          <Input
            id="band-share"
            type="number"
            min="0"
            max="100"
            step="5"
            value={percent}
            onChange={(event) => setPercent(event.target.value)}
            aria-invalid={fields.get("charge_share") !== undefined}
          />
          {fields.get("charge_share") ? (
            <p className="text-xs text-destructive">{fields.get("charge_share")}</p>
          ) : num(percent) === 0 ? (
            <p className="flex items-center gap-1.5 text-xs text-success">
              <BadgePercent className="h-3.5 w-3.5" aria-hidden />
              {t("fees.zeroIsFree")}
            </p>
          ) : (
            <p className="text-xs text-muted-foreground">{t("fees.shareHint")}</p>
          )}
        </div>

        {fields.get("store_uuid") ? (
          <p className="text-xs text-destructive">{fields.get("store_uuid")}</p>
        ) : null}

        <DialogFooter>
          <Button type="button" variant="outline" onClick={onDone}>
            {t("common.cancel")}
          </Button>
          <Button type="submit" disabled={invalid || save.isPending}>
            {save.isPending ? t("common.saving") : t("common.save")}
          </Button>
        </DialogFooter>
      </form>
    </>
  );
}
