import type { Prisma } from "@prisma/client";
import { prisma } from "../db/prisma.js";
import { logger } from "./logger.service.js";

/** Who caused an {@link AnalyticsEvent}. `"user"` pairs with an `actorId` (`UserProfile.id`); `"system"` is a background job; `"anon"` is an unauthenticated request. */
export type AnalyticsActorType = "user" | "system" | "anon";

/** Optional context for {@link AnalyticsService.recordEvent}. */
export interface RecordEventOptions {
  /** `UserProfile.id` — set together with `actorType: "user"` (the default when this is present). */
  actorId?: number;
  /** Overrides the inferred actor type (`"user"` when `actorId` is set, else `"anon"`). */
  actorType?: AnalyticsActorType;
  /** Small free-form blob for later slicing (`{ sourceKey, recipeId, … }`) — nothing queries into it today. */
  context?: Prisma.InputJsonValue;
}

/**
 * Records product usage events for the admin dashboard's metrics (see the
 * `AnalyticsEvent` model doc comment). A class rather than a bare function
 * — same convention as `LoggerService`/`ErrorHandlerService`: `public`
 * `recordEvent` is the API, `_insert` is the internal it fans out to.
 *
 * **Fire-and-forget by contract**: `recordEvent` returns `void`, not a
 * promise. The insert runs detached, and a failure is logged at `warn` and
 * swallowed — analytics must never add latency to, or fail, the request
 * that triggered it. Call sites therefore never `await` it.
 */
export class AnalyticsService {
  public recordEvent(type: string, options: RecordEventOptions = {}): void {
    const actorType: AnalyticsActorType =
      options.actorType ?? (options.actorId !== undefined ? "user" : "anon");

    void this._insert(type, actorType, options).catch((err: unknown) => {
      logger.warn("Analytics event insert failed", {
        eventType: type,
        error: err instanceof Error ? err.message : String(err),
      });
    });
  }

  private async _insert(
    type: string,
    actorType: AnalyticsActorType,
    options: RecordEventOptions,
  ): Promise<void> {
    try {
      await prisma.analyticsEvent.create({
        data: {
          type,
          actorType,
          actorId: options.actorId ?? null,
          context: options.context,
        },
      });
    } catch (err) {
      // Rethrown so `recordEvent`'s `.catch` above logs it — this layer
      // just isn't allowed a bare `await` per the repo's convention.
      throw err;
    }
  }
}

/** Single shared instance — stateless, same reasoning as `logger`. */
export const analytics = new AnalyticsService();
