"use client";

import Link from "next/link";
import { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Ban, Check, CircleDashed, Store, TriangleAlert, UserRound } from "lucide-react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useCan } from "@/features/auth/use-can";
import { AssignOrderDialog } from "@/features/orders/assign-order-dialog";
import { FailedCollectionPanel, ResolvedCollectionNote } from "@/features/orders/failed-collection";
import { OrderItemLine } from "@/features/orders/order-item-line";
import type { NoteTarget } from "@/features/orders/resolve-note-dialog";
import { ReadyTime } from "@/features/orders/ready-time";
import { CollectionMode } from "@/features/orders/collection-mode";
import { useI18n, useT } from "@/i18n";
import { cn } from "@/lib/cn";
import { friendlyErrorMessage } from "@/lib/errors";
import { formatCurrency, formatDateTime } from "@/lib/format";
import { queryKeys } from "@/lib/query-keys";
import { retryOrderPickup } from "@/services/orders";
import type { Order, OrderCollectionLeg, OrderItem, OrderPickupStop } from "@/types/order";

/** What the page needs to hear about when an item's action is pressed: the delivery's own item to act on. */
export interface ItemTarget {
  uuid: string;
  name: string;
  quantity: number;
}

interface Capabilities {
  canAcceptShortage: boolean;
  canCancelItem: boolean;
  canResolveNote: boolean;
}

interface Handlers {
  onShortage: (item: ItemTarget) => void;
  onCancelItem: (item: ItemTarget) => void;
  onResolveNote: (item: NoteTarget) => void;
  /** Called with nothing to say about why: the order is opened straight to the cancel reason box. */
  onCancelOrder: () => void;
}

/** One supplier of the order: the counter, and the collection (leg) that goes there, when the order is split. */
interface Unit {
  key: string;
  stop: OrderPickupStop;
  leg: OrderCollectionLeg | null;
  /** The collection it belongs to visits several suppliers: one captain for all of them, assigned once for the collection, not per supplier. */
  shared: boolean;
}

/**
 * The order, supplier by supplier.
 *
 * An order with several suppliers is not one errand. Each supplier is a counter with its own items, its own state (waiting, collected, could not
 * collect), and its own captain; the goods meet at one place (the wrapping shop, or the hub) and one captain delivers. So the page is organised
 * around the supplier: what is asked of it, who is collecting from it, what happened there, and the actions that belong to it.
 *
 * **Each thing that can have a captain is assigned from exactly one place.** A supplier with a collection of its own has its Assign button on its
 * card; a collection that visits several suppliers is assigned once, in a row above them; an ordinary order (one captain for every supplier) is
 * assigned from the order's own button in the page header, and nothing here repeats it.
 *
 * Items live here rather than in a separate table: an item is asked of a supplier, and the customer's totals are on the money card.
 */
export function SuppliersSection({
  order,
  capabilities,
  handlers,
}: {
  order: Order;
  capabilities: Capabilities;
  handlers: Handlers;
}) {
  const t = useT();
  const { can } = useCan();
  const queryClient = useQueryClient();
  const [assigning, setAssigning] = useState<string | null>(null);

  const legs = order.collection_legs ?? [];
  const split = order.leg === "delivery" && legs.length > 0;

  // One unit per supplier: the stops of every collection when the order is split, else the order's own stops.
  const units: Unit[] = split
    ? legs.flatMap((leg) =>
        (leg.stops ?? []).map((stop) => ({ key: `${leg.uuid}-${stop.uuid}`, stop, leg, shared: (leg.stops ?? []).length > 1 })),
      )
    : (order.pickups ?? []).map((stop) => ({ key: stop.uuid, stop, leg: null, shared: false }));

  // Lines that name no supplier (an ordinary order whose items were never split by counter) still have to be shown and acted on.
  const shown = new Set(units.flatMap((unit) => (unit.stop.items ?? []).map((item) => item.uuid)));
  const loose: OrderItem[] = split ? [] : (order.items ?? []).filter((item) => !shown.has(item.uuid));

  if (units.length === 0 && loose.length === 0) {
    return null;
  }

  // A failed collection the office dealt with is history: it no longer counts toward how far collection has got.
  const counting = units.filter((unit) => unit.leg?.resolved_at == null);
  const collected = counting.filter((unit) => unit.stop.collected).length;
  const percent = counting.length > 0 ? Math.round((collected / counting.length) * 100) : 0;
  const canAssignHere = can("orders.assign") && order.leg !== "collection";

  // The collections that visit several suppliers with one captain: each is assigned ONCE, from a row of its own above its suppliers.
  const sharedLegs = legs.filter((leg) => (leg.stops ?? []).length > 1 && leg.resolved_at == null);

  return (
    <Card className="mb-6">
      <CardHeader className="pb-3">
        <CardTitle className="flex flex-wrap items-center gap-2">
          <Store className="h-4 w-4 text-muted-foreground" aria-hidden />
          {t("orders.supplier.title")}
          <Badge variant="secondary" className="font-normal">
            {t("orders.supplier.collectedOf", { collected, total: counting.length })}
          </Badge>
        </CardTitle>
        <p className="max-w-2xl text-sm leading-6 text-muted-foreground">
          {split && sharedLegs.length === 0 ? t("orders.supplier.howItWorks") : units.length > 1 ? t("orders.supplier.oneCaptain") : t("orders.supplier.howItWorksSingle")}
        </p>
        {units.length > 0 ? (
          <div className="mt-2 h-1.5 overflow-hidden rounded-full bg-muted" role="progressbar" aria-valuenow={percent} aria-valuemin={0} aria-valuemax={100}>
            <div className="h-full rounded-full bg-primary transition-all" style={{ width: `${percent}%` }} />
          </div>
        ) : null}
      </CardHeader>

      <CardContent className="space-y-4">
        {/* One captain for all the suppliers, or one each: the choice that decides which assign buttons exist. */}
        <CollectionMode order={order} />

        {/* A collection that visits several suppliers has one captain, assigned here once. */}
        {sharedLegs.map((leg) => (
          <div key={leg.uuid} className="flex flex-wrap items-center justify-between gap-2 rounded-lg bg-muted/40 px-3 py-2">
            <div className="flex flex-wrap items-center gap-2 text-sm">
              <UserRound className="h-4 w-4 text-muted-foreground" aria-hidden />
              {leg.captain ? (
                <Link href={`/captains/${leg.captain.uuid}`} className="font-medium text-foreground hover:underline">
                  {leg.captain.name}
                </Link>
              ) : (
                <span className="text-muted-foreground">{t("orders.supplier.noCaptain")}</span>
              )}
              <Badge variant="secondary">{t("orders.supplier.allSuppliers", { count: (leg.stops ?? []).length })}</Badge>
              <Link href={`/orders/${leg.uuid}`} className="text-xs text-muted-foreground underline-offset-2 hover:underline" dir="ltr">
                {leg.order_number}
              </Link>
            </div>
            {canAssignHere && leg.status === "pending" && !leg.captain ? (
              <Button type="button" size="sm" onClick={() => setAssigning(leg.uuid)}>
                {t("orders.supplier.assign")}
              </Button>
            ) : null}
          </div>
        ))}

        {units.map((unit, index) => (
          <SupplierCard
            key={unit.key}
            order={order}
            unit={unit}
            position={index + 1}
            total={units.length}
            capabilities={capabilities}
            handlers={handlers}
            canAssign={canAssignHere}
            deliveryUuid={order.uuid}
            onAssign={() => unit.leg && setAssigning(unit.leg.uuid)}
          />
        ))}

        {loose.length > 0 ? (
          <div className="rounded-lg border border-dashed p-4">
            <p className="mb-2 text-sm font-medium text-foreground">{t("orders.supplier.otherItems")}</p>
            <ItemRows items={loose} order={order} capabilities={capabilities} handlers={handlers} />
          </div>
        ) : null}
      </CardContent>

      <AssignOrderDialog
        uuid={assigning ?? ""}
        open={assigning !== null}
        onOpenChange={(open) => {
          if (!open) {
            setAssigning(null);
            // The captain just given to a supplier shows on this order, which is a different one from the one the dialog worked on.
            void queryClient.invalidateQueries({ queryKey: ["orders"] });
          }
        }}
      />
    </Card>
  );
}

function SupplierCard({
  order,
  unit,
  position,
  total,
  capabilities,
  handlers,
  canAssign,
  deliveryUuid,
  onAssign,
}: {
  order: Order;
  unit: Unit;
  position: number;
  total: number;
  capabilities: Capabilities;
  handlers: Handlers;
  canAssign: boolean;
  deliveryUuid: string;
  onAssign: () => void;
}) {
  const t = useT();
  const { locale } = useI18n();
  const { can } = useCan();
  const queryClient = useQueryClient();
  const { stop, leg } = unit;
  const currency = order.currency;
  const items = stop.items ?? [];

  // Who collects here: the collection's captain when the order is split, else the order's own.
  const captain = leg ? (leg.captain ?? null) : order.driver ? { uuid: order.driver.uuid, name: order.driver.name } : null;
  const worked = leg ?? order;
  const workedOpen = leg ? leg.status === "pending" : order.status === "pending";
  const status = leg ? leg.status : order.status;

  // The retry goes to the order the captain works on, which for a split order is the collection, not the delivery.
  const retryUuid = leg ? leg.uuid : order.uuid;
  const canRetry = can("orders.assign") && status === "accepted";
  const retry = useMutation({
    mutationFn: () => retryOrderPickup(retryUuid, stop.uuid),
    onSuccess: (response) => {
      toast.success(response.Message ?? t("orders.stops.retryDone"));
      void queryClient.invalidateQueries({ queryKey: queryKeys.orders.detail(order.uuid) });
      void queryClient.invalidateQueries({ queryKey: ["orders"] });
    },
    onError: (error) => toast.error(friendlyErrorMessage(error, t)),
  });

  const live = items.filter((item) => item.cancelled !== true);
  // Every line of this supplier was called off: there is nothing left to fetch here, whatever the stop itself says about having been visited.
  const allCancelled = items.length > 0 && live.length === 0;
  const subtotal = live.reduce((sum, item) => sum + (item.unit_price ?? 0) * item.quantity, 0);

  // The one button that gives THIS supplier a captain. Only a supplier with a collection of its own has it: an ordinary order is one captain's
  // errand for all its suppliers and is assigned from the order's own button, and a collection that visits several suppliers is assigned once, above them.
  const resolved = leg?.resolved_at != null;
  const own = leg !== null && !unit.shared && !resolved;
  const showAssign = own && canAssign && !captain && workedOpen && !stop.collected;

  return (
    <div className={cn("rounded-xl border p-4", (stop.failed || allCancelled) && "border-destructive/40 bg-destructive-soft/20", resolved && !allCancelled && "bg-muted/30 opacity-80")}>
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div className="flex min-w-0 items-start gap-3">
          <span
            className={cn(
              "flex h-8 w-8 shrink-0 items-center justify-center rounded-full",
              allCancelled ? "bg-destructive-soft text-destructive" : stop.collected ? "bg-success-soft text-success" : stop.failed ? "bg-destructive-soft text-destructive" : "bg-muted text-muted-foreground",
            )}
          >
            {allCancelled ? <Ban className="h-4 w-4" aria-hidden /> : stop.collected ? <Check className="h-4 w-4" aria-hidden /> : stop.failed ? <TriangleAlert className="h-4 w-4" aria-hidden /> : <CircleDashed className="h-4 w-4" aria-hidden />}
          </span>
          <div className="min-w-0">
            <p className="font-semibold text-foreground">
              {total > 1 ? <span className="me-1 text-muted-foreground">{position}.</span> : null}
              {stop.store_name ?? t("orders.stops.unnamedStore")}
            </p>
            {stop.external_order_number ? (
              <p className="text-xs text-muted-foreground">
                {t("orders.supplier.subOrder")}{" "}
                <span className="font-medium text-foreground" dir="ltr">
                  {stop.external_order_number}
                </span>
              </p>
            ) : null}
            <p className="text-sm text-muted-foreground">{stop.address}</p>
            {leg ? (
              <p className="mt-0.5 text-xs text-muted-foreground">
                {t("orders.supplier.task")}{" "}
                <Link href={`/orders/${leg.uuid}`} className="underline-offset-2 hover:underline" dir="ltr">
                  {leg.order_number}
                </Link>
              </p>
            ) : null}
          </div>
        </div>

        <div className="flex flex-wrap items-center gap-2">
          {allCancelled ? (
            <Badge variant="destructive">{t("orders.items.cancelledBadge")}</Badge>
          ) : stop.collected ? (
            <Badge variant="success">{t("orders.supplier.collected")}</Badge>
          ) : stop.failed ? (
            <Badge variant="destructive">{t("orders.stops.failed")}</Badge>
          ) : (
            <Badge variant="secondary">{t("orders.supplier.waiting")}</Badge>
          )}
        </div>
      </div>

      {/* When this supplier will really have the goods ready, which the office can change after ringing them. */}
      <ReadyTime
        orderUuid={leg ? leg.uuid : order.uuid}
        deliveryUuid={deliveryUuid}
        stopUuid={stop.uuid}
        readyAt={stop.ready_at}
        editable={canAssign && !stop.collected && !resolved && (leg ? ["pending", "assigned", "accepted"].includes(leg.status) : ["pending", "assigned", "accepted"].includes(order.status))}
      />

      {/* The captain said they handed over a different number of pieces at the wrapping shop than they collected. */}
      {leg?.handoff_mismatch && !allCancelled ? (
        <Badge variant="destructive" className="mt-2 gap-1">
          <TriangleAlert className="h-3 w-3" aria-hidden />
          {t("orders.supplier.handoffMismatch", { handed: leg.items_handed_over ?? 0 })}
        </Badge>
      ) : null}

      {/* A collection that failed: the office sends another captain or carries on without this supplier. Once dealt with, a one-line history. */}
      {leg && leg.can_recover && leg.stops?.[0]?.uuid === stop.uuid ? <FailedCollectionPanel leg={leg} deliveryUuid={deliveryUuid} /> : null}
      {leg && resolved && leg.stops?.[0]?.uuid === stop.uuid ? <ResolvedCollectionNote leg={leg} /> : null}

      {/* Only a supplier with its own collection has steps and a captain of its own. */}
      {own ? (
        <>
        {/* Three steps, in the order they happen, so where this supplier stands reads at a glance. */}
        <ol className="mt-3 flex items-center gap-2 text-xs">
          {[
            { label: t("orders.supplier.step.captain"), done: captain !== null },
            { label: t("orders.supplier.step.collected"), done: stop.collected },
            { label: t("orders.supplier.step.atHub"), done: leg?.status === "delivered" },
          ].map((step, i, all) => (
            <li key={step.label} className="flex items-center gap-2">
              <span
                className={cn(
                  "inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-medium",
                  step.done ? "bg-success-soft text-success" : "bg-muted text-muted-foreground",
                )}
              >
                {step.done ? <Check className="h-3 w-3" aria-hidden /> : <span className="h-1.5 w-1.5 rounded-full bg-current opacity-50" />}
                {step.label}
              </span>
              {i < all.length - 1 ? <span className="h-px w-4 bg-border" aria-hidden /> : null}
            </li>
          ))}
        </ol>

        {/* Who is collecting here, and the button that gives this supplier a captain. */}
        <div className="mt-3 flex flex-wrap items-center justify-between gap-2 rounded-lg bg-muted/40 px-3 py-2">
          <div className="flex items-center gap-2 text-sm">
            <UserRound className="h-4 w-4 text-muted-foreground" aria-hidden />
            {captain ? (
              <Link href={`/captains/${captain.uuid}`} className="font-medium text-foreground hover:underline">
                {captain.name}
              </Link>
            ) : (
              <span className="text-muted-foreground">{t("orders.supplier.noCaptain")}</span>
            )}
          </div>
          {showAssign ? (
            <Button type="button" size="sm" onClick={onAssign}>
              {t("orders.supplier.assign")}
            </Button>
          ) : null}
        </div>
        </>
      ) : null}

      {/*
        Why the captain could not collect here, said as an answer to that question.

        It used to sit in the row below as a bare sentence between two buttons — the captain's
        words with nothing saying they were the captain's words, or what they were explaining.
        Somebody scanning a red card could read "the shop was closed" and not know whether that
        was the reason for the failure, a note about the address, or an instruction to them.
        Labelled, quoted and stamped with the hour, it answers rather than just appears.
      */}
      {stop.failed ? (
        <div className="mt-3 rounded-lg border border-destructive/30 bg-destructive-soft/30 p-3">
          <p className="text-xs font-medium uppercase tracking-wide text-destructive">
            {t("orders.stops.failureReason")}
          </p>

          <p className="mt-1 text-sm text-foreground">
            {stop.failure_reason ?? t("orders.stops.failureNoReason")}
          </p>

          {stop.failed_at ? (
            <p className="mt-1 text-xs text-muted-foreground">
              {t("orders.stops.failedAt", { at: formatDateTime(stop.failed_at, locale) })}
            </p>
          ) : null}
        </div>
      ) : null}

      {stop.failed ? (
        <div className="mt-3 flex flex-wrap items-center gap-2 text-sm">
          {canRetry ? (
            <Button type="button" size="sm" variant="outline" loading={retry.isPending} disabled={retry.isPending} onClick={() => retry.mutate()}>
              {retry.isPending ? t("common.saving") : t("orders.stops.retry")}
            </Button>
          ) : null}
          {/*
            A failed counter has exactly one other sensible move: calling the order off. On an
            ordinary order (no `leg`) that is a real second choice, not a formality — the supplier
            that just failed may be the only one there is, and "send the captain back" is not an
            answer when the shop was simply closed for the day. A split order keeps its own two
            choices here (send another captain, or carry on without this supplier) in the panel
            above, because "cancel the whole delivery" over one of several suppliers is a bigger
            step than this button should take on its own.
          */}
          {!leg && can("orders.cancel") ? (
            <Button type="button" size="sm" variant="ghost" onClick={handlers.onCancelOrder}>
              {t("orders.cancel.action")}
            </Button>
          ) : null}
        </div>
      ) : null}
      {(stop.retry_count ?? 0) > 0 ? (
        <p className="mt-1 text-xs text-muted-foreground">{t("orders.stops.retried", { count: stop.retry_count ?? 0 })}</p>
      ) : null}
      {stop.collected_at && !allCancelled ? (
        <p className="mt-1 text-xs text-muted-foreground">{t("orders.stops.collectedAt", { at: formatDateTime(stop.collected_at, locale) })}</p>
      ) : null}
      {stop.items_mismatch && !allCancelled ? (
        <Badge variant="warning" className="mt-2 gap-1">
          <TriangleAlert className="h-3 w-3" aria-hidden />
          {t("orders.stops.mismatch")}
        </Badge>
      ) : null}

      {/* The money at this counter: expected beside paid, never one in place of the other. */}
      {!allCancelled && (stop.collected || stop.amount_paid !== null) ? (
        <dl className="mt-3 grid grid-cols-2 gap-x-4 gap-y-2 text-sm sm:grid-cols-4">
          <Figure label={t("orders.stops.expected")} value={formatCurrency(stop.expected_goods_cost, currency, locale)} />
          <Figure label={t("orders.stops.paid")} value={formatCurrency(stop.amount_paid, currency, locale)} />
          <Figure
            label={t("orders.stops.variance")}
            value={formatCurrency(stop.amount_variance ?? null, currency, locale)}
            tone={stop.amount_variance != null && stop.amount_variance !== 0 ? "warning" : undefined}
          />
          <Figure label={t("orders.stops.itemsCollected")} value={stop.items_collected === null ? t("orders.stops.oneTap") : String(stop.items_collected)} />
        </dl>
      ) : null}

      {stop.collection_note ? (
        <p className="mt-3 rounded-md bg-muted/40 p-2 text-sm">
          <span className="text-muted-foreground">{t("orders.items.captainNote")}: </span>
          {stop.collection_note}
        </p>
      ) : null}

      {items.length > 0 && !resolved ? (
        <div className="mt-3 border-t pt-3">
          <ItemRows items={items} order={order} capabilities={capabilities} handlers={handlers} worked={worked.uuid} />
          <div className="mt-2 flex justify-end text-sm">
            <span className="text-muted-foreground">{t("orders.supplier.subtotal")}:&nbsp;</span>
            {/* What was really paid at this supplier once the captain has said so; the lines' price until then. */}
            <span className="numeric font-medium text-foreground">{formatCurrency(stop.amount_paid ?? subtotal, currency, locale)}</span>
            {stop.amount_paid !== null && Number(stop.amount_paid) !== subtotal ? (
              <span className="numeric ms-2 text-muted-foreground">
                ({t("orders.supplier.orderValue")}: {formatCurrency(subtotal, currency, locale)})
              </span>
            ) : null}
          </div>
        </div>
      ) : null}
    </div>
  );
}

/**
 * The items asked of one supplier, with the actions that apply to each.
 *
 * On a split order a line here is the collection's copy, and the customer's order holds its own: every action (accept a shortage, call
 * off) goes to the delivery's line, which `counterpart_uuid` names.
 */
function ItemRows({
  items,
  order,
  capabilities,
  handlers,
  worked,
}: {
  items: OrderItem[];
  order: Order;
  capabilities: Capabilities;
  handlers: Handlers;
  /** The order these lines sit on: the collection leg when the order is split, else the order itself. */
  worked?: string;
}) {
  const t = useT();
  const { locale } = useI18n();

  return (
    <ul className="space-y-3">
      {items.map((item) => {
        const target: ItemTarget = { uuid: item.counterpart_uuid ?? item.uuid, name: item.name, quantity: item.quantity };
        const struck = item.cancelled === true;
        /*
         * A note is answered where it was written, which is not where a shortage is settled.
         *
         * The two actions above act on the **delivery's** copy of a wrapped line (`counterpart_uuid`), because that
         * is the copy the customer pays for. The captain's note is on the copy they carried to the counter — this
         * one, on this order. Sending the answer to the counterpart would answer a line nobody flagged.
         */
        const note: NoteTarget = {
          orderUuid: worked ?? order.uuid,
          uuid: item.uuid,
          name: item.name,
          captainNote: item.collection_note ?? null,
          reasonLabel: item.collection_reason?.label ?? null,
          cancelled: struck,
        };

        return (
          <li key={item.uuid} className="flex flex-wrap items-start justify-between gap-3">
            <div className="min-w-0 flex-1">
              <OrderItemLine item={item} currency={order.currency} />
            </div>
            <div className="flex shrink-0 flex-col items-end gap-1">
              <span className={cn("numeric text-sm", struck ? "text-muted-foreground line-through" : "text-foreground")}>
                {item.quantity} × {formatCurrency(item.unit_price, order.currency, locale)}
              </span>
              {/* One row for every action this line offers. Answering a note is offered on a cancelled line too —
                  a product the supplier had none of is the loudest case of all for ringing the customer — while
                  the two that change the basket are not, which is why the struck check is per button. */}
              <span className="flex flex-wrap justify-end gap-1">
                {capabilities.canResolveNote && item.note_awaits_answer ? (
                  <Button type="button" variant="outline" size="sm" onClick={() => handlers.onResolveNote(note)}>
                    {t("orders.items.note.action")}
                  </Button>
                ) : null}
                {!struck && capabilities.canAcceptShortage && (item.missing_quantity ?? 0) > 0 ? (
                  <Button type="button" variant="outline" size="sm" onClick={() => handlers.onShortage(target)}>
                    {t("orders.items.shortage.action")}
                  </Button>
                ) : null}
                {!struck && capabilities.canCancelItem ? (
                  <Button type="button" variant="ghost" size="sm" onClick={() => handlers.onCancelItem(target)}>
                    {t("orders.items.cancel.action")}
                  </Button>
                ) : null}
              </span>
            </div>
          </li>
        );
      })}
    </ul>
  );
}

function Figure({ label, value, tone }: { label: string; value: string; tone?: "warning" }) {
  return (
    <div>
      <dt className="text-xs text-muted-foreground">{label}</dt>
      <dd className={tone === "warning" ? "numeric font-medium text-warning" : "numeric font-medium text-foreground"}>{value}</dd>
    </div>
  );
}
