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

import { withAuth, withPermission } from '@/lib/api/helpers';
import { ALL_RANGES, type DateRange, type PresetDateRange } from '@/lib/analytics/range';
import { CodedError, errorResponse, ValidationError } from '@/lib/errors';
import {
  getActiveVisitors,
  getMetric,
  getPageviewsSeries,
  getRealtime,
  getSession,
  getSessionActivity,
  getSessions,
  getSessionsWeekly,
  getStats,
  getWebsiteInfo,
  type UmamiMetricType,
} from '@/lib/services/umami.service';

/**
 * GET /api/v1/website-analytics?metric=...&range=...
 *
 * Single endpoint serving every Umami widget on the /website-analytics
 * page. Mirrors the shape of /api/v1/analytics so the client side can
 * reuse the same hook pattern.
 *
 * Supported metrics:
 *   - stats              → KPI tiles (pageviews, visitors, visits, etc.)
 *   - pageviews          → time-series for the trend chart
 *   - active             → live "right now" count (range ignored)
 *   - top-{type}         → top pages/referrers/countries/etc.
 *                          where type ∈ url|referrer|country|browser|
 *                          os|device|event
 *
 * Range param accepts the same presets as /api/v1/analytics, plus
 * `range=custom&from=YYYY-MM-DD&to=YYYY-MM-DD`.
 */

const ISO_DATE_RX = /^\d{4}-\d{2}-\d{2}$/;
// Umami v2/v3 metric `type` values surfaced by the CRM. `path` is the
// current name for what older versions called `url` - accept both as
// inbound metric names (old clients won't break) but `path` is what the
// service forwards to Umami.
const TOP_METRIC_RX = /^top-(path|url|referrer|country|browser|os|device|event)$/;

function parseRange(req: NextRequest): DateRange | { error: string } {
  const url = new URL(req.url);
  const rawRange = url.searchParams.get('range') ?? '30d';
  const fromParam = url.searchParams.get('from');
  const toParam = url.searchParams.get('to');

  if (rawRange === 'custom') {
    if (!fromParam || !toParam) {
      return { error: 'Custom range requires `from` and `to` (YYYY-MM-DD)' };
    }
    if (!ISO_DATE_RX.test(fromParam) || !ISO_DATE_RX.test(toParam)) {
      return { error: '`from`/`to` must be ISO date strings (YYYY-MM-DD)' };
    }
    if (fromParam > toParam) {
      return { error: '`from` must be on or before `to`' };
    }
    // Round-trip date check (catches "2026-02-31" type rollovers).
    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) {
        return { error: `\`${label}\` is not a valid calendar date` };
      }
    }
    return { kind: 'custom', from: fromParam, to: toParam };
  }
  if (!ALL_RANGES.includes(rawRange as PresetDateRange)) {
    return { error: 'Invalid range' };
  }
  return rawRange as PresetDateRange;
}

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');
      if (!metric) throw new ValidationError('Missing metric');

      const rangeOrError = parseRange(req);
      if (typeof rangeOrError === 'object' && 'error' in rangeOrError) {
        throw new ValidationError(rangeOrError.error);
      }
      const range = rangeOrError as DateRange;

      let data: unknown;
      try {
        if (metric === 'stats') {
          data = await getStats(ctx.portId, range);
        } else if (metric === 'pageviews') {
          data = await getPageviewsSeries(ctx.portId, range);
        } else if (metric === 'active') {
          data = await getActiveVisitors(ctx.portId);
        } else if (metric === 'realtime') {
          data = await getRealtime(ctx.portId);
        } else if (metric === 'website') {
          data = await getWebsiteInfo(ctx.portId);
        } else if (metric === 'sessions') {
          const page = Number(url.searchParams.get('page') ?? 1);
          const pageSize = Number(url.searchParams.get('pageSize') ?? 25);
          const query = url.searchParams.get('query') ?? undefined;
          data = await getSessions(ctx.portId, range, { page, pageSize, query });
        } else if (metric === 'session') {
          const sessionId = url.searchParams.get('sessionId');
          if (!sessionId) throw new ValidationError('Missing sessionId');
          data = await getSession(ctx.portId, sessionId);
        } else if (metric === 'session-activity') {
          const sessionId = url.searchParams.get('sessionId');
          if (!sessionId) throw new ValidationError('Missing sessionId');
          data = await getSessionActivity(ctx.portId, sessionId, range);
        } else if (metric === 'sessions-weekly') {
          data = await getSessionsWeekly(ctx.portId, range);
        } else if (TOP_METRIC_RX.test(metric)) {
          const raw = metric.replace(/^top-/, '');
          // Legacy alias - older callers still send `top-url`; map to the
          // Umami v3 enum name to keep them working post-rewrite.
          const type = (raw === 'url' ? 'path' : raw) as UmamiMetricType;
          const limit = Number(url.searchParams.get('limit') ?? 10);
          data = await getMetric(ctx.portId, range, type, limit);
        } else {
          throw new ValidationError(`Unknown metric: ${metric}`);
        }
      } catch (err) {
        if (err instanceof ValidationError) throw err;
        // Upstream Umami failure - re-throw as a typed code so the user gets
        // a friendly message and the request id is captured to error_events.
        const internalMessage = err instanceof Error ? err.message : 'Unknown error';
        throw new CodedError('UMAMI_UPSTREAM_ERROR', { internalMessage });
      }

      // `data === null` from the service means Umami isn't configured for
      // this port. Return 200 with `data: null` + the explicit
      // `notConfigured: true` flag so the UI renders a "configure your
      // credentials" empty state. **Do not throw** - a 409 here would
      // trigger React Query's default retry loop, and every retry fires
      // a system_settings lookup → pool saturation → server hang.
      if (data === null) {
        return NextResponse.json({ metric, range, data: null, notConfigured: true });
      }

      return NextResponse.json({ metric, range, data });
    } catch (err) {
      return errorResponse(err);
    }
  }),
);
