"use client";

import * as React from "react";
import { DownloadIcon, SearchIcon } from "lucide-react";

import { kes } from "@/components/medora/payroll";
import { ListTable, type Col } from "@/components/medora/record-lists";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";

/*
 * The sales list (PS-22) and the sales report book (RP-06, RP-07). The list never loads every sale: it asks
 * for one page at a time with the filters applied on the server, and starts on today. The report book
 * derives every sales report from the same sale records, so the reports always agree with each other.
 */

export type SaleItem = { product: string; category: string; qty: number; price: number; cost: number; vat: number };
export type SaleRecord = {
  receipt: string;
  date: string;
  time: string;
  branch: string;
  seller: string;
  cashier: string;
  customer?: string;
  method: "Cash" | "M-Pesa" | "Card" | "Credit";
  items: SaleItem[];
  etims: boolean;
  status: "Completed" | "Part returned" | "Returned";
  returned: number;
  archived?: boolean;
  invoice?: string;
};
export type StkAttempt = { checkoutId: string; at: string; receipt: string; phone: string; amount: number; result: "Paid" | "Cancelled by customer" | "Timed out, paid on check" | "Timed out, not paid"; mpesaRef?: string };

const r2 = (n: number) => Math.round(n * 100) / 100;
export const saleNet = (s: SaleRecord) => r2(s.items.reduce((t, i) => t + i.qty * i.price, 0));
export const saleVat = (s: SaleRecord) => r2(s.items.reduce((t, i) => t + i.qty * i.price * (i.vat / 100), 0));
export const saleTotalOf = (s: SaleRecord) => r2(saleNet(s) + saleVat(s));
const sum = <T,>(xs: T[], f: (x: T) => number) => r2(xs.reduce((t, x) => t + f(x), 0));

/* ------------------------------------ PS-22 Sales list ------------------------------------ */

export type SalesQuery = { from: string; to: string; seller: string; cashier: string; q: string; page: number; pageSize: number };
export type SalesPage = { rows: SaleRecord[]; total: number; totalValue: number };

/**
 * One page of sales, fetched with the filters. `fetchPage` is the API call; the list holds only the page on
 * screen. Export asks the server for every matching sale as a file, rather than downloading pages here.
 */
export function SalesList({ today, sellers, cashiers, fetchPage, onExport, onOpen }: { today: string; sellers: string[]; cashiers: string[]; fetchPage: (q: SalesQuery) => Promise<SalesPage>; onExport: (q: Omit<SalesQuery, "page" | "pageSize">, count: number) => void; onOpen: (receipt: string) => void }) {
  const [q, setQ] = React.useState<SalesQuery>({ from: today, to: today, seller: "", cashier: "", q: "", page: 1, pageSize: 10 });
  const [data, setData] = React.useState<SalesPage | null>(null);
  const [loading, setLoading] = React.useState(true);
  const req = React.useRef(0);

  React.useEffect(() => {
    const id = ++req.current;
    fetchPage(q).then((d) => {
      // Ignore a slow answer to an older query, so the list never shows the wrong filter's rows.
      if (id !== req.current) return;
      setData(d);
      setLoading(false);
    });
  }, [q, fetchPage]);

  const set = (patch: Partial<SalesQuery>) => {
    setLoading(true);
    setQ((x) => ({ ...x, ...patch, page: patch.page ?? 1 }));
  };
  const pages = data ? Math.max(1, Math.ceil(data.total / q.pageSize)) : 1;
  const first = data && data.total ? (q.page - 1) * q.pageSize + 1 : 0;
  const last = data ? Math.min(data.total, q.page * q.pageSize) : 0;
  const isToday = q.from === today && q.to === today;

  return (
    <div className="space-y-3">
      <div className="flex flex-wrap items-end gap-2">
        <div className="space-y-1">
          <label htmlFor="sl-from" className="text-xs font-medium">
            From
          </label>
          <Input id="sl-from" type="date" className="w-40" max={q.to} value={q.from} onChange={(e) => e.target.value && set({ from: e.target.value })} />
        </div>
        <div className="space-y-1">
          <label htmlFor="sl-to" className="text-xs font-medium">
            To
          </label>
          <Input id="sl-to" type="date" className="w-40" min={q.from} max={today} value={q.to} onChange={(e) => e.target.value && set({ to: e.target.value })} />
        </div>
        <NativeSelect aria-label="Seller" value={q.seller} onChange={(e) => set({ seller: e.target.value })}>
          <NativeSelectOption value="">Any seller</NativeSelectOption>
          {sellers.map((s) => (
            <NativeSelectOption key={s}>{s}</NativeSelectOption>
          ))}
        </NativeSelect>
        <NativeSelect aria-label="Cashier" value={q.cashier} onChange={(e) => set({ cashier: e.target.value })}>
          <NativeSelectOption value="">Any cashier</NativeSelectOption>
          {cashiers.map((s) => (
            <NativeSelectOption key={s}>{s}</NativeSelectOption>
          ))}
        </NativeSelect>
        <div className="relative min-w-44 flex-1">
          <SearchIcon className="pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground" aria-hidden />
          <Input aria-label="Search receipt or customer" placeholder="Receipt or customer" className="pl-8" value={q.q} onChange={(e) => set({ q: e.target.value })} />
        </div>
        <Button size="sm" variant="outline" disabled={!data?.total} onClick={() => onExport({ from: q.from, to: q.to, seller: q.seller, cashier: q.cashier, q: q.q }, data?.total ?? 0)}>
          <DownloadIcon data-icon="inline-start" />
          Export {data?.total ?? 0}
        </Button>
      </div>
      <p className="text-sm" aria-live="polite">
        {isToday ? "Today" : `${q.from} to ${q.to}`}
        {q.seller ? `, sold by ${q.seller}` : ""}
        {q.cashier ? `, at ${q.cashier}'s till` : ""}: {data ? <><strong className="tabular-nums">{data.total.toLocaleString("en-US")}</strong> {data.total === 1 ? "sale" : "sales"} worth <strong className="tabular-nums">{kes(data.totalValue)}</strong></> : "…"}
      </p>
      <div className="overflow-x-auto rounded-md border border-[var(--color-border-panel)] bg-card">
        <table className="w-full min-w-[680px] text-sm" aria-busy={loading}>
          <thead className="bg-[var(--color-bg-surface-subtle)] text-left text-xs text-muted-foreground">
            <tr>
              <th className="px-3 py-2 font-medium">Receipt</th>
              <th className="px-3 py-2 font-medium">Seller and cashier</th>
              <th className="px-3 py-2 font-medium">Customer</th>
              <th className="px-3 py-2 font-medium">Paid by</th>
              <th className="px-3 py-2 text-right font-medium">Total</th>
            </tr>
          </thead>
          <tbody className="divide-y divide-[var(--color-border-subtle)]">
            {loading || !data
              ? Array.from({ length: 4 }, (_, i) => (
                  <tr key={i}>
                    <td colSpan={5} className="px-3 py-2">
                      <Skeleton className="h-5 w-full" />
                    </td>
                  </tr>
                ))
              : data.rows.map((s) => (
                  <tr key={s.receipt} className="hover:bg-muted/40">
                    <td className="px-3 py-2 whitespace-nowrap">
                      <button type="button" className="cursor-pointer font-mono text-xs text-primary underline-offset-4 hover:underline" onClick={() => onOpen(s.receipt)}>
                        {s.receipt}
                      </button>
                      <span className="block text-xs text-muted-foreground">
                        {s.date} {s.time}
                      </span>
                    </td>
                    <td className="px-3 py-2">
                      {s.seller}
                      <span className="block text-xs text-muted-foreground">till: {s.cashier}</span>
                    </td>
                    <td className="px-3 py-2">{s.customer ?? <span className="text-muted-foreground">Walk-in</span>}</td>
                    <td className="px-3 py-2">{s.method}</td>
                    <td className="px-3 py-2 text-right tabular-nums">{kes(saleTotalOf(s))}</td>
                  </tr>
                ))}
            {!loading && data && !data.rows.length ? (
              <tr>
                <td colSpan={5} className="px-3 py-8 text-center text-muted-foreground">
                  No sales match. {isToday ? "Nothing sold yet today, or widen the dates." : "Try other filters."}
                </td>
              </tr>
            ) : null}
          </tbody>
        </table>
      </div>
      <div className="flex items-center justify-between text-xs text-muted-foreground">
        <span>{data?.total ? `${first}–${last} of ${data.total.toLocaleString("en-US")}` : ""}</span>
        <span className="flex items-center gap-1">
          <Button size="sm" variant="outline" disabled={loading || q.page <= 1} onClick={() => set({ page: q.page - 1 })}>
            Previous
          </Button>
          <span className="px-2 tabular-nums">
            Page {q.page} of {pages}
          </span>
          <Button size="sm" variant="outline" disabled={loading || q.page >= pages} onClick={() => set({ page: q.page + 1 })}>
            Next
          </Button>
        </span>
      </div>
    </div>
  );
}

/* ------------------------------------ RP-06, RP-07 Sales report book ------------------------------------ */

type Built = { rows: Record<string, React.ReactNode | number | string>[]; cols: Col<Record<string, React.ReactNode | number | string>>[]; note?: string };
type ReportDef = { id: string; title: string; group: "Existing reports (RP-06)" | "New reports (RP-07)"; build: (sales: SaleRecord[], stk: StkAttempt[], ctx: { onRestore: (r: string) => void }) => Built };

const money = (key: string, header: string, withTotal = true): Col<Record<string, React.ReactNode | number | string>> => ({ key, header, numeric: true, cell: (r) => kes(Number(r[key])), total: withTotal ? (s) => kes(sum(s, (r) => Number(r[key]))) : undefined });
const text = (key: string, header: string): Col<Record<string, React.ReactNode | number | string>> => ({ key, header, cell: (r) => r[key] as React.ReactNode });
const count = (key: string, header: string): Col<Record<string, React.ReactNode | number | string>> => ({ key, header, numeric: true, cell: (r) => Number(r[key]).toLocaleString("en-US"), total: (s) => sum(s, (r) => Number(r[key])).toLocaleString("en-US") });

const groupBy = <T,>(xs: T[], k: (x: T) => string) => {
  const m = new Map<string, T[]>();
  for (const x of xs) m.set(k(x), [...(m.get(k(x)) ?? []), x]);
  return [...m.entries()];
};
const live = (sales: SaleRecord[]) => sales.filter((s) => !s.archived);
const itemsOf = (sales: SaleRecord[]) => live(sales).flatMap((s) => s.items.map((i) => ({ s, i })));

export const SALES_REPORTS: ReportDef[] = [
  {
    id: "history",
    title: "Sales history",
    group: "Existing reports (RP-06)",
    build: (sales) => ({
      rows: live(sales).map((s) => ({ k: s.receipt, receipt: s.receipt, when: `${s.date} ${s.time}`, cashier: s.cashier, method: s.method, total: saleTotalOf(s) })),
      cols: [text("receipt", "Receipt"), text("when", "When"), text("cashier", "Cashier"), text("method", "Paid by"), money("total", "Total")],
    }),
  },
  {
    id: "activity",
    title: "Sales activity with margin",
    group: "Existing reports (RP-06)",
    build: (sales) => ({
      rows: groupBy(itemsOf(sales), (x) => x.i.product).map(([p, xs]) => {
        const rev = sum(xs, (x) => x.i.qty * x.i.price);
        const cost = sum(xs, (x) => x.i.qty * x.i.cost);
        return { k: p, product: p, qty: sum(xs, (x) => x.i.qty), revenue: rev, cost, margin: `${rev ? (((rev - cost) / rev) * 100).toFixed(1) : "0.0"}%` };
      }),
      cols: [text("product", "Product"), count("qty", "Units"), money("revenue", "Revenue, ex VAT"), money("cost", "Cost"), { key: "margin", header: "Margin", numeric: true, cell: (r) => r.margin as string }],
    }),
  },
  {
    id: "payments",
    title: "Payment breakdown",
    group: "Existing reports (RP-06)",
    build: (sales) => ({
      rows: groupBy(live(sales), (s) => s.method).map(([m, ss]) => ({ k: m, method: m, n: ss.length, amount: sum(ss, saleTotalOf) })),
      cols: [text("method", "Method"), count("n", "Sales"), money("amount", "Amount")],
    }),
  },
  {
    id: "cashier",
    title: "Cashier summary",
    group: "Existing reports (RP-06)",
    build: (sales) => ({
      rows: groupBy(live(sales), (s) => s.cashier).map(([c, ss]) => ({ k: c, cashier: c, n: ss.length, cash: sum(ss.filter((s) => s.method === "Cash"), saleTotalOf), other: sum(ss.filter((s) => s.method !== "Cash"), saleTotalOf), total: sum(ss, saleTotalOf) })),
      cols: [text("cashier", "Cashier"), count("n", "Sales"), money("cash", "Cash"), money("other", "M-Pesa, card, credit"), money("total", "Total")],
    }),
  },
  {
    id: "vat",
    title: "VAT",
    group: "Existing reports (RP-06)",
    build: (sales) => ({
      rows: groupBy(itemsOf(sales), (x) => (x.i.vat ? `VAT ${x.i.vat}%` : "Exempt")).map(([code, xs]) => ({ k: code, code, base: sum(xs, (x) => x.i.qty * x.i.price), vat: sum(xs, (x) => x.i.qty * x.i.price * (x.i.vat / 100)) })),
      cols: [text("code", "Tax code"), money("base", "Taxable value"), money("vat", "VAT")],
    }),
  },
  {
    id: "etims",
    title: "eTIMS",
    group: "Existing reports (RP-06)",
    build: (sales) => ({
      rows: live(sales).filter((s) => s.etims).map((s) => ({ k: s.receipt, receipt: s.receipt, when: `${s.date} ${s.time}`, kra: `KRA-${s.receipt.slice(-6)}`, total: saleTotalOf(s) })),
      cols: [text("receipt", "Receipt"), text("when", "When"), text("kra", "KRA invoice no."), money("total", "Total")],
    }),
  },
  {
    id: "returns",
    title: "Returns and refunds",
    group: "Existing reports (RP-06)",
    build: (sales) => ({
      rows: live(sales).filter((s) => s.returned > 0).map((s) => ({ k: s.receipt, receipt: s.receipt, status: s.status, total: saleTotalOf(s), refunded: s.returned })),
      cols: [text("receipt", "Sale"), text("status", "Status"), money("total", "Sale total", false), money("refunded", "Refunded")],
    }),
  },
  {
    id: "archived",
    title: "Archived sales",
    group: "Existing reports (RP-06)",
    build: (sales, _stk, ctx) => ({
      note: "Archived sales are left out of every other report until restored.",
      rows: sales.filter((s) => s.archived).map((s) => ({ k: s.receipt, receipt: s.receipt, when: `${s.date} ${s.time}`, cashier: s.cashier, total: saleTotalOf(s), act: s.receipt })),
      cols: [
        text("receipt", "Receipt"),
        text("when", "When"),
        text("cashier", "Cashier"),
        money("total", "Total", false),
        {
          key: "act",
          header: "",
          cell: (r) => (
            <Button size="sm" variant="outline" onClick={() => ctx.onRestore(String(r.act))}>
              Restore
            </Button>
          ),
        },
      ],
    }),
  },
  {
    id: "daily",
    title: "Daily sales",
    group: "New reports (RP-07)",
    build: (sales) => ({
      rows: groupBy(live(sales), (s) => s.date).map(([d, ss]) => ({ k: d, date: d, n: ss.length, net: sum(ss, saleNet), vat: sum(ss, saleVat), total: sum(ss, saleTotalOf) })),
      cols: [text("date", "Date"), count("n", "Sales"), money("net", "Net"), money("vat", "VAT"), money("total", "Total")],
    }),
  },
  {
    id: "daily-items",
    title: "Daily item sales",
    group: "New reports (RP-07)",
    build: (sales) => ({
      rows: groupBy(itemsOf(sales), (x) => `${x.s.date}|${x.i.product}`).map(([k, xs]) => ({ k, date: xs[0].s.date, product: xs[0].i.product, qty: sum(xs, (x) => x.i.qty), value: sum(xs, (x) => x.i.qty * x.i.price) })),
      cols: [text("date", "Date"), text("product", "Product"), count("qty", "Units"), money("value", "Value, ex VAT")],
    }),
  },
  {
    id: "card",
    title: "Card payments",
    group: "New reports (RP-07)",
    build: (sales) => ({
      rows: live(sales).filter((s) => s.method === "Card").map((s) => ({ k: s.receipt, receipt: s.receipt, when: `${s.date} ${s.time}`, cashier: s.cashier, total: saleTotalOf(s) })),
      cols: [text("receipt", "Receipt"), text("when", "When"), text("cashier", "Cashier"), money("total", "Amount")],
    }),
  },
  {
    id: "mpesa",
    title: "M-Pesa payments",
    group: "New reports (RP-07)",
    build: (sales, stk) => ({
      rows: live(sales).filter((s) => s.method === "M-Pesa").map((s) => ({ k: s.receipt, receipt: s.receipt, when: `${s.date} ${s.time}`, ref: stk.find((a) => a.receipt === s.receipt && a.mpesaRef)?.mpesaRef ?? "Paybill", total: saleTotalOf(s) })),
      cols: [text("receipt", "Receipt"), text("when", "When"), text("ref", "M-Pesa code"), money("total", "Amount")],
    }),
  },
  {
    id: "stk",
    title: "M-Pesa STK log",
    group: "New reports (RP-07)",
    build: (_sales, stk) => ({
      note: "Every prompt sent, including those that failed. A timeout is checked with M-Pesa before any retry.",
      rows: stk.map((a) => ({ k: a.checkoutId, id: a.checkoutId, at: a.at, receipt: a.receipt, phone: a.phone, result: a.result, amount: a.amount })),
      cols: [text("id", "Checkout id"), text("at", "Sent"), text("receipt", "For sale"), text("phone", "Phone"), { key: "result", header: "Result", cell: (r) => <span className={cn(String(r.result).includes("not paid") || String(r.result).includes("Cancelled") ? "text-status-error-text" : "text-status-success-text")}>{r.result as string}</span> }, money("amount", "Amount", false)],
    }),
  },
  {
    id: "non-etims",
    title: "Non-eTIMS sales",
    group: "New reports (RP-07)",
    build: (sales) => ({
      note: "Sales not sent to KRA. Each needs a reason: before eTIMS was enabled, or KRA rejected it.",
      rows: live(sales).filter((s) => !s.etims).map((s) => ({ k: s.receipt, receipt: s.receipt, when: `${s.date} ${s.time}`, vat: saleVat(s), total: saleTotalOf(s) })),
      cols: [text("receipt", "Receipt"), text("when", "When"), money("vat", "VAT charged"), money("total", "Total")],
    }),
  },
  {
    id: "users",
    title: "Users' sales summary",
    group: "New reports (RP-07)",
    build: (sales) => ({
      rows: groupBy(live(sales), (s) => s.seller).map(([u, ss]) => ({ k: u, user: u, n: ss.length, avg: r2(sum(ss, saleTotalOf) / ss.length), total: sum(ss, saleTotalOf) })),
      cols: [text("user", "Sold by"), count("n", "Sales"), money("avg", "Average sale", false), money("total", "Total")],
    }),
  },
  {
    id: "sold-items",
    title: "Sold items",
    group: "New reports (RP-07)",
    build: (sales) => ({
      rows: itemsOf(sales).map(({ s, i }, n) => ({ k: `${s.receipt}-${n}`, receipt: s.receipt, product: i.product, qty: i.qty, price: i.price, value: r2(i.qty * i.price) })),
      cols: [text("receipt", "Receipt"), text("product", "Product"), count("qty", "Qty"), money("price", "Price", false), money("value", "Value, ex VAT")],
    }),
  },
  {
    id: "with-activity",
    title: "Sales with activity",
    group: "New reports (RP-07)",
    build: (sales) => ({
      note: "Sales that changed after the till: returned, part returned or turned into an invoice.",
      rows: live(sales).filter((s) => s.status !== "Completed" || s.invoice).map((s) => ({ k: s.receipt, receipt: s.receipt, what: s.invoice ? `Invoiced as ${s.invoice}` : s.status, total: saleTotalOf(s) })),
      cols: [text("receipt", "Receipt"), text("what", "What happened"), money("total", "Total", false)],
    }),
  },
  {
    id: "product",
    title: "Product sales",
    group: "New reports (RP-07)",
    build: (sales) => ({
      rows: groupBy(itemsOf(sales), (x) => x.i.category).map(([c, xs]) => ({ k: c, category: c, products: new Set(xs.map((x) => x.i.product)).size, qty: sum(xs, (x) => x.i.qty), value: sum(xs, (x) => x.i.qty * x.i.price) })),
      cols: [text("category", "Category"), count("products", "Products"), count("qty", "Units"), money("value", "Value, ex VAT")],
    }),
  },
  {
    id: "invoice",
    title: "Invoice sales",
    group: "New reports (RP-07)",
    build: (sales) => ({
      rows: live(sales).filter((s) => s.invoice).map((s) => ({ k: s.receipt, invoice: s.invoice ?? "", customer: s.customer ?? "", total: saleTotalOf(s) })),
      cols: [text("invoice", "Invoice"), text("customer", "Customer"), money("total", "Total")],
    }),
  },
];

/** RP-06 and RP-07: every sales report the PRD names, picked from one list, all built from the same records. */
export function SalesReportBook({ sales, stk, period, onRestore }: { sales: SaleRecord[]; stk: StkAttempt[]; period: string; onRestore: (receipt: string) => void }) {
  const [id, setId] = React.useState(SALES_REPORTS[0].id);
  const def = SALES_REPORTS.find((r) => r.id === id) ?? SALES_REPORTS[0];
  const built = def.build(sales, stk, { onRestore });
  return (
    <div className="grid gap-4 lg:grid-cols-[14rem_1fr]">
      <nav aria-label="Sales reports" className="space-y-3">
        {(["Existing reports (RP-06)", "New reports (RP-07)"] as const).map((g) => (
          <div key={g}>
            <p className="mb-1 text-[11px] font-semibold tracking-wide text-muted-foreground uppercase">{g}</p>
            <ul className="space-y-0.5">
              {SALES_REPORTS.filter((r) => r.group === g).map((r) => (
                <li key={r.id}>
                  <button type="button" aria-current={r.id === id ? "page" : undefined} onClick={() => setId(r.id)} className={cn("w-full cursor-pointer rounded-md px-2 py-1 text-left text-sm", r.id === id ? "bg-[var(--color-bg-selected)] font-medium" : "text-muted-foreground hover:bg-muted hover:text-foreground")}>
                    {r.title}
                  </button>
                </li>
              ))}
            </ul>
          </div>
        ))}
      </nav>
      <div className="min-w-0 space-y-2">
        <div>
          <h4 className="text-sm font-semibold">{def.title}</h4>
          <p className="text-xs text-muted-foreground">
            {period}
            {built.note ? ` · ${built.note}` : ""}
          </p>
        </div>
        <ListTable key={id} rows={built.rows} cols={built.cols} rowKey={(r) => String(r.k)} totalsLabel={built.cols.some((c) => c.total) ? (s) => `${s.length} rows` : undefined} empty="Nothing in this period." minWidth="min-w-[520px]" />
      </div>
    </div>
  );
}
