import { useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
  Alert,
  Badge,
  Box,
  Button,
  FileButton,
  Group,
  Loader,
  NumberInput,
  Paper,
  Select,
  Stack,
  Text,
  TextInput,
  Tooltip,
} from "@mantine/core";
import {
  CheckCircle2,
  Download,
  Eye,
  FileText,
  Lock,
  Receipt,
  Send,
  Upload,
  XCircle,
} from "lucide-react";
import toast from "react-hot-toast";
import type { Freight } from "@edr/types";
import { isViewable } from "@edr/ui-common";

import { bookingsService } from "@/services/bookings.service";
import {
  downloadBookingFile,
  fetchViewableFile,
} from "@/services/files.service";
import { formatDateTime } from "@/lib/format";
import { extractErrorMessage } from "@/utils/errorExtractor";

const CURRENCIES = ["ETB", "USD"];

const STATUS_META: Record<
  Freight.ClearanceChargeStatus,
  { label: string; color: string }
> = {
  DOC_UPLOADED: { label: "Awaiting billing", color: "yellow" },
  BILLED: { label: "Draft — not sent", color: "blue" },
  SENT: { label: "Awaiting customer approval", color: "orange" },
  REJECTED: { label: "Rejected by customer", color: "red" },
  ACCEPTED: { label: "Accepted — invoice unpaid", color: "teal" },
  PAID: { label: "Paid", color: "edr-green" },
};

/** Once the customer accepts, the invoice exists and GL can no longer edit. */
const isLocked = (s: Freight.ClearanceChargeStatus) =>
  s === "ACCEPTED" || s === "PAID";

type BillInput = { amount: number; currency: string; description: string };

export interface ClearanceChargesTabProps {
  bookingId: string;
  /** DJ uploads the port document; ET bills, sends and creates miscellaneous. */
  roleMode: "ET" | "DJ";
  onViewFile: (file: { name: string; url: string }) => void;
}

/**
 * Post-finalization charges billed to the customer: port charges (document
 * from GL Djibouti, priced by GL Ethiopia) and any number of miscellaneous
 * charges. GL prices + describes a charge and sends it; the customer accepts
 * (invoice issued, charge locked) or rejects with a note (GL revises and
 * re-sends). ETB settles through the portal gateway (CBE), other currencies
 * through Finance's manual settlement.
 */
export function ClearanceChargesTab({
  bookingId,
  roleMode,
  onViewFile,
}: ClearanceChargesTabProps) {
  const qc = useQueryClient();
  // Bumped after each create so the form remounts empty for the next charge.
  const [miscCreated, setMiscCreated] = useState(0);
  const { data: charges, isLoading } = useQuery({
    queryKey: ["clearance-charges", bookingId],
    queryFn: () => bookingsService.getClearanceCharges(bookingId),
  });

  const refresh = (next: Freight.ClearanceCharge[]) =>
    qc.setQueryData(["clearance-charges", bookingId], next);
  const onError = (e: unknown) =>
    toast.error(extractErrorMessage(e, "Could not update the charge"));

  const uploadPort = useMutation({
    mutationFn: (file: File) =>
      bookingsService.uploadPortChargeDocument(bookingId, file),
    onSuccess: (next) => {
      toast.success("Port-charges document uploaded");
      refresh(next);
    },
    onError,
  });
  const bill = useMutation({
    // Body must be exactly the DTO — the API rejects unknown keys like chargeId.
    mutationFn: ({ chargeId, ...payload }: BillInput & { chargeId: string }) =>
      bookingsService.billClearanceCharge(bookingId, chargeId, payload),
    onSuccess: (next) => {
      toast.success("Charge amount saved");
      refresh(next);
    },
    onError,
  });
  const send = useMutation({
    mutationFn: (chargeId: string) =>
      bookingsService.sendClearanceCharge(bookingId, chargeId),
    onSuccess: (next) => {
      toast.success("Sent to the customer for approval");
      refresh(next);
    },
    onError,
  });
  const createMisc = useMutation({
    mutationFn: ({ file, ...payload }: BillInput & { file: File }) =>
      bookingsService.createMiscellaneousCharge(bookingId, file, payload),
    onSuccess: (next) => {
      toast.success("Miscellaneous charge created");
      // Remount the form so the next charge starts from an empty one.
      setMiscCreated((n) => n + 1);
      refresh(next);
    },
    onError,
  });

  if (isLoading) {
    return (
      <Group justify="center" py="xl" gap={10}>
        <Loader size="sm" color="edr-green" />
        <Text c="dimmed">Loading charges…</Text>
      </Group>
    );
  }

  const port = (charges ?? []).find((c) => c.type === "PORT_CHARGES") ?? null;
  const miscCharges = (charges ?? []).filter(
    (c) => c.type === "MISCELLANEOUS",
  );
  const busy =
    uploadPort.isPending || bill.isPending || send.isPending || createMisc.isPending;

  const totals = new Map<string, number>();
  for (const c of charges ?? []) {
    if (c.amount != null && c.currency)
      totals.set(c.currency, (totals.get(c.currency) ?? 0) + c.amount);
  }

  return (
    <Stack gap="md" maw={860}>
      <ChargeCard
        title="Port charges"
        charge={port}
        roleMode={roleMode}
        busy={busy}
        emptyHint={
          roleMode === "DJ"
            ? "Upload the port-charges document to start this charge."
            : "Waiting for GL Djibouti to upload the port-charges document."
        }
        onViewFile={onViewFile}
        onBill={(input) => port && bill.mutate({ chargeId: port.id, ...input })}
        onSend={() => port && send.mutate(port.id)}
        djUpload={
          roleMode === "DJ" && (!port || port.status === "DOC_UPLOADED") ? (
            <FileButton
              onChange={(f) => f && uploadPort.mutate(f)}
              accept="application/pdf,image/*"
              disabled={busy}
            >
              {(props) => (
                <Button
                  {...props}
                  size="compact-sm"
                  color="edr-green"
                  radius="md"
                  leftSection={<Upload size={14} />}
                  loading={uploadPort.isPending}
                >
                  {port ? "Replace document" : "Upload document"}
                </Button>
              )}
            </FileButton>
          ) : null
        }
      />

      {/* Any number of miscellaneous charges, in any order relative to the
          port charge — each is billed and paid on its own. */}
      {miscCharges.map((c, i) => (
        <ChargeCard
          key={c.id}
          title={
            miscCharges.length > 1
              ? `Miscellaneous charge ${i + 1}`
              : "Miscellaneous charge"
          }
          charge={c}
          roleMode={roleMode}
          busy={busy}
          emptyHint=""
          onViewFile={onViewFile}
          onBill={(input) => bill.mutate({ chargeId: c.id, ...input })}
          onSend={() => send.mutate(c.id)}
        />
      ))}

      {roleMode === "ET" && (
        <Paper withBorder radius="md" p="md">
          <Text fz="14px" fw={700} c="edr-text" mb={4}>
            {miscCharges.length > 0
              ? "Add another miscellaneous charge"
              : "Add a miscellaneous charge"}
          </Text>
          <Text fz="12px" c="dimmed" mb="sm">
            Upload the supporting document, set the amount and say what it is
            for. The customer sees it once you send it for approval.
          </Text>
          <MiscCreateForm
            key={miscCreated}
            busy={createMisc.isPending}
            onCreate={(file, input) => createMisc.mutate({ file, ...input })}
          />
        </Paper>
      )}

      {totals.size > 0 && (
        <Paper withBorder radius="md" p="md">
          <Group justify="space-between">
            <Text fz="13px" fw={700} c="edr-text">
              Total billed
            </Text>
            <Group gap="md">
              {[...totals.entries()].map(([currency, amount]) => (
                <Text key={currency} fz="14px" fw={800} c="edr-text">
                  {amount.toLocaleString(undefined, {
                    minimumFractionDigits: 2,
                  })}{" "}
                  {currency}
                </Text>
              ))}
            </Group>
          </Group>
        </Paper>
      )}
    </Stack>
  );
}

function ChargeCard({
  title,
  charge,
  roleMode,
  busy,
  emptyHint,
  onViewFile,
  onBill,
  onSend,
  djUpload,
  etCreate,
}: {
  title: string;
  charge: Freight.ClearanceCharge | null;
  roleMode: "ET" | "DJ";
  busy: boolean;
  emptyHint: string;
  onViewFile: (file: { name: string; url: string }) => void;
  onBill: (input: BillInput) => void;
  onSend: () => void;
  djUpload?: React.ReactNode;
  etCreate?: React.ReactNode;
}) {
  const [editing, setEditing] = useState(false);
  const [amount, setAmount] = useState<number | string>(charge?.amount ?? "");
  const [currency, setCurrency] = useState<string>(charge?.currency ?? "ETB");
  const [description, setDescription] = useState(charge?.description ?? "");

  const status = charge?.status ?? null;
  const meta = status ? STATUS_META[status] : null;
  const locked = status != null && isLocked(status);
  const needsDescription = charge?.type === "MISCELLANEOUS";
  // ET enters/revises the price until the customer accepts it.
  const showBillForm =
    roleMode === "ET" &&
    charge != null &&
    !locked &&
    (charge.status === "DOC_UPLOADED" || editing);

  return (
    <Paper withBorder radius="md" p="md">
      <Group justify="space-between" wrap="nowrap" align="flex-start">
        <Group gap={10} wrap="nowrap">
          <Receipt size={18} color="var(--mantine-color-edr-green-6)" />
          <Box>
            <Text fz="14px" fw={700} c="edr-text">
              {title}
            </Text>
            {charge?.uploadedAt && (
              <Text fz="11.5px" c="dimmed">
                Document uploaded
                {charge.uploadedByName ? ` by ${charge.uploadedByName}` : ""} ·{" "}
                {formatDateTime(charge.uploadedAt)}
              </Text>
            )}
            {charge?.billedAt && (
              <Text fz="11.5px" c="dimmed">
                Billed{charge.billedByName ? ` by ${charge.billedByName}` : ""} ·{" "}
                {formatDateTime(charge.billedAt)}
              </Text>
            )}
            {charge?.status === "ACCEPTED" && charge.customerDecidedAt && (
              <Text fz="11.5px" c="teal.8" fw={600}>
                Accepted by the customer · {formatDateTime(charge.customerDecidedAt)}
                {charge.invoiceNumber ? ` (invoice ${charge.invoiceNumber})` : ""}
              </Text>
            )}
            {charge?.paidAt && (
              <Text fz="11.5px" c="edr-green.8" fw={600}>
                Paid · {formatDateTime(charge.paidAt)}
                {charge.invoiceNumber ? ` (invoice ${charge.invoiceNumber})` : ""}
              </Text>
            )}
          </Box>
        </Group>
        <Group gap={8} wrap="nowrap">
          {charge?.amount != null && charge.currency && (
            <Text fz="14px" fw={800} c="edr-text">
              {charge.amount.toLocaleString(undefined, {
                minimumFractionDigits: 2,
              })}{" "}
              {charge.currency}
            </Text>
          )}
          {meta && (
            <Badge variant="light" color={meta.color} radius="sm">
              {meta.label}
            </Badge>
          )}
        </Group>
      </Group>

      {charge?.file && (
        <Group gap={8} mt="sm" wrap="nowrap">
          <FileText size={15} color="var(--mantine-color-edr-green-6)" />
          <Text fz="12.5px" c="edr-text" truncate style={{ minWidth: 0 }}>
            {charge.file.name}
          </Text>
          {isViewable({ name: charge.file.name, url: "" }) && (
            <Tooltip label="View">
              <Box
                component="button"
                type="button"
                onClick={() =>
                  void fetchViewableFile(
                    charge.file!.id,
                    charge.file!.name,
                  ).then(onViewFile)
                }
                c="edr-green"
                style={{
                  display: "flex",
                  background: "transparent",
                  border: "none",
                  cursor: "pointer",
                }}
              >
                <Eye size={15} />
              </Box>
            </Tooltip>
          )}
          <Tooltip label="Download">
            <Box
              component="button"
              type="button"
              onClick={() =>
                void downloadBookingFile(charge.file!.id, charge.file!.name)
              }
              c="edr-green"
              style={{
                display: "flex",
                background: "transparent",
                border: "none",
                cursor: "pointer",
              }}
            >
              <Download size={15} />
            </Box>
          </Tooltip>
        </Group>
      )}

      {charge?.description && !showBillForm && (
        <Text fz="12.5px" c="edr-text" mt="xs">
          {charge.description}
        </Text>
      )}

      {charge?.status === "REJECTED" && charge.customerNote && (
        <Alert
          color="red"
          variant="light"
          radius="md"
          p="xs"
          mt="sm"
          icon={<XCircle size={16} />}
          title="Rejected by the customer"
        >
          <Text fz="12.5px">{charge.customerNote}</Text>
          {charge.customerDecidedAt && (
            <Text fz="11px" c="dimmed" mt={4}>
              {formatDateTime(charge.customerDecidedAt)} — fix the price or
              description and send it again.
            </Text>
          )}
        </Alert>
      )}

      {!charge && (
        <Text fz="12.5px" c="dimmed" mt="xs">
          {emptyHint}
        </Text>
      )}
      {djUpload && <Box mt="sm">{djUpload}</Box>}
      {etCreate && <Box mt="sm">{etCreate}</Box>}

      {showBillForm && (
        <Group mt="sm" gap={8} align="flex-end" wrap="wrap">
          <TextInput
            label={needsDescription ? "What is this charge for?" : "Description (optional)"}
            size="xs"
            radius="md"
            value={description}
            onChange={(e) => setDescription(e.currentTarget.value)}
            maxLength={1000}
            w={320}
          />
          <NumberInput
            label="Amount"
            size="xs"
            radius="md"
            min={0.01}
            decimalScale={2}
            value={amount}
            onChange={setAmount}
            w={160}
          />
          <Select
            label="Currency"
            size="xs"
            radius="md"
            data={CURRENCIES}
            value={currency}
            onChange={(v) => v && setCurrency(v)}
            w={100}
          />
          <Button
            size="compact-sm"
            color="edr-green"
            radius="md"
            disabled={
              busy ||
              !(Number(amount) > 0) ||
              (needsDescription && !description.trim())
            }
            onClick={() => {
              onBill({
                amount: Number(amount),
                currency,
                description: description.trim(),
              });
              setEditing(false);
            }}
          >
            Save
          </Button>
          {editing && (
            <Button
              size="compact-sm"
              variant="subtle"
              color="gray"
              radius="md"
              disabled={busy}
              onClick={() => setEditing(false)}
            >
              Cancel
            </Button>
          )}
        </Group>
      )}

      {roleMode === "ET" && charge && !showBillForm && !locked && (
        <Group mt="sm" gap={8} justify="flex-end">
          <Button
            size="compact-sm"
            variant="light"
            color="gray"
            radius="md"
            disabled={busy}
            onClick={() => {
              setAmount(charge.amount ?? "");
              setCurrency(charge.currency ?? "ETB");
              setDescription(charge.description ?? "");
              setEditing(true);
            }}
          >
            {charge.status === "SENT" ? "Revise" : "Edit"}
          </Button>
          {(charge.status === "BILLED" || charge.status === "REJECTED") && (
            <Tooltip label="The customer accepts or rejects the price in the portal; the invoice is issued when they accept.">
              <Button
                size="compact-sm"
                color="edr-green"
                radius="md"
                leftSection={<Send size={14} />}
                disabled={busy}
                onClick={onSend}
              >
                {charge.status === "REJECTED"
                  ? "Send again for approval"
                  : "Send to customer for approval"}
              </Button>
            </Tooltip>
          )}
        </Group>
      )}
      {charge?.status === "ACCEPTED" && (
        <Group mt="sm" gap={6} justify="flex-end">
          <Lock size={14} color="var(--mantine-color-teal-7)" />
          <Text fz="12px" c="teal.8" fw={600}>
            Locked — invoice {charge.invoiceNumber ?? ""} awaiting payment
          </Text>
        </Group>
      )}
      {charge?.status === "PAID" && (
        <Group mt="sm" gap={6} justify="flex-end">
          <CheckCircle2 size={14} color="var(--mantine-color-edr-green-6)" />
          <Text fz="12px" c="edr-green.8" fw={600}>
            Settled
          </Text>
        </Group>
      )}
    </Paper>
  );
}

function MiscCreateForm({
  busy,
  onCreate,
}: {
  busy: boolean;
  onCreate: (file: File, input: BillInput) => void;
}) {
  const [file, setFile] = useState<File | null>(null);
  const [amount, setAmount] = useState<number | string>("");
  const [currency, setCurrency] = useState("ETB");
  const [description, setDescription] = useState("");

  return (
    <Group gap={8} align="flex-end" wrap="wrap">
      <TextInput
        label="What is this charge for?"
        placeholder="e.g. Container cleaning and weighbridge fee"
        size="xs"
        radius="md"
        value={description}
        onChange={(e) => setDescription(e.currentTarget.value)}
        maxLength={1000}
        w={320}
      />
      <FileButton onChange={setFile} accept="application/pdf,image/*" disabled={busy}>
        {(props) => (
          <Button
            {...props}
            size="compact-sm"
            variant="light"
            color="edr-green"
            radius="md"
            leftSection={<Upload size={14} />}
          >
            {file ? file.name : "Choose document"}
          </Button>
        )}
      </FileButton>
      <NumberInput
        label="Amount"
        size="xs"
        radius="md"
        min={0.01}
        decimalScale={2}
        value={amount}
        onChange={setAmount}
        w={160}
      />
      <Select
        label="Currency"
        size="xs"
        radius="md"
        data={CURRENCIES}
        value={currency}
        onChange={(v) => v && setCurrency(v)}
        w={100}
      />
      <Button
        size="compact-sm"
        color="edr-green"
        radius="md"
        disabled={busy || !file || !(Number(amount) > 0) || !description.trim()}
        loading={busy}
        onClick={() =>
          file &&
          onCreate(file, {
            amount: Number(amount),
            currency,
            description: description.trim(),
          })
        }
      >
        Create charge
      </Button>
    </Group>
  );
}
