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 { logger } from '@/lib/logger';
import { renderReport, type ReportData } from '@/lib/pdf/reports/render-report';
import { resolveDashboardReportData } from '@/lib/services/dashboard-report-data.service';
import {
  resolveClientReportData,
  resolveBerthReportData,
  resolveInterestReportData,
} from '@/lib/services/list-report-data.service';
import { createAuditLog } from '@/lib/audit';

const dashboardConfigSchema = z.object({
  kind: z.literal('dashboard'),
  widgetIds: z.array(z.string()).min(1).max(40),
  dateFrom: z
    .string()
    .regex(/^\d{4}-\d{2}-\d{2}$/)
    .optional(),
  dateTo: z
    .string()
    .regex(/^\d{4}-\d{2}-\d{2}$/)
    .optional(),
});

const listFilters = z.object({ includeArchived: z.boolean().optional() }).passthrough().optional();

const clientsConfigSchema = z.object({
  kind: z.literal('clients'),
  columns: z.array(z.string()).max(20).optional(),
  filters: listFilters,
});

const berthsConfigSchema = z.object({
  kind: z.literal('berths'),
  columns: z.array(z.string()).max(20).optional(),
  filters: listFilters,
});

const interestsConfigSchema = z.object({
  kind: z.literal('interests'),
  columns: z.array(z.string()).max(20).optional(),
  filters: listFilters,
});

const requestSchema = z.object({
  title: z.string().min(1).max(200),
  subtitle: z.string().max(400).optional(),
  config: z.discriminatedUnion('kind', [
    dashboardConfigSchema,
    clientsConfigSchema,
    berthsConfigSchema,
    interestsConfigSchema,
  ]),
});

/**
 * POST /api/v1/reports/generate
 *
 * Renders a port-branded PDF report and streams it back to the
 * caller. Permission gating sits at the entry: `reports.export` (a
 * new permission added in seed-data; defaults to true for super-
 * admins and sales-managers, false for sales-agents). The route also
 * defends in depth via per-section service calls, each of which
 * reapplies the active port filter - a rep with reports.export but
 * no clients.view still gets a clients table in the PDF, since the
 * export is a snapshot of state the rep already has dashboard access
 * to. Defer narrower per-section gating to a follow-up once the
 * usage pattern is clearer.
 *
 * Audit trail: every successful export writes a `document` create
 * audit row with the report kind + widget ids in metadata. Useful
 * for "who exported what" investigations downstream.
 */
export const POST = withAuth(
  withPermission('reports', 'export', async (req, ctx) => {
    try {
      const body = await parseBody(req, requestSchema);

      const data: ReportData = {};
      switch (body.config.kind) {
        case 'dashboard':
          data.dashboard = await resolveDashboardReportData(ctx.portId, body.config.widgetIds, {
            dateFrom: body.config.dateFrom,
            dateTo: body.config.dateTo,
          });
          break;
        case 'clients':
          data.clients = await resolveClientReportData(ctx.portId, {
            includeArchived: Boolean(
              (body.config.filters as { includeArchived?: boolean } | undefined)?.includeArchived,
            ),
          });
          break;
        case 'berths':
          data.berths = await resolveBerthReportData(ctx.portId, {
            includeArchived: Boolean(
              (body.config.filters as { includeArchived?: boolean } | undefined)?.includeArchived,
            ),
          });
          break;
        case 'interests':
          data.interests = await resolveInterestReportData(ctx.portId, {
            includeArchived: Boolean(
              (body.config.filters as { includeArchived?: boolean } | undefined)?.includeArchived,
            ),
          });
          break;
        default: {
          const _exhaustive: never = body.config;
          throw new ValidationError(
            `Unsupported report kind: ${(_exhaustive as { kind: string }).kind}`,
          );
        }
      }

      const buffer = await renderReport({
        portId: ctx.portId,
        request: body,
        data,
      });

      // Audit BEFORE returning so a failed write doesn't silently
      // leak an export. The `void` is intentional - audit is best-
      // effort relative to the export; the PDF download succeeding
      // is the contract.
      void createAuditLog({
        portId: ctx.portId,
        userId: ctx.userId,
        action: 'create',
        entityType: 'report',
        entityId: 'pdf-export',
        metadata: {
          kind: body.config.kind,
          title: body.title,
          ...(body.config.kind === 'dashboard'
            ? {
                widgetIds: body.config.widgetIds,
                dateFrom: body.config.dateFrom,
                dateTo: body.config.dateTo,
              }
            : {}),
          sizeBytes: buffer.byteLength,
        },
        ipAddress: ctx.ipAddress,
        userAgent: ctx.userAgent,
      });

      const filename = sanitizeFilename(`${body.title}.pdf`);
      // Stream the buffer back inline. The Content-Disposition uses
      // `attachment; filename*` (RFC 5987) for unicode titles -
      // Port names with diacritics need this or browsers fall back
      // to a mojibake'd ASCII filename. `filename=` carries the
      // ASCII fallback for older HTTP stacks.
      // Node Buffer is not directly assignable to BodyInit in the
      // Next.js / Web Fetch typings; passing the underlying
      // ArrayBuffer view keeps the bytes intact without an extra copy.
      return new NextResponse(new Uint8Array(buffer), {
        status: 200,
        headers: {
          'Content-Type': 'application/pdf',
          'Content-Length': String(buffer.byteLength),
          'Content-Disposition': `attachment; filename="${filename.ascii}"; filename*=UTF-8''${filename.encoded}`,
          // No-cache: each export reflects the moment-in-time data,
          // so caching the response would leak stale numbers to
          // subsequent calls.
          'Cache-Control': 'no-store',
        },
      });
    } catch (error) {
      logger.warn({ err: error, portId: ctx.portId }, 'PDF report generation failed');
      return errorResponse(error);
    }
  }),
);

/**
 * Build a safe attachment filename. The ASCII variant strips
 * non-ASCII characters for the legacy `filename=` field; the
 * percent-encoded variant carries the full unicode title for the
 * RFC 5987 `filename*=` field. Replace `/` and `\\` so the file
 * doesn't accidentally look like a path component.
 */
function sanitizeFilename(name: string): { ascii: string; encoded: string } {
  const noPaths = name.replace(/[\\/]/g, '_');
  const ascii = noPaths.replace(/[^\x20-\x7e]/g, '_').replace(/"/g, "'");
  const encoded = encodeURIComponent(noPaths);
  return { ascii, encoded };
}
