import { NextResponse } from 'next/server';
import { z } from 'zod';

import { withAuth, withPermission } from '@/lib/api/helpers';
import { parseBody } from '@/lib/api/route-helpers';
import { errorResponse, ValidationError } from '@/lib/errors';
import { ENTITY_KEYS, ENTITY_REGISTRY, type EntityKey } from '@/lib/reports/custom/registry';

/**
 * POST /api/v1/reports/custom/run
 *
 * Executes a custom-report query and returns raw rows. The UI calls
 * this with the entity + selected columns + optional date range; the
 * service resolves the column allowlist and runs the underlying
 * Drizzle query.
 *
 * Permission: `reports.export` — same gate as the saved-template
 * endpoints (anyone who can export reports can run a custom slice).
 *
 * The handler returns JSON `{ data: rows[] }` rather than streaming
 * CSV — the client serializes via the existing CSV exporter so all
 * download formats (CSV/XLSX/PDF) reuse one code path.
 */
const bodySchema = z.object({
  entity: z.enum(ENTITY_KEYS),
  columns: z.array(z.string().min(1)).min(1).max(50),
  from: z.string().datetime().optional(),
  to: z.string().datetime().optional(),
});

export const POST = withAuth(
  withPermission('reports', 'export', async (req, ctx) => {
    try {
      const body = await parseBody(req, bodySchema);
      const def = ENTITY_REGISTRY[body.entity as EntityKey];

      // Cross-validate columns against the registry's allowlist.
      const allowedKeys = new Set(def.columns.map((c) => c.key));
      const requested = body.columns.filter((k) => allowedKeys.has(k));
      if (requested.length === 0) {
        throw new ValidationError(`No valid columns selected for entity "${body.entity}"`);
      }

      const filter = {
        from: body.from ? new Date(body.from) : undefined,
        to: body.to ? new Date(body.to) : undefined,
      };

      const rows = await def.runQuery({
        portId: ctx.portId,
        columns: requested,
        filter,
      });

      // Money columns travel with a hidden sibling currency column so the
      // client formatter can render `€1,234,567` instead of bare numbers
      // even when the user didn't tick the currency column for display.
      // The sibling is stripped from the meta-column list below (so the
      // table doesn't render it twice) but survives in the row payload.
      const MONEY_SIBLINGS: Record<string, string> = {
        price: 'priceCurrency',
        depositExpectedAmount: 'depositExpectedCurrency',
      };
      const siblingsToAttach = new Set<string>();
      for (const k of requested) {
        const sib = MONEY_SIBLINGS[k];
        if (sib && allowedKeys.has(sib)) siblingsToAttach.add(sib);
      }
      const projectionKeys = [...requested, ...siblingsToAttach].filter(
        (k, idx, arr) => arr.indexOf(k) === idx,
      );

      const projected = rows.map((row) => {
        const out: Record<string, unknown> = {};
        for (const k of projectionKeys) out[k] = row[k];
        return out;
      });

      return NextResponse.json({
        data: projected,
        meta: {
          entity: body.entity,
          columns: requested.map((k) => ({
            key: k,
            label: def.columns.find((c) => c.key === k)?.label ?? k,
          })),
          rowCount: projected.length,
        },
      });
    } catch (error) {
      return errorResponse(error);
    }
  }),
);
