/**
 * Platform Telemetry
 * ──────────────────
 * Fire-and-forget event logger that writes to PlatformEvent. Call
 * this from API routes + significant client actions so the admin
 * analytics surface (DAU / WAU / MAU, feature adoption, real-time
 * activity feed) has a single source of truth.
 *
 * Design rules:
 *   • Non-blocking — every write is .catch()-swallowed so a DB blip
 *     can never surface as a user-facing error.
 *   • No-op in test envs unless PLATFORM_TELEMETRY_DISABLED != 1.
 *   • Stable `action` keys — these are the analytics aggregation
 *     keys; don't rename once shipped.
 *
 * Usage (API route):
 *
 *   await trackEvent({
 *     userId: user.id,
 *     brandId,
 *     action: "audit_started",
 *     category: "audit",
 *     metadata: { pageCount: 100, auditType: "technical" },
 *     request: req,                    // pulls ip / ua + path
 *   });
 */

import { prisma } from "@/lib/prisma";

export type PlatformEventCategory =
  | "navigation"
  | "search"
  | "audit"
  | "report"
  | "integration"
  | "content"
  | "ai"
  | "export"
  | "settings"
  | "auth"
  | "admin"
  | "seo"
  | "technical";

export interface TrackEventInput {
  userId?: string | null;
  brandId?: string | null;
  action: string;
  category: PlatformEventCategory;
  metadata?: Record<string, unknown>;
  /** Pass the incoming Request to auto-capture ipAddress + userAgent
   *  + request path in metadata. Omitted for server-side / cron events. */
  request?: Request | null;
}

const DISABLED = process.env.PLATFORM_TELEMETRY_DISABLED === "1";

/** Fire-and-forget insert into PlatformEvent. Returns immediately;
 *  the write happens in the background. Failures are logged but
 *  never thrown to the caller. */
export function trackEvent(input: TrackEventInput): void {
  if (DISABLED) return;
  void writeEvent(input).catch((err) => {
    console.warn("[platform-telemetry] write failed", err instanceof Error ? err.message : err);
  });
}

async function writeEvent(input: TrackEventInput): Promise<void> {
  const req = input.request ?? null;
  const ipAddress = req ? extractIp(req) : null;
  const userAgent = req?.headers.get("user-agent") ?? null;
  const path = req ? safeUrl(req.url)?.pathname : undefined;

  const metadata = {
    ...(input.metadata ?? {}),
    ...(path ? { path } : {}),
  };

  await prisma.platformEvent.create({
    data: {
      userId: input.userId ?? null,
      brandId: input.brandId ?? null,
      action: input.action,
      category: input.category,
      metadata: Object.keys(metadata).length > 0 ? (metadata as object) : undefined,
      ipAddress,
      userAgent: userAgent?.slice(0, 500) ?? null,
    },
  });
}

function extractIp(req: Request): string | null {
  // Vercel / most reverse proxies forward the original IP here. Take
  // the first address in the chain when multiple are present.
  const forwarded = req.headers.get("x-forwarded-for");
  if (forwarded) return forwarded.split(",")[0]?.trim() ?? null;
  return req.headers.get("x-real-ip") ?? null;
}

function safeUrl(raw: string): URL | null {
  try { return new URL(raw); } catch { return null; }
}

/** Timing wrapper — records an action before and after the handler
 *  runs, emitting the elapsed ms + status. Common case for API
 *  routes that want "endpoint_called" telemetry without two calls. */
export async function trackTimed<T>(
  input: Omit<TrackEventInput, "metadata"> & { metadata?: Record<string, unknown> },
  handler: () => Promise<T>,
): Promise<T> {
  const start = Date.now();
  try {
    const result = await handler();
    trackEvent({
      ...input,
      metadata: { ...(input.metadata ?? {}), elapsedMs: Date.now() - start, status: "ok" },
    });
    return result;
  } catch (err) {
    trackEvent({
      ...input,
      metadata: {
        ...(input.metadata ?? {}),
        elapsedMs: Date.now() - start,
        status: "error",
        error: err instanceof Error ? err.message : String(err),
      },
    });
    throw err;
  }
}
