import { env } from "cloudflare:workers";
import { Effect, Layer } from "effect";
import { type FumaDB } from "@executor-js/fumadb";
import { type DrizzleConfig } from "@executor-js/fumadb/adapters/drizzle";
import { type schema as fumaSchema, type RelationsMap } from "@executor-js/fumadb/schema";

import {
  createExecutorFumaDb,
  DbProvider,
  type ExecutorDbHandle,
  type ExecutorDbProvider,
} from "@executor-js/api/server";
import { makeR2BlobStore } from "@executor-js/cloudflare/blob-store";
import type { FumaDb, FumaTables } from "@executor-js/sdk";

import { DbService } from "./db";

type DrizzleFumaSchema<TTables extends FumaTables> = ReturnType<
  typeof fumaSchema<string, TTables, RelationsMap<TTables>>
>;

export interface DrizzleFumaDb<TTables extends FumaTables = FumaTables> {
  readonly db: FumaDb<DrizzleFumaSchema<TTables>>;
  readonly fuma: FumaDB<DrizzleFumaSchema<TTables>[]>;
}

export interface CreateDrizzleFumaDbOptions<TTables extends FumaTables = FumaTables> {
  readonly db: DrizzleConfig["db"];
  readonly tables: TTables;
  readonly namespace: string;
  readonly version?: string;
  readonly provider: ExecutorDbProvider;
}

// Cloud opens its own postgres-js drizzle handle (see ./db.ts) and runs
// migrations out-of-band, so this is the pure FumaDB assembly over an
// already-opened `db`. Delegates to the shared factory; the local wrapper
// keeps the cloud-specific default version + option names.
export const createDrizzleFumaDb = <const TTables extends FumaTables>(
  options: CreateDrizzleFumaDbOptions<TTables>,
): DrizzleFumaDb<TTables> =>
  createExecutorFumaDb(options.db, {
    tables: options.tables,
    namespace: options.namespace,
    version: options.version ?? "1.0.0",
    provider: options.provider,
  });

export const CLOUD_NAMESPACE = "executor_cloud";

// Shared DbProvider seam (P2a). Cloud opens a fresh postgres-js connection per
// request (Cloudflare forbids sharing I/O across handlers); this assembles the
// FumaDB handle over the request-scoped `DbService.db`. Migrations run
// out-of-band, so there is no schema bring-up here, and `close` is a no-op —
// `DbService.Live` owns the postgres connection lifecycle.
export const cloudDbProviderLayer = (
  tables: FumaTables,
): Layer.Layer<DbProvider, never, DbService> =>
  Layer.effect(DbProvider)(
    Effect.map(DbService.asEffect(), ({ db }): ExecutorDbHandle => {
      const fuma = createDrizzleFumaDb({
        db,
        tables,
        namespace: CLOUD_NAMESPACE,
        provider: "postgresql",
      });
      return {
        db: fuma.db,
        fuma: fuma.fuma,
        close: async () => {},
        // Plugin blobs (multi-MB resolved specs) live in R2, not Postgres.
        // Guarded because test workers / local dev may run without the
        // binding — the executor then falls back to the FumaDB `blob` table.
        blobs: env.BLOBS ? makeR2BlobStore(env.BLOBS) : undefined,
      };
    }),
  );
