import type { FastifyInstance, FastifyRequest } from "fastify";
import { isAnalyticsOptedIn, schema } from "@app/database";
import {
  analyticsBatchInputSchema,
  MAX_ANALYTICS_BATCH_SIZE,
  validateAnalyticsPropertySize,
} from "@app/validation";
import { parse, errors } from "../lib/errors.js";
import { audit } from "../lib/helpers.js";

/** Max events per batch is also the per-minute rate-limit budget for this endpoint. */
const ANALYTICS_RATE_LIMIT_MAX = MAX_ANALYTICS_BATCH_SIZE;

/**
 * Product analytics ingestion (spec §8.2).
 * Accepts both authenticated and anonymous events. Anonymous events must have
 * anonymousId and can be backfilled to userId later when the user logs in.
 */
export async function analyticsRoutes(app: FastifyInstance) {
  app.post(
    "/v1/analytics/events",
    {
      config: {
        rateLimit: {
          max: ANALYTICS_RATE_LIMIT_MAX,
          timeWindow: "1 minute",
          keyGenerator: (req) => `analytics:${req.ip}:${(req as unknown as { userId?: string }).userId ?? "anon"}`,
        },
      },
    },
    async (req, reply) => {
      const input = parse(analyticsBatchInputSchema, req.body, "analytics batch");
      validateAnalyticsPropertySize(input.events);

      let userId: string | undefined;
      try {
        const payload = await req.jwtVerify<{ sub: string; role: string; type: string }>();
        if (payload.type === "access" && payload.sub) {
          userId = payload.sub;
        }
      } catch {
        // Anonymous event – fine as long as the batch carries anonymousId.
      }
      const isAuthenticated = !!userId;

      if (isAuthenticated && userId) {
        const optedIn = await isAnalyticsOptedIn(app.db, userId);
        if (!optedIn) {
          throw errors.forbidden("Analytics collection disabled by user consent.");
        }
      }

      const now = new Date();
      const rows = input.events.map((event, index) => {
        const occurredAt = event.occurredAt ? new Date(event.occurredAt) : now;
        if (Number.isNaN(occurredAt.getTime())) {
          throw errors.badRequest(`Invalid occurredAt for event ${index}`);
        }
        return {
          occurredAt,
          receivedAt: now,
          eventName: event.name,
          anonymousId: event.anonymousId ?? null,
          sessionId: event.sessionId ?? null,
          userId: isAuthenticated ? userId : null,
          householdId: event.householdId ?? null,
          appVersion: event.appVersion ?? null,
          platform: event.platform ?? null,
          locale: event.locale ?? null,
          experimentVariant: event.experimentVariant ?? null,
          properties: event.properties ?? {},
        };
      });

      // Single insert for the whole batch. On conflict is impossible because
      // events are insert-only with random UUIDs.
      const result = await app.db.insert(schema.productAnalyticsEvents).values(rows);
      const accepted = result.rowCount ?? rows.length;

      if (isAuthenticated) {
        await audit(app.db, {
          actorUserId: userId,
          action: "analytics.ingest",
          targetType: "analytics_batch",
          targetId: String(accepted),
          ip: req.ip,
        });
      }

      return reply.code(202).send({ accepted, rejected: 0 });
    },
  );
}
