/**
 * GET /api/qbo/diagnose-ar
 *   One-off reconciliation diagnostic — pulls QBO's Aged Receivable Detail
 *   report live and diffs it against Pulse's qbo_invoices to identify the
 *   gap between Pulse's headline A/R and what QBO reports.
 *
 *   Returns per-invoice mismatches in three buckets:
 *     - in_qbo_not_in_pulse  — QBO knows about it, Pulse doesn't (sync miss)
 *     - in_pulse_not_in_qbo  — Pulse has live A/R for it, QBO doesn't
 *                              (likely a tombstone candidate)
 *     - balance_diff         — both have it but the balance differs
 */

import { NextResponse } from 'next/server';
import { requireAdmin } from '@/lib/auth-utils';
import postgresClient from '@/lib/services/postgres-client';
import { QboClient } from '@/lib/services/qbo-client';

// QBO report rows are deeply nested — walk them and yield every leaf "Data"
// row.  Each leaf carries an array of column values matching the report's
// column header (Customer | Date | Transaction Type | Num | Due Date | Aging |
// Open Balance, etc.).
function* walkRows(node: unknown): Generator<{ values: string[]; group?: string }> {
  if (!node || typeof node !== 'object') return;
  const n = node as Record<string, unknown>;
  if (Array.isArray(n.Row)) {
    for (const child of n.Row as unknown[]) yield* walkRows(child);
    return;
  }
  if (n.Rows) {
    yield* walkRows(n.Rows);
    return;
  }
  if (n.type === 'Data' && n.ColData && Array.isArray(n.ColData)) {
    const values = (n.ColData as Array<{ value?: string }>).map((c) => c?.value ?? '');
    yield { values, group: typeof n.group === 'string' ? n.group : undefined };
  }
  if (n.Header || n.Summary) {
    // Section node — recurse into its rows
    if (n.Rows) yield* walkRows(n.Rows);
  }
}

export async function GET() {
  const { error } = await requireAdmin();
  if (error) return error;
  const client = new QboClient();
  const report = await client.getAgedReceivableDetail();

  // Map columns by ColTitle so we don't rely on positional order
  const cols: Array<{ ColTitle?: string; ColType?: string }> =
    (report.Columns?.Column ?? []) as Array<{ ColTitle?: string; ColType?: string }>;
  const idx = (title: string) => cols.findIndex((c) => (c.ColTitle ?? '').toLowerCase() === title.toLowerCase());

  const iNum     = idx('Num');
  const iCust    = idx('Customer');
  const iBalance = idx('Open Balance');
  const iType    = idx('Transaction Type');
  const iAging   = idx('Aging');

  type QboLine = {
    docNumber: string;
    customer: string;
    type: string;
    aging: string;
    balance: number;
  };
  const qboInvoices: QboLine[] = [];
  let qboTotal = 0;
  for (const row of walkRows(report.Rows)) {
    const v = row.values;
    if (!v.length) continue;
    const balanceStr = iBalance >= 0 ? v[iBalance] : '';
    const balance = balanceStr ? parseFloat(balanceStr) : 0;
    const docNumber = iNum >= 0 ? v[iNum] : '';
    const customer = iCust >= 0 ? v[iCust] : '';
    const type = iType >= 0 ? v[iType] : '';
    const aging = iAging >= 0 ? v[iAging] : '';
    // Skip total/summary rows (no doc number, no customer)
    if (!docNumber && !customer) continue;
    qboInvoices.push({ docNumber, customer, type, aging, balance });
    qboTotal += balance;
  }

  const pulseRes = await postgresClient.query<{
    id: string;
    doc_number: string | null;
    customer_ref_name: string | null;
    balance: string;
    status: string;
  }>(
    `SELECT id, doc_number, customer_ref_name, balance::text, status
       FROM qbo_invoices
      WHERE is_deleted = false
        AND status IN ('Open','Overdue')
        AND balance <> 0`,
  );
  const pulseByDoc = new Map<string, { id: string; customer: string; balance: number; status: string }>();
  let pulseTotal = 0;
  for (const r of pulseRes.rows) {
    const balance = parseFloat(r.balance);
    pulseTotal += balance;
    if (r.doc_number) {
      pulseByDoc.set(r.doc_number, {
        id: r.id,
        customer: r.customer_ref_name ?? '',
        balance,
        status: r.status,
      });
    }
  }

  // Diff
  const inQboNotInPulse: QboLine[] = [];
  const balanceDiff: Array<{
    docNumber: string;
    customer: string;
    qboBalance: number;
    pulseBalance: number;
    delta: number;
  }> = [];
  const seenDocs = new Set<string>();
  for (const inv of qboInvoices) {
    if (!inv.docNumber) continue;
    seenDocs.add(inv.docNumber);
    const pulse = pulseByDoc.get(inv.docNumber);
    if (!pulse) {
      inQboNotInPulse.push(inv);
      continue;
    }
    const delta = Math.round((inv.balance - pulse.balance) * 100) / 100;
    if (Math.abs(delta) > 0.005) {
      balanceDiff.push({
        docNumber: inv.docNumber,
        customer: inv.customer || pulse.customer,
        qboBalance: inv.balance,
        pulseBalance: pulse.balance,
        delta,
      });
    }
  }
  const inPulseNotInQbo: Array<{
    docNumber: string;
    customer: string;
    balance: number;
    status: string;
  }> = [];
  for (const [docNumber, p] of pulseByDoc) {
    if (!seenDocs.has(docNumber)) {
      inPulseNotInQbo.push({
        docNumber,
        customer: p.customer,
        balance: p.balance,
        status: p.status,
      });
    }
  }

  const sumOf = <T extends { balance?: number; delta?: number }>(arr: T[], key: 'balance' | 'delta') =>
    Math.round(arr.reduce((s, x) => s + (x[key] ?? 0), 0) * 100) / 100;

  return NextResponse.json({
    summary: {
      qbo_aging_total:     Math.round(qboTotal * 100) / 100,
      pulse_gross_ar:      Math.round(pulseTotal * 100) / 100,
      qbo_invoice_count:   qboInvoices.length,
      pulse_invoice_count: pulseRes.rows.length,
      gap:                 Math.round((pulseTotal - qboTotal) * 100) / 100,
    },
    differences: {
      in_qbo_not_in_pulse:   { count: inQboNotInPulse.length, sum: sumOf(inQboNotInPulse, 'balance'), rows: inQboNotInPulse },
      in_pulse_not_in_qbo:   { count: inPulseNotInQbo.length, sum: sumOf(inPulseNotInQbo, 'balance'), rows: inPulseNotInQbo },
      balance_diff:          { count: balanceDiff.length,     sum: sumOf(balanceDiff, 'delta'),       rows: balanceDiff },
    },
    report_meta: {
      report_name:  report.Header?.ReportName,
      end_period:   report.Header?.EndPeriod,
      time:         report.Header?.Time,
      report_basis: report.Header?.ReportBasis,
    },
  });
}
