import React from "react";
import { Document, Page, View, Text, Image, StyleSheet, renderToBuffer } from "@react-pdf/renderer";
import { formatPrice, formatDate } from "./formatters";
import { computeTaxBreakdown } from "./taxBreakdown";
import type { InvoiceSeller } from "./seller";

export type { InvoiceSeller };

// Rendered by the frontend at checkout time (attached to the order
// confirmation email) and again on demand for "Rechnung herunterladen" —
// same render call both times, so a re-download always matches what was
// emailed.
//
// Built-in Helvetica, not a registered web font — this can render inside a
// fire-and-forget email step; a font-fetch failure there is one more way
// to lose the invoice attachment for no real design benefit.
const BRAND = "#f6a701";
const TEXT_MUTED = "#6b6b69";
const BORDER = "#e5e0d8";
const BG_MUTED = "#f8f5f1";
const SUCCESS = "#2f8f4e";

const styles = StyleSheet.create({
  page: { padding: 0, fontSize: 10, fontFamily: "Helvetica", color: "#1a1a18" },
  // A rule, not a filled band — a bold brand-colored line with a thin
  // muted second line underneath, rather than a plain 1pt gray divider.
  headerBand: {
    padding: 32,
    paddingBottom: 24,
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    borderBottomWidth: 3,
    borderBottomColor: BRAND,
  },
  headerRuleThin: { height: 1, backgroundColor: BORDER, marginHorizontal: 32 },
  wordmark: { fontFamily: "Helvetica-Bold", fontSize: 14 },
  kindLabel: { fontFamily: "Helvetica-Bold", fontSize: 22, color: BRAND, letterSpacing: 1 },
  body: { padding: 32, paddingBottom: 90 },
  addressRow: { flexDirection: "row", flexWrap: "wrap", justifyContent: "space-between", rowGap: 12, marginBottom: 24 },
  addressBlock: { width: "45%" },
  // Narrower variant for when a 3rd (shipping) block joins Von/An — three
  // of these plus the row's own space-between still fit a Page's width
  // without any block cramping its text.
  addressBlockThird: { width: "30%" },
  addressLabel: { fontSize: 8, color: TEXT_MUTED, marginBottom: 4, textTransform: "uppercase" },
  addressLine: { fontSize: 10, lineHeight: 1.5 },
  metaRow: { flexDirection: "row", gap: 10, marginBottom: 16, flexWrap: "wrap" },
  metaBox: { borderWidth: 1, borderColor: BORDER, borderRadius: 6, paddingVertical: 8, paddingHorizontal: 12 },
  metaLabel: { fontSize: 7, color: TEXT_MUTED, textTransform: "uppercase", marginBottom: 2 },
  metaValue: { fontSize: 10, fontFamily: "Helvetica-Bold" },
  paidBadgeText: { fontSize: 10, fontFamily: "Helvetica-Bold", color: SUCCESS },
  table: { borderRadius: 6, overflow: "hidden", borderWidth: 1, borderColor: BORDER, marginTop: 8, marginBottom: 16 },
  tableHeader: { flexDirection: "row", backgroundColor: BG_MUTED, paddingVertical: 8, paddingHorizontal: 10 },
  // Every item row shares this same tinted background now — no more
  // odd/even zebra striping that left alternating rows plain white; the
  // borderTop is what separates rows visually instead.
  tableRow: { flexDirection: "row", paddingVertical: 8, paddingHorizontal: 10, borderTopWidth: 1, borderTopColor: BORDER, backgroundColor: BG_MUTED },
  // marginRight (not a tableRow `gap`) — explicit and unambiguous across
  // react-pdf's flexbox implementation for a mixed Image/Text row, rather
  // than relying on a row-level `gap` to apply consistently between an
  // empty-when-no-image colImage and colName.
  colImage: { width: 28, marginRight: 10 },
  itemImage: { width: 28, height: 28, borderRadius: 3 },
  colName: { flex: 3 },
  colQty: { flex: 1, textAlign: "right" },
  colPrice: { flex: 1, textAlign: "right" },
  colTotal: { flex: 1, textAlign: "right" },
  headerCell: { fontSize: 8, color: TEXT_MUTED, textTransform: "uppercase" },
  bundleLine: { fontSize: 8, color: TEXT_MUTED, marginTop: 2 },
  summary: { alignItems: "flex-end", marginBottom: 24 },
  summaryBox: { width: 240, backgroundColor: BG_MUTED, borderRadius: 6, padding: 14 },
  summaryRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 2 },
  summaryLabel: { fontSize: 10, color: TEXT_MUTED },
  summaryValue: { fontSize: 10 },
  grandTotalRow: { flexDirection: "row", justifyContent: "space-between", paddingTop: 8, marginTop: 6, borderTopWidth: 1, borderTopColor: BORDER },
  grandTotalLabel: { fontSize: 12, fontFamily: "Helvetica-Bold" },
  grandTotalValue: { fontSize: 12, fontFamily: "Helvetica-Bold" },
  // Netto, right under Gesamt — a bit of top margin to read as its own
  // line following the total, not crowded against the divider above it.
  nettoRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 2, marginTop: 6 },
  // "Enthält X% MwSt.: Y €" annotations under Gesamt — same "contained
  // within the total, not an additional deduction" framing the website's
  // own VatBreakdown component already uses on /cart and /checkout.
  // Deliberately NOT part of the summaryRow additive chain above (Zwischen-
  // summe → Rabatt → Versand → Gesamt): every row in that chain sums
  // exactly to Gesamt on its own; the tax-rate breakdown is informational
  // context about what portion of Gesamt is tax, not a further adjustment.
  // A previous version showed the same per-rate net/tax figures as their
  // own additive-looking rows alongside Rabatt/Versand, which double-
  // counted shipping/discount (already proportionally folded into each
  // rate's own net/tax by computeTaxBreakdown) — the visible rows never
  // actually summed to the printed Gesamt. Fixed 2026-07-23.
  vatNotes: { marginTop: 6 },
  vatNoteRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 1 },
  vatNoteLabel: { fontSize: 8, color: TEXT_MUTED },
  vatNoteValue: { fontSize: 8, color: TEXT_MUTED },
  // Moved here (below the summary card) from the top meta row — reads more
  // naturally right next to the amount it's confirming was paid, and keeps
  // the meta row itself to just the three reference numbers. Deliberately
  // a *sibling* of summaryBox, not a row inside it — the confirmation
  // applies to the whole card's total, so it sits visually outside/below
  // the card rather than being absorbed as one more line inside it (a
  // correction made after initially placing it inside the card). Plain
  // green text, not a pill/badge box — a colored background here read as
  // too heavy/attention-grabbing for what's just a status note.
  paidBadgeRow: { alignItems: "flex-end", marginTop: 10 },
  // `fixed` (below, on the element) + absolute positioning — always pinned
  // to the bottom of the page regardless of how much content is above it,
  // rather than just following wherever the content flow happens to end.
  // Centered (not left-aligned) — reads as a formal closing block rather
  // than a stray left-hanging line once the page's content above is itself
  // centered around the summary box on the right and the table spanning
  // full width.
  footer: {
    position: "absolute",
    bottom: 32,
    left: 32,
    right: 32,
    borderTopWidth: 1,
    borderTopColor: BORDER,
    paddingTop: 12,
    fontSize: 8,
    color: TEXT_MUTED,
    textAlign: "center",
  },
});

export type InvoiceItem = {
  productName: string;
  quantity: number;
  unitPrice: number;
  taxRatePercent: number;
  bundleContents?: string | null;
  variantName?: string | null;
  imageUrl?: string | null;
};

export type InvoiceOrder = {
  orderNumber: string;
  invoiceNumber: string;
  invoiceIssuedAt: string;
  customerFirstName: string;
  customerLastName: string;
  // Optional B2B recipient fields (Orders.companyName/vatId in the Payload
  // backend) — when present, shown in the "An" block: companyName as its
  // own line above the contact person's name, vatId as its own line below
  // the address. Neither implies the other (see Orders.ts's own comment).
  companyName?: string | null;
  vatId?: string | null;
  // Innergemeinschaftliche Lieferung (§4 Nr. 1b UStG) — decided server-side
  // at checkout via a live VIES lookup (see the frontend's api/checkout/
  // route.ts and lib/vatExemption.ts), never guessed from vatId's mere
  // presence/format. When true, every item's own taxRatePercent is already
  // 0 and unitPrice already de-grossed (the actual charged/persisted
  // figures) — this flag only controls the exemption note shown here,
  // it doesn't itself change any arithmetic in this file.
  vatExempt?: boolean;
  // Kleinunternehmerregelung (§19 UStG) — snapshotted per order at checkout
  // time (frontend's api/checkout/route.ts, from the seller's own
  // company-settings.kleinunternehmer), never read live off the seller at
  // render time: an invoice already issued must keep showing the tax
  // treatment that actually applied at the moment of sale, even if the
  // tenant later toggles this setting on/off. Takes precedence over
  // vatExempt below when both would otherwise apply — a Kleinunternehmer
  // never charges VAT in the first place, so there is nothing left to
  // "exempt" via the separate intra-community rule.
  kleinunternehmer?: boolean;
  deliveryMethod: "address" | "packstation";
  street?: string | null;
  packstationNumber?: string | null;
  postNumber?: string | null;
  zip: string;
  city: string;
  country: string;
  // Optional package destination distinct from the "An" recipient above —
  // when set, the invoice shows both addresses (billing stays "An", this
  // becomes its own "Lieferadresse" block) instead of implying the order
  // shipped to the billing address, which is only true when this is unset.
  hasDifferentShippingAddress?: boolean;
  shippingFirstName?: string | null;
  shippingLastName?: string | null;
  shippingDeliveryMethod?: "address" | "packstation" | null;
  shippingStreet?: string | null;
  shippingPackstationNumber?: string | null;
  shippingPostNumber?: string | null;
  shippingZip?: string | null;
  shippingCity?: string | null;
  shippingCountry?: string | null;
  paymentMethodTitle: string;
  items: InvoiceItem[];
  subtotal: number;
  shippingCost: number;
  discountAmount: number;
  discountCode: string | null;
  total: number;
};

// Used only by /company-settings-preview's Live Preview — a fixed sample
// order so the admin sees a realistic-looking invoice while editing
// company-settings fields, without depending on any real order existing.
export const SAMPLE_INVOICE_ORDER: InvoiceOrder = {
  orderNumber: "#EP-0001-A7K2",
  invoiceNumber: "RE-0001",
  invoiceIssuedAt: new Date().toISOString(),
  customerFirstName: "Max",
  customerLastName: "Mustermann",
  deliveryMethod: "address",
  street: "Musterweg 5",
  zip: "10115",
  city: "Berlin",
  country: "Deutschland",
  paymentMethodTitle: "Kreditkarte",
  items: [
    { productName: "ToDo-Karten – Set", quantity: 1, unitPrice: 12.9, taxRatePercent: 19, bundleContents: null },
    { productName: "Wochenplaner – Überblick", quantity: 2, unitPrice: 14.9, taxRatePercent: 19, bundleContents: null },
  ],
  subtotal: 42.7,
  shippingCost: 0,
  discountAmount: 5,
  discountCode: "WILLKOMMEN10",
  total: 37.7,
};

// "Überweisung" (bank transfer) is the only payment method on this shop
// that ISN'T settled immediately — Kreditkarte/PayPal both capture at
// checkout. Rather than hardcode a list of "immediate" method titles
// (fragile the moment a new one is added in Payload's payment-methods
// collection), the only method that's ever NOT immediate is named
// explicitly — everything else defaults to "paid already".
function isPaidImmediately(paymentMethodTitle: string): boolean {
  return paymentMethodTitle !== "Überweisung";
}

// Distributes the order-level discount/shipping proportionally across each
// item's gross line total before computing that line's net/tax — so the
// per-rate summary still reconciles exactly to `order.total` even when a
// discount or shipping cost is present alongside items taxed at different
// rates. Falls back to the seller's default rate for any line that
// predates this field (older orders had no per-item snapshot).
function groupByTaxRate(order: InvoiceOrder, defaultRate: number): { rate: number; net: number; tax: number; gross: number }[] {
  return computeTaxBreakdown(
    order.items.map((item) => ({ quantity: item.quantity, unitPrice: item.unitPrice, taxRatePercent: item.taxRatePercent ?? defaultRate })),
    order.subtotal,
    order.discountAmount,
    order.shippingCost,
  );
}

// Packstation deliveries show number and Postnummer on their own lines
// (not one combined "Packstation X · Postnummer Y" line) — each is looked
// up independently at a DHL Packstation, easier to scan stacked than
// split by a middle dot in running text. A plain street address stays a
// single line, same as before.
function DeliveryLines({ deliveryMethod, street, packstationNumber, postNumber }: { deliveryMethod?: string | null; street?: string | null; packstationNumber?: string | null; postNumber?: string | null }) {
  if (deliveryMethod === "packstation") {
    return (
      <>
        <Text style={styles.addressLine}>Packstation {packstationNumber}</Text>
        <Text style={styles.addressLine}>Postnummer {postNumber}</Text>
      </>
    );
  }
  return <Text style={styles.addressLine}>{street}</Text>;
}

// Exported (not just used internally by renderInvoicePdf below) so
// /company-settings-preview's client component can mount it directly with
// @react-pdf/renderer's browser-side <PDFViewer> — a live, in-browser
// rendered PDF that re-renders as the admin edits company-settings fields
// via Payload's postMessage-based useLivePreview(), no server round-trip
// needed for each keystroke the way an HTML preview would.
export function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller: InvoiceSeller }) {
  const rateGroups = groupByTaxRate(order, seller.taxRatePercent);
  const paid = isPaidImmediately(order.paymentMethodTitle);
  const addressBlockStyle = order.hasDifferentShippingAddress ? styles.addressBlockThird : styles.addressBlock;

  return (
    <Document>
      <Page size="A4" style={styles.page}>
        <View style={styles.headerBand}>
          <Text style={styles.wordmark}>einfach produktiv.</Text>
          <Text style={styles.kindLabel}>RECHNUNG</Text>
        </View>

        <View style={styles.body}>
          <View style={styles.addressRow}>
            <View style={addressBlockStyle}>
              <Text style={styles.addressLabel}>Von</Text>
              <Text style={styles.addressLine}>{seller.sellerName}</Text>
              <Text style={styles.addressLine}>{seller.sellerStreet}</Text>
              <Text style={styles.addressLine}>
                {seller.sellerZip} {seller.sellerCity}
              </Text>
              <Text style={styles.addressLine}>{seller.sellerCountry}</Text>
            </View>
            <View style={addressBlockStyle}>
              <Text style={styles.addressLabel}>An</Text>
              {order.companyName && <Text style={styles.addressLine}>{order.companyName}</Text>}
              <Text style={styles.addressLine}>
                {order.customerFirstName} {order.customerLastName}
              </Text>
              <DeliveryLines deliveryMethod={order.deliveryMethod} street={order.street} packstationNumber={order.packstationNumber} postNumber={order.postNumber} />
              <Text style={styles.addressLine}>
                {order.zip} {order.city}
              </Text>
              <Text style={styles.addressLine}>{order.country}</Text>
              {order.vatId && <Text style={styles.addressLine}>USt-IdNr. {order.vatId}</Text>}
            </View>
            {order.hasDifferentShippingAddress && (
              <View style={addressBlockStyle}>
                <Text style={styles.addressLabel}>Lieferadresse</Text>
                <Text style={styles.addressLine}>
                  {order.shippingFirstName} {order.shippingLastName}
                </Text>
                <DeliveryLines
                  deliveryMethod={order.shippingDeliveryMethod}
                  street={order.shippingStreet}
                  packstationNumber={order.shippingPackstationNumber}
                  postNumber={order.shippingPostNumber}
                />
                <Text style={styles.addressLine}>
                  {order.shippingZip} {order.shippingCity}
                </Text>
                <Text style={styles.addressLine}>{order.shippingCountry}</Text>
              </View>
            )}
          </View>

          <View style={styles.metaRow}>
            <View style={styles.metaBox}>
              <Text style={styles.metaLabel}>Rechnungs-Nr.</Text>
              <Text style={styles.metaValue}>{order.invoiceNumber}</Text>
            </View>
            <View style={styles.metaBox}>
              <Text style={styles.metaLabel}>Datum</Text>
              <Text style={styles.metaValue}>{formatDate(order.invoiceIssuedAt)}</Text>
            </View>
            <View style={styles.metaBox}>
              <Text style={styles.metaLabel}>Bestellnummer</Text>
              <Text style={styles.metaValue}>{order.orderNumber}</Text>
            </View>
          </View>

          <View style={styles.table}>
            <View style={styles.tableHeader}>
              <View style={styles.colImage} />
              <Text style={[styles.colName, styles.headerCell]}>Artikel</Text>
              <Text style={[styles.colQty, styles.headerCell]}>Menge</Text>
              <Text style={[styles.colPrice, styles.headerCell]}>Einzelpreis</Text>
              <Text style={[styles.colTotal, styles.headerCell]}>Betrag</Text>
            </View>
            {order.items.map((item, i) => (
              <View style={styles.tableRow} key={i}>
                <View style={styles.colImage}>
                  {item.imageUrl && <Image src={item.imageUrl} style={styles.itemImage} />}
                </View>
                <View style={styles.colName}>
                  <Text>
                    {item.productName}
                    {item.variantName ? ` (${item.variantName})` : ""}
                  </Text>
                  {item.bundleContents ? <Text style={styles.bundleLine}>{item.bundleContents}</Text> : null}
                </View>
                <Text style={styles.colQty}>{item.quantity}</Text>
                <Text style={styles.colPrice}>{formatPrice(item.unitPrice)}</Text>
                <Text style={styles.colTotal}>{formatPrice(item.quantity * item.unitPrice)}</Text>
              </View>
            ))}
          </View>

          <View style={styles.summary}>
            <View style={styles.summaryBox}>
              {/* Genuinely additive chain — Zwischensumme (raw item gross,
                  unscaled) − Rabatt + Versand always equals Gesamt exactly,
                  unlike the old layout this replaced (see vatNotes' own
                  comment above). */}
              <View style={styles.summaryRow}>
                <Text style={styles.summaryLabel}>Zwischensumme</Text>
                <Text style={styles.summaryValue}>{formatPrice(order.subtotal)}</Text>
              </View>
              {order.discountAmount > 0 && (
                <View style={styles.summaryRow}>
                  <Text style={styles.summaryLabel}>Rabatt{order.discountCode ? ` (${order.discountCode})` : ""}</Text>
                  <Text style={styles.summaryValue}>-{formatPrice(order.discountAmount)}</Text>
                </View>
              )}
              <View style={styles.summaryRow}>
                <Text style={styles.summaryLabel}>Versand</Text>
                <Text style={styles.summaryValue}>{order.shippingCost === 0 ? "Kostenlos" : formatPrice(order.shippingCost)}</Text>
              </View>
              <View style={styles.grandTotalRow}>
                <Text style={styles.grandTotalLabel}>Gesamt</Text>
                <Text style={styles.grandTotalValue}>{formatPrice(order.total)}</Text>
              </View>
              <View style={styles.vatNotes}>
                {/* Exempt orders already have every item at 0% (see this
                    file's own InvoiceOrder.vatExempt comment) — "enthält
                    0% MwSt.: 0,00 €" would be a meaningless thing to print,
                    so this replaces the whole per-rate breakdown with the
                    actual legal basis instead. Kept directly under Gesamt
                    (not after Netto) — "enthält X% MwSt." describes what's
                    contained in Gesamt; sandwiching Netto between the two
                    made it read as if Netto were what contained the tax,
                    which is backwards (Netto is the figure with tax
                    already taken out). */}
                {order.kleinunternehmer ? (
                  <Text style={styles.vatNoteLabel}>Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.</Text>
                ) : order.vatExempt ? (
                  <Text style={styles.vatNoteLabel}>Steuerfreie innergemeinschaftliche Lieferung (§4 Nr. 1b UStG)</Text>
                ) : (
                  rateGroups.map((g) => (
                    <View style={styles.vatNoteRow} key={g.rate}>
                      <Text style={styles.vatNoteLabel}>enthält {g.rate}% MwSt.</Text>
                      <Text style={styles.vatNoteValue}>{formatPrice(g.tax)}</Text>
                    </View>
                  ))
                )}
              </View>
              {/* Netto (Gesamt minus every rate's own tax, summed — a
                  single well-defined figure regardless of how many
                  distinct rates the order actually has) — businesses read
                  this off the invoice directly for their own input-tax
                  deduction instead of having to compute Gesamt minus MwSt
                  by hand. Shown on every invoice, not just B2B/non-exempt
                  ones — on a vatExempt order Netto and Gesamt happen to be
                  the same figure (0% tax), but the row stays for layout
                  consistency across every invoice this shop issues. The one
                  exception is `kleinunternehmer` (built 2026-07-24, at the
                  user's own request): there Netto = Gesamt for a
                  structural reason, not a coincidental one (§19 UStG means
                  there was never a tax component to subtract in the first
                  place), and the row would just look like a second,
                  redundant total sitting directly under the §19 notice
                  above rather than genuinely new information. Placed last
                  (after the MwSt annotation, not before it) — see that
                  annotation's own comment above for why the order matters
                  here. Styled with the same small/muted vatNoteLabel/
                  vatNoteValue as that annotation, not the larger
                  summaryLabel/summaryValue the build-up rows above Gesamt
                  use — Netto is supplementary detail *below* the hero
                  Gesamt figure, same visual tier as the tax breakdown, not
                  another primary line competing with it. */}
              {!order.kleinunternehmer && (
                <View style={styles.nettoRow}>
                  <Text style={styles.vatNoteLabel}>Netto</Text>
                  <Text style={styles.vatNoteValue}>{formatPrice(rateGroups.reduce((sum, g) => sum + g.net, 0))}</Text>
                </View>
              )}
            </View>
            {paid && (
              <View style={styles.paidBadgeRow}>
                <Text style={styles.paidBadgeText}>✓ Bereits beglichen ({order.paymentMethodTitle})</Text>
              </View>
            )}
          </View>
        </View>

        <View style={styles.footer} fixed>
          <Text>
            {seller.sellerName} · {seller.sellerStreet}, {seller.sellerZip} {seller.sellerCity} · {seller.sellerEmail}
            {/* Omitted entirely (not "USt-IdNr. " with a blank value) when
                unset — a Kleinunternehmer (§19 UStG) often has no USt-IdNr.
                at all, see seller.ts's own comment. */}
            {seller.vatId ? ` · USt-IdNr. ${seller.vatId}` : ""}
            {seller.registerCourt && seller.registerNumber ? ` · ${seller.registerCourt} · ${seller.registerNumber}` : ""}
            {seller.managingDirector ? ` · Geschäftsführung: ${seller.managingDirector}` : ""}
          </Text>
          {/* Always shown when set, regardless of payment method — not
              gated on "Überweisung" specifically (a customer who paid by
              card can still want the seller's bank details for other
              reasons, e.g. a refund) — see this package's README. */}
          {(seller.iban || seller.bic) && (
            <Text style={{ marginTop: 4 }}>
              Bankverbindung: {[seller.bankName, seller.iban && `IBAN ${seller.iban}`, seller.bic && `BIC ${seller.bic}`].filter(Boolean).join(" · ")}
            </Text>
          )}
        </View>
      </Page>
    </Document>
  );
}

export async function renderInvoicePdf(order: InvoiceOrder, seller: InvoiceSeller): Promise<Buffer> {
  return renderToBuffer(<InvoiceDocument order={order} seller={seller} />);
}

// Exported for unit testing (see src/__tests__/invoicePdf.test.ts) — the
// actual money math and payment-status logic, independent of PDF
// rendering.
export const __testables = { isPaidImmediately, groupByTaxRate };
