import type { LoaderFunctionArgs } from "react-router";
import { Link, useLoaderData, useNavigation, useFetcher } from "react-router";

import { authenticate } from "../shopify.server";
import db from "../db.server";

interface RecentOrder {
  id: string; // gid
  numericId: string;
  name: string;
  createdAt: string;
  totalPrice: string;
  currency: string;
  customerName: string;
  hasInvoice: boolean;
  invoiceNumber?: string;
  invoiceVersion?: number;
  invoiceSent?: boolean;
  invoiceCancelled?: boolean;
  pdfUrl?: string;
}

const RECENT_ORDERS_QUERY = `#graphql
  query RecentOrders($first: Int!) {
    orders(first: $first, sortKey: CREATED_AT, reverse: true) {
      nodes {
        id
        name
        createdAt
        displayFinancialStatus
        totalPriceSet { shopMoney { amount currencyCode } }
        customer { firstName lastName }
      }
    }
  }
`;

type Filter = "all" | "missing" | "with";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { admin, session } = await authenticate.admin(request);

  const url = new URL(request.url);
  const filterParam = (url.searchParams.get("filter") ?? "all") as Filter;
  const filter: Filter = ["all", "missing", "with"].includes(filterParam)
    ? filterParam
    : "all";

  let orders: RecentOrder[] = [];
  try {
    const res = await admin.graphql(RECENT_ORDERS_QUERY, { variables: { first: 50 } });
    const json = (await res.json()) as {
      data?: {
        orders?: {
          nodes?: Array<{
            id: string;
            name: string;
            createdAt: string;
            totalPriceSet?: { shopMoney: { amount: string; currencyCode: string } };
            customer?: { firstName: string | null; lastName: string | null } | null;
          }>;
        };
      };
    };
    const nodes = json.data?.orders?.nodes ?? [];
    const orderIds = nodes.map((n) => n.id);

    const invoices = await db.invoice.findMany({
      where: {
        shopDomain: session.shop,
        orderId: { in: orderIds },
        kind: "invoice",
      },
      orderBy: [{ version: "desc" }, { createdAt: "desc" }],
    });
    const latestByOrder = new Map<string, (typeof invoices)[number]>();
    for (const inv of invoices) {
      if (!latestByOrder.has(inv.orderId)) latestByOrder.set(inv.orderId, inv);
    }

    orders = nodes.map((n) => {
      const inv = latestByOrder.get(n.id);
      const customer = n.customer
        ? [n.customer.firstName, n.customer.lastName].filter(Boolean).join(" ").trim()
        : "";
      return {
        id: n.id,
        numericId: n.id.replace(/^.*\//, ""),
        name: n.name,
        createdAt: n.createdAt,
        totalPrice: n.totalPriceSet?.shopMoney.amount ?? "",
        currency: n.totalPriceSet?.shopMoney.currencyCode ?? "EUR",
        customerName: customer || "Guest",
        hasInvoice: !!inv && !inv.cancelledAt,
        invoiceNumber: inv?.invoiceNumber,
        invoiceVersion: inv?.version,
        invoiceSent: !!inv?.sentAt,
        invoiceCancelled: !!inv?.cancelledAt,
        pdfUrl: inv?.pdfUrl,
      };
    });
  } catch (err) {
    console.warn("Failed to load recent orders:", err);
  }

  const allCount = orders.length;
  const withCount = orders.filter((o) => o.hasInvoice).length;
  const missingCount = allCount - withCount;

  if (filter === "missing") orders = orders.filter((o) => !o.hasInvoice);
  if (filter === "with") orders = orders.filter((o) => o.hasInvoice);

  return {
    orders,
    filter,
    counts: { all: allCount, with: withCount, missing: missingCount },
  };
};

const dateFmt = new Intl.DateTimeFormat("en-GB", {
  day: "2-digit",
  month: "short",
  year: "numeric",
});
const moneyFmt = new Intl.NumberFormat("de-AT", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
});

function formatMoney(amount: string, currency: string): string {
  const n = Number(amount);
  if (!Number.isFinite(n)) return `${amount} ${currency}`;
  return `${moneyFmt.format(n)} ${currency}`;
}

export default function InvoicesPage() {
  const { orders, filter, counts } = useLoaderData<typeof loader>();
  const navigation = useNavigation();
  const isLoading = navigation.state !== "idle";

  return (
    <s-page heading="Invoices">
      <s-section heading="Recent orders">
        <s-stack direction="block" gap="base">
          <s-paragraph>
            Generate the invoice for an order, regenerate an unsent draft,
            or cancel-and-reissue a sent one. Newest orders appear first.
          </s-paragraph>

          <s-stack direction="inline" gap="small" alignItems="center">
            <FilterChip to="?filter=all" active={filter === "all"} count={counts.all}>
              All
            </FilterChip>
            <FilterChip
              to="?filter=missing"
              active={filter === "missing"}
              count={counts.missing}
            >
              Missing invoice
            </FilterChip>
            <FilterChip to="?filter=with" active={filter === "with"} count={counts.with}>
              Has invoice
            </FilterChip>
          </s-stack>
        </s-stack>

        {isLoading ? (
          <s-stack direction="inline" gap="base" alignItems="center">
            <s-spinner size="base" accessibilityLabel="Loading orders" />
            <s-text tone="neutral">Loading…</s-text>
          </s-stack>
        ) : orders.length === 0 ? (
          <s-stack direction="block" gap="base" alignItems="center">
            <s-text type="strong">No orders match this filter</s-text>
            <s-paragraph tone="neutral">
              Try a different filter or wait for new orders.
            </s-paragraph>
          </s-stack>
        ) : (
          <s-table>
            <s-table-header-row>
              <s-table-header listSlot="primary">Order</s-table-header>
              <s-table-header>Customer</s-table-header>
              <s-table-header>Date</s-table-header>
              <s-table-header format="numeric">Total</s-table-header>
              <s-table-header listSlot="secondary">Invoice</s-table-header>
              <s-table-header listSlot="labeled">Actions</s-table-header>
            </s-table-header-row>
            <s-table-body>
              {orders.map((order) => (
                <OrderRow key={order.id} order={order} />
              ))}
            </s-table-body>
          </s-table>
        )}
      </s-section>

      <s-section heading="About this page">
        <s-stack direction="block" gap="small">
          <s-paragraph>
            Buttons trigger the same generation pipeline used by the order
            page action and Shopify Flow. PDFs are uploaded to Shopify Files
            and linked back to the order via metafields.
          </s-paragraph>
          <s-paragraph tone="neutral">
            Tip: filter by <em>Missing invoice</em> to find orders that still
            need one.
          </s-paragraph>
        </s-stack>
      </s-section>
    </s-page>
  );
}

function FilterChip({
  to,
  active,
  count,
  children,
}: {
  to: string;
  active: boolean;
  count: number;
  children: React.ReactNode;
}) {
  return (
    <Link to={to} style={{ textDecoration: "none" }}>
      <s-clickable-chip color={active ? "strong" : "base"} accessibilityLabel={String(children)}>
        {children} ({count})
      </s-clickable-chip>
    </Link>
  );
}

function OrderRow({ order }: { order: RecentOrder }) {
  const fetcher = useFetcher<{ ok: boolean; error?: string; invoiceNumber?: string }>();
  const sendFetcher = useFetcher<{ ok: boolean; error?: string }>();
  const isBusy = fetcher.state !== "idle";
  const isSending = sendFetcher.state !== "idle";
  const isCancelReissue = order.hasInvoice && order.invoiceSent;
  const buttonLabel = !order.hasInvoice
    ? "Generate"
    : order.invoiceSent
      ? "Cancel & reissue"
      : "Regenerate";
  const sendLabel = order.invoiceSent ? "Re-send" : "Send";

  return (
    <s-table-row>
      <s-table-cell>
        <s-stack direction="block" gap="none">
          <s-text type="strong">{order.name}</s-text>
        </s-stack>
      </s-table-cell>
      <s-table-cell>{order.customerName}</s-table-cell>
      <s-table-cell>{dateFmt.format(new Date(order.createdAt))}</s-table-cell>
      <s-table-cell>{formatMoney(order.totalPrice, order.currency)}</s-table-cell>
      <s-table-cell>
        {order.hasInvoice ? (
          <s-stack direction="block" gap="none">
            <s-stack direction="inline" gap="small" alignItems="center">
              <s-text type="strong">{order.invoiceNumber}</s-text>
              {order.invoiceCancelled ? (
                <s-badge tone="critical">Cancelled</s-badge>
              ) : order.invoiceSent ? (
                <s-badge tone="success">Sent</s-badge>
              ) : (
                <s-badge tone="info">Issued</s-badge>
              )}
              {order.invoiceVersion && order.invoiceVersion > 1 ? (
                <s-text tone="neutral">v{order.invoiceVersion}</s-text>
              ) : null}
            </s-stack>
            {fetcher.data?.error ? (
              <s-text tone="critical">{fetcher.data.error}</s-text>
            ) : null}
            {sendFetcher.data?.error ? (
              <s-text tone="critical">{sendFetcher.data.error}</s-text>
            ) : null}
          </s-stack>
        ) : (
          <s-text tone="neutral">—</s-text>
        )}
      </s-table-cell>
      <s-table-cell>
        <s-stack direction="inline" gap="small" justifyContent="end" alignItems="center">
          {order.pdfUrl ? (
            <s-link href={order.pdfUrl} target="_blank">
              PDF
            </s-link>
          ) : null}
          <fetcher.Form method="post" action={`/api/orders/${order.numericId}/invoice`}>
            {isCancelReissue ? (
              <input type="hidden" name="action" value="cancel_reissue" />
            ) : null}
            <s-button
              type="submit"
              disabled={isBusy || isSending}
              variant={order.hasInvoice ? "secondary" : "primary"}
              tone={isCancelReissue ? "critical" : "auto"}
            >
              {isBusy ? "Working…" : buttonLabel}
            </s-button>
          </fetcher.Form>
          <sendFetcher.Form method="post" action={`/api/orders/${order.numericId}/invoice`}>
            <input type="hidden" name="action" value="send" />
            <s-button
              type="submit"
              disabled={isBusy || isSending}
              variant={order.hasInvoice && !order.invoiceSent ? "primary" : "secondary"}
            >
              {isSending ? "Sending…" : sendLabel}
            </s-button>
          </sendFetcher.Form>
        </s-stack>
      </s-table-cell>
    </s-table-row>
  );
}
