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
  name: string;
  createdAt: string;
  totalPrice: string;
  currency: string;
  hasInvoice: boolean;
  invoiceNumber?: string;
  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 } }
      }
    }
  }
`;

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

  const url = new URL(request.url);
  const filter = url.searchParams.get("filter") ?? "all";

  // Recent orders from Shopify (first 25).
  let orders: RecentOrder[] = [];
  try {
    const res = await admin.graphql(RECENT_ORDERS_QUERY, { variables: { first: 25 } });
    const json = (await res.json()) as {
      data?: {
        orders?: {
          nodes?: Array<{
            id: string;
            name: string;
            createdAt: string;
            totalPriceSet?: { shopMoney: { amount: string; currencyCode: string } };
          }>;
        };
      };
    };
    const nodes = json.data?.orders?.nodes ?? [];
    const orderIds = nodes.map((n) => n.id);

    // Look up which of these orders already have a non-cancelled invoice.
    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);
      return {
        id: n.id,
        name: n.name,
        createdAt: n.createdAt,
        totalPrice: n.totalPriceSet?.shopMoney.amount ?? "",
        currency: n.totalPriceSet?.shopMoney.currencyCode ?? "EUR",
        hasInvoice: !!inv && !inv.cancelledAt,
        invoiceNumber: inv?.invoiceNumber,
        invoiceSent: !!inv?.sentAt,
        invoiceCancelled: !!inv?.cancelledAt,
        pdfUrl: inv?.pdfUrl,
      };
    });
  } catch (err) {
    console.warn("Failed to load recent orders:", err);
  }

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

  return { orders, filter };
};

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

  return (
    <s-page heading="Invoices">
      <s-section heading="Recent orders">
        <s-paragraph>
          Generate or view the latest invoice for each order. For sent
          invoices, use Cancel & reissue to issue a Stornorechnung followed
          by a fresh invoice.
        </s-paragraph>

        <s-stack direction="inline" gap="base">
          <Link to="?filter=all">All</Link>
          <Link to="?filter=missing">Missing invoice</Link>
          <Link to="?filter=with">Has invoice</Link>
        </s-stack>

        {isLoading ? (
          <s-paragraph>Loading…</s-paragraph>
        ) : orders.length === 0 ? (
          <s-paragraph>No orders match the current filter.</s-paragraph>
        ) : (
          <s-unordered-list>
            {orders.map((o) => (
              <OrderRow key={o.id} order={o} />
            ))}
          </s-unordered-list>
        )}
      </s-section>
      <s-section heading="Bulk generate">
        <s-paragraph>
          Use the buttons next to each order to generate one at a time. (A
          true multi-select bulk run will be added once the admin block
          extension is installed.)
        </s-paragraph>
        <s-paragraph>
          Tip: filter by <em>Missing invoice</em> to see orders that still
          need one.
        </s-paragraph>
      </s-section>
    </s-page>
  );
}

function OrderRow({ order }: { order: RecentOrder }) {
  const numericId = order.id.replace(/^.*\//, "");
  const fetcher = useFetcher<{ ok: boolean; error?: string; invoiceNumber?: string }>();
  const isBusy = fetcher.state !== "idle";

  return (
    <s-list-item>
      <s-stack direction="inline" gap="base" align-items="center">
        <strong>{order.name}</strong>
        <span>{order.totalPrice} {order.currency}</span>
        {order.hasInvoice ? (
          <span>
            ✓ {order.invoiceNumber}
            {order.invoiceCancelled
              ? " (cancelled)"
              : order.invoiceSent
                ? " (sent)"
                : ""}
            {order.pdfUrl ? (
              <>
                {" — "}
                <a href={order.pdfUrl} target="_blank" rel="noreferrer">PDF</a>
              </>
            ) : null}
          </span>
        ) : (
          <span>— no invoice yet</span>
        )}
        <fetcher.Form method="post" action={`/api/orders/${numericId}/invoice`}>
          <s-button type="submit" disabled={isBusy} variant="primary">
            {order.hasInvoice
              ? order.invoiceSent
                ? "Cancel & reissue"
                : "Regenerate"
              : "Generate"}
          </s-button>
          {order.hasInvoice && order.invoiceSent && (
            <input type="hidden" name="action" value="cancel_reissue" />
          )}
        </fetcher.Form>
        {fetcher.data?.error && (
          <span style={{ color: "red" }}>{fetcher.data.error}</span>
        )}
      </s-stack>
    </s-list-item>
  );
}
