import { NextRequest, NextResponse } from 'next/server';

import { withAuth, withPermission } from '@/lib/api/helpers';
import { errorResponse, ValidationError } from '@/lib/errors';
import {
  ALL_RANGES,
  getLeadSourceAttribution,
  getOccupancyTimeline,
  getPipelineFunnel,
  type DateRange,
  type MetricBase,
  type PresetDateRange,
} from '@/lib/services/analytics.service';

const METRICS: Record<MetricBase, (portId: string, range: DateRange) => Promise<unknown>> = {
  pipeline_funnel: getPipelineFunnel,
  occupancy_timeline: getOccupancyTimeline,
  lead_source_attribution: getLeadSourceAttribution,
};

const ISO_DATE_RX = /^\d{4}-\d{2}-\d{2}$/;

export const GET = withAuth(
  withPermission('reports', 'view_analytics', async (req: NextRequest, ctx) => {
    try {
      const url = new URL(req.url);
      const metric = url.searchParams.get('metric') as MetricBase | null;
      const rawRange = url.searchParams.get('range') ?? '30d';
      const fromParam = url.searchParams.get('from');
      const toParam = url.searchParams.get('to');

      if (!metric || !(metric in METRICS)) {
        throw new ValidationError('Invalid or missing metric');
      }

      let range: DateRange;
      if (rawRange === 'custom') {
        if (!fromParam || !toParam) {
          throw new ValidationError('Custom range requires `from` and `to` (YYYY-MM-DD)');
        }
        if (!ISO_DATE_RX.test(fromParam) || !ISO_DATE_RX.test(toParam)) {
          throw new ValidationError('`from`/`to` must be ISO date strings (YYYY-MM-DD)');
        }
        if (fromParam > toParam) {
          throw new ValidationError('`from` must be on or before `to`');
        }
        // Round-trip date check: regex passes "9999-13-99" or "2026-02-31"
        // (rolls over silently when handed to `new Date`). Re-serialize and
        // confirm it matches the input to catch invalid calendar values.
        for (const [label, raw] of [
          ['from', fromParam],
          ['to', toParam],
        ] as const) {
          const d = new Date(`${raw}T00:00:00.000Z`);
          if (Number.isNaN(d.getTime()) || d.toISOString().slice(0, 10) !== raw) {
            throw new ValidationError(`\`${label}\` is not a valid calendar date`);
          }
        }
        // Backstop against the occupancy-timeline N+1 query loop. Each day
        // in the range issues its own DB query, so a multi-year custom
        // range would saturate the connection pool. 365 days is a generous
        // ceiling for analytical queries; if a longer span is needed, the
        // service should be restructured to use `generate_series` instead
        // of a JS loop.
        const fromMs = new Date(`${fromParam}T00:00:00.000Z`).getTime();
        const toMs = new Date(`${toParam}T23:59:59.999Z`).getTime();
        if ((toMs - fromMs) / 86_400_000 > 365) {
          throw new ValidationError('Custom range cannot exceed 365 days');
        }
        range = { kind: 'custom', from: fromParam, to: toParam };
      } else {
        if (!ALL_RANGES.includes(rawRange as PresetDateRange)) {
          throw new ValidationError('Invalid range');
        }
        range = rawRange as PresetDateRange;
      }

      const data = await METRICS[metric](ctx.portId, range);
      return NextResponse.json({ metric, range, data });
    } catch (error) {
      return errorResponse(error);
    }
  }),
);
