// ---------------------------------------------------------------------------
// Autumn billing service — wraps the autumn-js SDK with Effect
// ---------------------------------------------------------------------------

import { env } from "cloudflare:workers";
import { Autumn } from "autumn-js";
import { Context, Data, Effect, Layer } from "effect";

import { captureCauseEffect } from "../../observability";

// ---------------------------------------------------------------------------
// Errors
// ---------------------------------------------------------------------------

export class AutumnError extends Data.TaggedError("AutumnError")<{
  message: string;
  cause?: unknown;
}> {}

// ---------------------------------------------------------------------------
// Service interface
// ---------------------------------------------------------------------------

export type IAutumnService = Readonly<{
  use: <A>(fn: (client: Autumn) => Promise<A>) => Effect.Effect<A, AutumnError, never>;
  checkExecutionBalance: (
    organizationId: string,
  ) => Effect.Effect<{ readonly allowed: boolean }, AutumnError, never>;
  /**
   * Fire-and-forget-safe execution usage tracker. Errors are caught and
   * logged; the returned Effect never fails. Callers typically
   * `Effect.runFork` it at the boundary so the billing call can't stall a
   * user-facing request.
   */
  trackExecution: (organizationId: string) => Effect.Effect<void, never, never>;
}>;

// ---------------------------------------------------------------------------
// Implementation
// ---------------------------------------------------------------------------

const make = Effect.sync(() => {
  const secretKey = env.AUTUMN_SECRET_KEY;

  if (!secretKey) {
    const notConfigured = Effect.fail(
      new AutumnError({ message: "Autumn not configured: AUTUMN_SECRET_KEY is empty" }),
    );
    return {
      use: () => notConfigured,
      checkExecutionBalance: () => notConfigured,
      trackExecution: () => Effect.void,
    } satisfies IAutumnService;
  }

  // AUTUMN_API_URL points the real SDK at an Autumn emulator in tests/dev.
  const client = new Autumn({
    secretKey,
    ...(env.AUTUMN_API_URL ? { serverURL: env.AUTUMN_API_URL } : {}),
  });

  const use = <A>(fn: (client: Autumn) => Promise<A>) =>
    Effect.tryPromise({
      try: () => fn(client),
      catch: (cause) => new AutumnError({ message: "Autumn SDK request failed", cause }),
    }).pipe(Effect.withSpan(`autumn.${fn.name ?? "use"}`));

  const trackExecution = (organizationId: string) =>
    Effect.gen(function* () {
      yield* Effect.annotateCurrentSpan({ "autumn.customer.id": organizationId });
      yield* use((c) =>
        c.track({ customerId: organizationId, featureId: "executions", value: 1 }),
      ).pipe(
        Effect.catchTag("AutumnError", (error) =>
          Effect.gen(function* () {
            // Silent billing data loss is worth paging on: autumn.trackExecution
            // is fire-and-forget so the caller doesn't handle it themselves.
            yield* Effect.sync(() => {
              console.error("[billing] track failed:", error);
            });
            yield* captureCauseEffect(error);
            yield* Effect.annotateCurrentSpan({ "autumn.track.failed": true });
          }),
        ),
      );
    }).pipe(Effect.withSpan("autumn.trackExecution"));

  const checkExecutionBalance = (organizationId: string) =>
    Effect.gen(function* () {
      yield* Effect.annotateCurrentSpan({ "autumn.customer.id": organizationId });
      const check = yield* use((c) =>
        c.check({ customerId: organizationId, featureId: "executions" }),
      );
      return { allowed: check.allowed };
    }).pipe(Effect.withSpan("autumn.checkExecutionBalance"));

  return { use, checkExecutionBalance, trackExecution } satisfies IAutumnService;
});

export class AutumnService extends Context.Service<AutumnService, IAutumnService>()(
  "@executor-js/cloud/AutumnService",
) {
  static Default = Layer.effect(this)(make);
}
