import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { Effect, Layer } from "effect";
import { afterAll, beforeAll, expect, test } from "@effect/vitest";

import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/sdk";
import { makeScopedExecutor } from "@executor-js/api/server";

import { createSelfHostDb, SelfHostDb } from "./db/self-host-db";
import { SelfHostScopedExecutorSeams } from "./execution";
import type { SelfHostPlugins } from "./plugins";

// The self-host scoped-executor seams (DbProvider over the long-lived SelfHostDb,
// fresh per-request plugins, host config) over the shared `makeScopedExecutor`,
// leaving `SelfHostDb` as the only requirement (the production path provides the
// same seams via `SelfHostExecutionStackLayer`).
const createScopedExecutor = (
  accountId: string,
  organizationId: string,
  organizationName: string,
) =>
  makeScopedExecutor<SelfHostPlugins>(accountId, organizationId, organizationName).pipe(
    Effect.provide(SelfHostScopedExecutorSeams),
  );

const dataDir = mkdtempSync(join(tmpdir(), "eh-src-"));
process.env.EXECUTOR_DATA_DIR = dataDir;

let dbLayer!: Layer.Layer<SelfHostDb>;
let dbHandle: Awaited<ReturnType<typeof createSelfHostDb>> | undefined;

beforeAll(async () => {
  dbHandle = await createSelfHostDb({
    path: join(dataDir, "data.db"),
    namespace: "executor_selfhost",
    version: "1.0.0",
  });
  dbLayer = Layer.succeed(SelfHostDb)(dbHandle);
});
afterAll(async () => {
  await dbHandle?.close();
});

// Inline OpenAPI spec so the test doesn't depend on the network to register.
const TINY_SPEC = JSON.stringify({
  openapi: "3.0.0",
  info: { title: "Tiny", version: "1.0.0" },
  servers: [{ url: "https://httpbin.org" }],
  paths: {
    "/get": {
      get: {
        operationId: "httpGet",
        summary: "GET",
        responses: { "200": { description: "ok" } },
      },
    },
  },
});

test("an org-owned connection registers tools shared across org members", async () => {
  // Alice (a member) uploads the integration spec and attaches an org-owned
  // connection. Org-owned connections (and their per-connection tools) are
  // visible to every member of the tenant.
  const added = await Effect.runPromise(
    Effect.gen(function* () {
      const alice = yield* createScopedExecutor("alice", "default-org", "Default");
      const result = yield* alice.openapi.addSpec({
        spec: { kind: "blob", value: TINY_SPEC },
        slug: "tiny",
        baseUrl: "",
      });
      yield* alice.connections.create({
        owner: "org",
        name: ConnectionName.make("shared"),
        integration: IntegrationSlug.make("tiny"),
        template: AuthTemplateSlug.make("none"),
        value: "",
      });
      return result;
    }).pipe(Effect.provide(dbLayer), Effect.scoped),
  );
  expect(String(added.slug)).toBe("tiny");
  expect(added.toolCount).toBeGreaterThan(0);

  // Bob — a different user in the SAME org — sees the org-owned connection's
  // tools (addressed `tools.tiny.org.shared.<tool>`).
  const bobToolAddresses = await Effect.runPromise(
    Effect.gen(function* () {
      const bob = yield* createScopedExecutor("bob", "default-org", "Default");
      const tools = yield* bob.tools.list();
      return tools.map((tool) => String(tool.address));
    }).pipe(Effect.provide(dbLayer), Effect.scoped),
  );
  expect(bobToolAddresses.some((address) => address.startsWith("tools.tiny.org."))).toBe(true);
});
