import { useEffect, useState } from 'react'
import { formatINR, type BillLine, type BillTotals } from '@sims/domain'
import { renderInvoiceHtml, type InvoiceDoc } from '@sims/printing'
import { Badge, Button, DataTable, EmptyState, Field, Modal, Notice, PageHeader, StatCard, Stats, Toolbar } from '@sims/ui'
import { createItem, getAudit, getBills, getExpiry, getItems, getParties, getReturns, getSeller, getStock, type Seller } from '../api'

/** The first live pages — real data from the store-server (the slice). */

const inr = (p: unknown) => formatINR(Number(p), { symbol: false })

function useData<T>(loader: () => Promise<T>, deps: unknown[] = []): { data?: T; error?: string; reload: () => void } {
  const [data, setData] = useState<T | undefined>()
  const [error, setError] = useState<string | undefined>()
  const reload = () => {
    setError(undefined)
    loader().then(setData).catch((e: Error) => setError(e.message))
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(reload, deps)
  return { data, error, reload }
}

const PAGE = 100

/** Offset pagination with an explicit "Load more" — R13: a cap the operator can lift, never silent. */
function usePaged<T>(
  loader: (limit: number, offset: number) => Promise<T[]>,
  deps: unknown[],
): { rows: T[]; error?: string; hasMore: boolean; loading: boolean; loadMore: () => void } {
  const [rows, setRows] = useState<T[]>([])
  const [error, setError] = useState<string | undefined>()
  const [hasMore, setHasMore] = useState(false)
  const [loading, setLoading] = useState(false)
  const load = (offset: number) => {
    setLoading(true)
    setError(undefined)
    loader(PAGE, offset)
      .then((page) => {
        setRows((prev) => (offset === 0 ? page : [...prev, ...page]))
        setHasMore(page.length === PAGE)
      })
      .catch((e: Error) => setError(e.message))
      .finally(() => setLoading(false))
  }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(() => { setRows([]); load(0) }, deps)
  return { rows, error, hasMore, loading, loadMore: () => load(rows.length) }
}

export function ItemsPage() {
  const [q, setQ] = useState('')
  const { data, error, reload } = useData(() => getItems(q), [q])
  const [creating, setCreating] = useState(false)

  return (
    <div className="wf-page">
      <PageHeader
        title="Items" badge="LIVE"
        desc="The item master, straight from the store DB. What you add here is billable at the counter immediately."
        actions={<Button tone="primary" onClick={() => setCreating(true)}>New item</Button>}
      />
      <Toolbar>
        <input className="wf" style={{ maxWidth: 280 }} placeholder="Search name / code / barcode…" value={q} onChange={(e) => setQ(e.target.value)} />
        <Badge>{data?.length ?? '…'} items</Badge>
      </Toolbar>
      {error !== undefined && <Notice tone="err">{error}</Notice>}
      {data !== undefined && (
        <DataTable
          columns={[
            { key: 'code', label: 'Code' }, { key: 'name', label: 'Name' }, { key: 'hsn', label: 'HSN' },
            { key: 'tax', label: 'GST class' }, { key: 'mrp', label: 'MRP', numeric: true },
            { key: 'price', label: 'Price', numeric: true }, { key: 'status', label: 'Status' },
          ]}
          rows={data.map((i) => ({
            code: String(i['code']), name: String(i['name']), hsn: String(i['hsn']),
            tax: String(i['taxClassCode']),
            mrp: i['mrpPaise'] !== undefined ? inr(i['mrpPaise']) : '—',
            price: `${inr(i['salePricePaise'])}${(i['unit'] as { code: string }).code === 'KG' ? '/kg' : ''}`,
            status: <Badge tone={i['status'] === 'active' ? 'ok' : 'warn'}>{String(i['status'])}</Badge>,
          }))}
        />
      )}
      {creating && <NewItemModal onClose={() => setCreating(false)} onCreated={() => { setCreating(false); reload() }} />}
    </div>
  )
}

function NewItemModal(props: { onClose: () => void; onCreated: () => void }) {
  const [f, setF] = useState({ code: '', name: '', hsn: '', taxClassCode: 'GST18', unitCode: 'PCS', priceRs: '', mrpRs: '', barcode: '' })
  const [error, setError] = useState<string | undefined>()
  const set = (k: keyof typeof f) => (e: { target: { value: string } }) => setF((p) => ({ ...p, [k]: e.target.value }))

  const save = () => {
    createItem({
      code: f.code, name: f.name, hsn: f.hsn, taxClassCode: f.taxClassCode,
      unitCode: f.unitCode, unitDecimals: f.unitCode === 'KG' ? 3 : 0,
      salePricePaise: Math.round(Number(f.priceRs) * 100),
      ...(f.mrpRs !== '' ? { mrpPaise: Math.round(Number(f.mrpRs) * 100) } : {}),
      ...(f.barcode !== '' ? { barcode: f.barcode } : {}),
    }).then(props.onCreated).catch((e: Error) => setError(e.message))
  }

  return (
    <Modal title="New item" onClose={props.onClose}>
      <Field label="Code"><input className="wf" value={f.code} onChange={set('code')} /></Field>
      <Field label="Name"><input className="wf" value={f.name} onChange={set('name')} /></Field>
      <Field label="HSN"><input className="wf" value={f.hsn} onChange={set('hsn')} /></Field>
      <Field label="GST class">
        <select className="wf" value={f.taxClassCode} onChange={set('taxClassCode')}>
          {['ZERO', 'GST5', 'GST12', 'GST18', 'DEMERIT'].map((c) => <option key={c}>{c}</option>)}
        </select>
      </Field>
      <Field label="Unit">
        <select className="wf" value={f.unitCode} onChange={set('unitCode')}>
          <option>PCS</option><option>KG</option>
        </select>
      </Field>
      <Field label="Sale price (₹, tax-inclusive)"><input className="wf num" value={f.priceRs} onChange={set('priceRs')} /></Field>
      <Field label="MRP (₹, optional)"><input className="wf num" value={f.mrpRs} onChange={set('mrpRs')} /></Field>
      <Field label="Barcode (optional)"><input className="wf num" value={f.barcode} onChange={set('barcode')} /></Field>
      {error !== undefined && <Notice tone="err">{error}</Notice>}
      <div style={{ display: 'flex', gap: 8 }}>
        <Button tone="primary" onClick={save}>Save item</Button>
        <Button onClick={props.onClose}>Cancel</Button>
      </div>
    </Modal>
  )
}

export function CustomersPage() {
  const { data, error } = useData(() => getParties('customer'))
  return (
    <div className="wf-page">
      <PageHeader title="Customers" badge="LIVE" desc="Phone-keyed profiles — created here or from the POS customer-attach (F6)." />
      {error !== undefined && <Notice tone="err">{error}</Notice>}
      {data === undefined ? <EmptyState>Loading…</EmptyState> : (
        <DataTable
          columns={[
            { key: 'code', label: 'Code' }, { key: 'name', label: 'Name' },
            { key: 'phone', label: 'Phone' }, { key: 'gstin', label: 'GSTIN' },
            { key: 'limit', label: 'Credit limit', numeric: true },
          ]}
          rows={data.map((p) => ({
            code: String(p['code']), name: String(p['name']),
            phone: p['phone'] !== undefined ? String(p['phone']) : '—',
            gstin: p['gstin'] !== undefined ? <Badge tone="accent">{String(p['gstin'])}</Badge> : '—',
            limit: p['creditLimitPaise'] !== undefined ? inr(p['creditLimitPaise']) : '—',
          }))}
        />
      )}
    </div>
  )
}

/** Build the A4 GST invoice for a bill row and open it in a print-ready window. */
function openInvoice(bill: Record<string, unknown>, seller: Seller | undefined): string | undefined {
  let payload: { lines: BillLine[]; totals: BillTotals; payments?: { mode: string }[] }
  try {
    payload = JSON.parse(String(bill['payload'])) as typeof payload
  } catch {
    return 'This bill has no stored line detail to invoice.'
  }
  // The B2B buyer + place of supply are snapshotted on the bill at commit (S3),
  // so the invoice reads them from the document, not from today's customer row.
  const buyerGstin = bill['buyer_gstin'] !== null && bill['buyer_gstin'] !== undefined ? String(bill['buyer_gstin']) : undefined
  const placeOfSupply = bill['place_of_supply'] !== null && bill['place_of_supply'] !== undefined ? String(bill['place_of_supply']) : undefined
  const posState = placeOfSupply ?? seller?.stateCode
  const doc: InvoiceDoc = {
    seller: {
      name: seller?.name ?? 'Store',
      ...(seller?.gstin !== undefined ? { gstin: seller.gstin } : {}),
      ...(seller?.stateCode !== undefined ? { stateCode: seller.stateCode } : {}),
    },
    ...(bill['customer_name'] !== null && bill['customer_name'] !== undefined
      ? {
        buyer: {
          name: String(bill['customer_name']),
          ...(buyerGstin !== undefined ? { gstin: buyerGstin } : {}),
          ...(placeOfSupply !== undefined ? { stateCode: placeOfSupply } : {}),
        },
      }
      : {}),
    invoiceNo: String(bill['doc_no']),
    invoiceDate: String(bill['business_date']),
    ...(posState !== undefined ? { placeOfSupplyStateCode: posState } : {}),
    lines: payload.lines,
    totals: payload.totals,
    ...(payload.payments?.[0]?.mode !== undefined ? { paymentLabel: payload.payments[0].mode } : {}),
  }
  // Serve the self-contained HTML as a blob URL — no document.write, and the new
  // window loads it as a real document (so its own Print A4 button works).
  const url = URL.createObjectURL(new Blob([renderInvoiceHtml(doc)], { type: 'text/html' }))
  const win = window.open(url, '_blank')
  if (win === null) { URL.revokeObjectURL(url); return 'Allow pop-ups to open the invoice (it prints in a new tab).' }
  setTimeout(() => URL.revokeObjectURL(url), 60_000)
  return undefined
}

export function BillsPage() {
  const [date, setDate] = useState(new Date().toISOString().slice(0, 10))
  const { rows, error, hasMore, loading, loadMore } = usePaged<Record<string, unknown>>(
    (limit, offset) => getBills(date, limit, offset), [date],
  )
  const { data: seller } = useData(() => getSeller())
  const [printErr, setPrintErr] = useState<string | undefined>()
  const total = rows.reduce((a, b) => a + Number(b['payable_paise']), 0)
  return (
    <div className="wf-page">
      <PageHeader title="Bills" badge="LIVE" desc="Every committed bill — immutable documents from the counters. Click a row to open its A4 GST invoice." />
      <Toolbar>
        <input type="date" className="wf" style={{ maxWidth: 170 }} value={date} onChange={(e) => setDate(e.target.value)} />
        <Badge>{rows.length}{hasMore ? '+' : ''} bills</Badge>
        <Badge tone="accent">{formatINR(total)}</Badge>
      </Toolbar>
      {error !== undefined && <Notice tone="err">{error}</Notice>}
      {printErr !== undefined && <Notice tone="warn">{printErr}</Notice>}
      {rows.length === 0 && loading ? <EmptyState>Loading…</EmptyState>
        : rows.length === 0 && error === undefined ? <EmptyState>No bills on {date}. Make one at the POS!</EmptyState> : (
        <DataTable
          columns={[
            { key: 'no', label: 'Bill No' }, { key: 'time', label: 'Time' }, { key: 'cashier', label: 'Cashier' },
            { key: 'customer', label: 'Customer' }, { key: 'gst', label: 'GST', numeric: true },
            { key: 'amount', label: 'Amount', numeric: true }, { key: 'invoice', label: 'Invoice' },
          ]}
          onRowClick={(_row, i) => setPrintErr(openInvoice(rows[i]!, seller))}
          rows={rows.map((b) => ({
            no: String(b['doc_no']),
            time: new Date(String(b['created_at_wall'])).toLocaleTimeString(),
            cashier: String(b['cashier_name'] ?? ''),
            customer: b['customer_name'] !== null ? String(b['customer_name']) : 'walk-in',
            gst: inr(Number(b['cgst_paise']) + Number(b['sgst_paise']) + Number(b['igst_paise']) + Number(b['cess_paise'])),
            amount: inr(b['payable_paise']),
            invoice: <Badge tone="accent">Print A4 ↗</Badge>,
          }))}
        />
      )}
      {hasMore && (
        <div style={{ marginTop: 10 }}>
          <Button onClick={loadMore}>{loading ? 'Loading…' : 'Load more'}</Button>
        </div>
      )}
    </div>
  )
}

/**
 * Sales → Returns & Credit Notes — LIVE. Every SALE_RETURN document, tenant-scoped and
 * paginated (R13). A credit note references its original bill (Against Bill) and reverses tax
 * at the original bill-date rates; the refund is the immutable document's payable. Replaces the
 * registry wireframe via the CUSTOM map.
 */
export function ReturnsPage() {
  const { rows, error, hasMore, loading, loadMore } = usePaged<Record<string, unknown>>(
    (limit, offset) => getReturns(limit, offset), [],
  )
  const total = rows.reduce((a, b) => a + Number(b['payable_paise']), 0)
  const reasonOf = (b: Record<string, unknown>): string => {
    try { return String((JSON.parse(String(b['payload'])) as { reason?: string }).reason ?? '') } catch { return '' }
  }
  return (
    <div className="wf-page">
      <PageHeader
        title="Returns & Credit Notes" badge="LIVE"
        desc="Each credit note reverses an original bill — taxes reverse at the original bill-date rates. Immutable documents from the counters (POS F9)."
      />
      <Toolbar>
        <Badge>{rows.length}{hasMore ? '+' : ''} credit notes</Badge>
        <Badge tone="accent">{formatINR(total)} refunded</Badge>
      </Toolbar>
      {error !== undefined && <Notice tone="err">{error}</Notice>}
      {rows.length === 0 && loading ? <EmptyState>Loading…</EmptyState>
        : rows.length === 0 && error === undefined ? <EmptyState>No credit notes yet — process a return at the POS (F9).</EmptyState> : (
        <DataTable
          columns={[
            { key: 'no', label: 'Credit Note' }, { key: 'against', label: 'Against Bill' },
            { key: 'date', label: 'Date' }, { key: 'cashier', label: 'Cashier' },
            { key: 'customer', label: 'Customer' }, { key: 'reason', label: 'Reason' },
            { key: 'amount', label: 'Refund', numeric: true },
          ]}
          rows={rows.map((b) => ({
            no: String(b['doc_no']),
            against: b['original_doc_no'] !== null && b['original_doc_no'] !== undefined ? String(b['original_doc_no']) : '—',
            date: new Date(String(b['created_at_wall'])).toLocaleString(),
            cashier: String(b['cashier_name'] ?? ''),
            customer: b['customer_name'] !== null && b['customer_name'] !== undefined ? String(b['customer_name']) : 'walk-in',
            reason: reasonOf(b),
            amount: inr(b['payable_paise']),
          }))}
        />
      )}
      {hasMore && (
        <div style={{ marginTop: 10 }}>
          <Button onClick={loadMore}>{loading ? 'Loading…' : 'Load more'}</Button>
        </div>
      )}
    </div>
  )
}

export function StockPage() {
  const { data, error } = useData(() => getStock())
  // S5: the per-batch breakdown for batch-tracked items (a second table, kept simple).
  const { data: batches } = useData(() => getExpiry())
  const today = new Date().toISOString().slice(0, 10)
  return (
    <div className="wf-page">
      <PageHeader title="Stock" badge="LIVE" desc="On-hand derived from movements (invariant I4) — opening stock minus every sale. Batch-tracked items break down by lot below." />
      {error !== undefined && <Notice tone="err">{error}</Notice>}
      {data === undefined ? <EmptyState>Loading…</EmptyState> : (
        <DataTable
          columns={[
            { key: 'code', label: 'Code' }, { key: 'name', label: 'Item' },
            { key: 'onhand', label: 'On hand', numeric: true }, { key: 'tracked', label: 'Tracking' },
            { key: 'state', label: '' },
          ]}
          rows={data.map((s) => {
            const onHand = Number(s['on_hand'])
            return {
              code: String(s['code']), name: String(s['name']),
              onhand: `${Math.round(onHand * 1000) / 1000} ${String(s['unit_code'])}`,
              tracked: Number(s['batch_tracked']) === 1 ? <Badge tone="accent">batch</Badge> : '—',
              state: onHand <= 0 ? <Badge tone="err">out</Badge> : onHand < 15 ? <Badge tone="warn">low</Badge> : <Badge tone="ok">ok</Badge>,
            }
          })}
        />
      )}
      {batches !== undefined && batches.length > 0 && (
        <>
          <h3 style={{ margin: '18px 0 8px' }}>Batch-tracked stock</h3>
          <DataTable
            columns={[
              { key: 'item', label: 'Item' }, { key: 'batch', label: 'Batch' },
              { key: 'expiry', label: 'Expiry' }, { key: 'onhand', label: 'On hand', numeric: true },
              { key: 'value', label: 'Value @ sale', numeric: true },
            ]}
            rows={batches.map((b) => {
              const onHand = Number(b['on_hand'])
              const expiry = b['expiry_date'] !== null ? String(b['expiry_date']) : undefined
              const expired = expiry !== undefined && expiry < today
              return {
                item: String(b['name']),
                batch: String(b['batch_no']),
                expiry: expiry === undefined ? <Badge>no expiry</Badge>
                  : expired ? <Badge tone="err">{expiry}</Badge> : expiry,
                onhand: `${Math.round(onHand * 1000) / 1000} ${String(b['unit_code'])}`,
                value: inr(onHand * Number(b['sale_price_paise'])),
              }
            })}
          />
        </>
      )}
    </div>
  )
}

export function AuditPage() {
  const { rows, error, hasMore, loading, loadMore } = usePaged<Record<string, unknown>>(
    (limit, offset) => getAudit(limit, offset), [],
  )
  return (
    <div className="wf-page">
      <PageHeader title="Audit Log" badge="LIVE" desc="Append-only — logins, failed logins, item edits, every bill commit." />
      {error !== undefined && <Notice tone="err">{error}</Notice>}
      {rows.length === 0 && loading ? <EmptyState>Loading…</EmptyState>
        : rows.length === 0 && error === undefined ? <EmptyState>No audit entries yet.</EmptyState> : (
        <DataTable
          columns={[
            { key: 'at', label: 'When' }, { key: 'who', label: 'Who' },
            { key: 'action', label: 'Action' }, { key: 'detail', label: 'Detail' },
          ]}
          rows={rows.map((a) => ({
            at: new Date(String(a['at_wall'])).toLocaleString(),
            who: String(a['user_name'] ?? a['user_id']),
            action: <Badge tone="accent">{String(a['action'])}</Badge>,
            // Override rows carry a before_json (who approved what change from what) —
            // show before → after so PRICE/QTY/DISCOUNT overrides read at a glance.
            detail: `${String(a['entity'])} ${a['before_json'] !== null ? `${String(a['before_json']).slice(0, 60)} → ` : ''}${a['after_json'] !== null ? String(a['after_json']).slice(0, 90) : ''}`,
          }))}
        />
      )}
      {hasMore && (
        <div style={{ marginTop: 10 }}>
          <Button onClick={loadMore}>{loading ? 'Loading…' : 'Load more'}</Button>
        </div>
      )}
    </div>
  )
}

/**
 * S5 Expiry Dashboard — live. Every in-stock batch, bucketed by the working date:
 * expired-with-stock, expiring ≤7 days, ≤30 days. Per-batch on-hand is derived from
 * movements (R7); value is on-hand × the item's current sale price. Replaces the
 * registry wireframe via the CUSTOM map.
 */
interface ExpiryRow { id: string; code: string; name: string; batchNo: string; expiry?: string; onHand: number; unit: string; value: number }

function bucketTable(title: string, tone: 'err' | 'warn' | undefined, rows: ExpiryRow[]) {
  if (rows.length === 0) return null
  return (
    <>
      <h3 style={{ margin: '18px 0 8px', color: tone !== undefined ? `var(--${tone})` : undefined }}>{title}</h3>
      <DataTable
        columns={[
          { key: 'item', label: 'Item' }, { key: 'batch', label: 'Batch' },
          { key: 'expiry', label: 'Expiry' }, { key: 'qty', label: 'On hand', numeric: true },
          { key: 'value', label: 'Value @ sale', numeric: true },
        ]}
        rows={rows.map((r) => ({
          item: r.name, batch: r.batchNo,
          expiry: r.expiry === undefined ? '—'
            : tone === 'err' ? <Badge tone="err">{r.expiry}</Badge>
            : tone === 'warn' ? <Badge tone="warn">{r.expiry}</Badge>
            : r.expiry,
          qty: `${Math.round(r.onHand * 1000) / 1000} ${r.unit}`,
          value: inr(r.value),
        }))}
      />
    </>
  )
}

export function ExpiryPage() {
  const { data, error } = useData(() => getExpiry())
  const today = new Date().toISOString().slice(0, 10)
  const plusDays = (n: number): string => {
    const d = new Date()
    d.setDate(d.getDate() + n)
    return d.toISOString().slice(0, 10)
  }
  const in7 = plusDays(7)
  const in30 = plusDays(30)

  const rows: ExpiryRow[] = (data ?? []).map((r) => ({
    id: String(r['id']), code: String(r['code']), name: String(r['name']),
    batchNo: String(r['batch_no']),
    expiry: r['expiry_date'] !== null && r['expiry_date'] !== undefined ? String(r['expiry_date']) : undefined,
    onHand: Number(r['on_hand']), unit: String(r['unit_code']),
    value: Number(r['on_hand']) * Number(r['sale_price_paise']),
  }))
  const dated = rows.filter((r) => r.expiry !== undefined)
  const expired = dated.filter((r) => r.expiry! < today)
  const le7 = dated.filter((r) => r.expiry! >= today && r.expiry! <= in7)
  const le30 = dated.filter((r) => r.expiry! > in7 && r.expiry! <= in30)
  const far = rows.filter((r) => r.expiry === undefined || r.expiry > in30)
  const sum = (list: ExpiryRow[]): number => list.reduce((a, r) => a + r.value, 0)

  return (
    <div className="wf-page">
      <PageHeader title="Expiry Dashboard" badge="LIVE" desc="Near-expiry and expired stock by batch — act before it becomes wastage. Value is on-hand × current sale price." />
      {error !== undefined && <Notice tone="err">{error}</Notice>}
      {data === undefined ? <EmptyState>Loading…</EmptyState> : rows.length === 0 ? (
        <EmptyState>No batch-tracked stock yet — receive a batch in Purchase Entry.</EmptyState>
      ) : (
        <>
          <Stats>
            <StatCard label="Expiring ≤ 7 days" value={formatINR(sum(le7))} hint={`${le7.length} batch${le7.length === 1 ? '' : 'es'}`} />
            <StatCard label="Expiring ≤ 30 days" value={formatINR(sum(le30))} hint={`${le30.length} batch${le30.length === 1 ? '' : 'es'}`} />
            <StatCard label="Expired — with stock" value={formatINR(sum(expired))} hint={`${expired.length} batch${expired.length === 1 ? '' : 'es'}`} />
          </Stats>
          {bucketTable('Expired — clear or write off', 'err', expired)}
          {bucketTable('Expiring within 7 days', 'warn', le7)}
          {bucketTable('Expiring within 30 days', undefined, le30)}
          <div style={{ marginTop: 18 }}>
            <Notice tone="warn">
              Dead-stock: {far.length} batch{far.length === 1 ? '' : 'es'} carry {formatINR(sum(far))} of stock dated beyond 30 days.
              Slow-mover ranking by sales velocity is a follow-up — this view flags by expiry today.
            </Notice>
          </div>
        </>
      )}
    </div>
  )
}
