// ---------------------------------------------------------------------------
// GraphQL plugin — `configureAuth` (custom auth method merge-append) coverage.
//
// `configureAuth` merge-appends apikey/oauth2 methods onto an existing
// integration's opaque `authenticationTemplate`. These tests exercise the
// extension method directly (the same path the HTTP `configure` handler calls):
//   - round-trip: add a custom apikey method → `getConfig` shows it,
//   - append: a configured method does not drop the integration's existing
//     declared methods,
//   - slug generation: a method with a blank slug is assigned `custom_<id>`,
//   - dedupe: a matching slug replaces in place; two slug-less methods in one
//     call get distinct generated slugs,
//   - unknown slug is a no-op (returns []).
// ---------------------------------------------------------------------------

import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";

import { createExecutor } from "@executor-js/sdk";
import { makeTestConfig, memoryCredentialsPlugin } from "@executor-js/sdk/testing";

import { graphqlPlugin } from "./plugin";
import type { GraphqlAuthMethod } from "./types";

const makeExecutor = () =>
  createExecutor(
    makeTestConfig({
      plugins: [memoryCredentialsPlugin(), graphqlPlugin()] as const,
    }),
  );

const customApiKey: GraphqlAuthMethod = {
  slug: "my_custom",
  kind: "apikey",
  placements: [{ carrier: "header", name: "X-Api-Key" }],
};

describe("GraphQL Plugin — configureAuth (custom auth method)", () => {
  it.effect("adds a custom apiKey method and getConfig reflects it", () =>
    Effect.gen(function* () {
      const executor = yield* makeExecutor();

      yield* executor.graphql.addIntegration({
        endpoint: "https://x.example/graphql",
        slug: "cfg_api",
      });

      const merged = yield* executor.graphql.configureAuth("cfg_api", {
        authenticationTemplate: [customApiKey],
      });

      expect(merged).toHaveLength(1);
      expect(merged[0]!.slug).toBe("my_custom");

      const config = yield* executor.graphql.getConfig("cfg_api");
      const template = config?.authenticationTemplate ?? [];
      expect(template).toEqual([
        {
          slug: "my_custom",
          kind: "apikey",
          placements: [{ carrier: "header", name: "X-Api-Key" }],
        },
      ]);
    }),
  );

  it.effect("appends to an existing declared template without dropping entries", () =>
    Effect.gen(function* () {
      const executor = yield* makeExecutor();

      yield* executor.graphql.addIntegration({
        endpoint: "https://x.example/graphql",
        slug: "cfg_append",
        authenticationTemplate: [
          { slug: "seed", kind: "apikey", placements: [{ carrier: "header", name: "X-Seed" }] },
        ],
      });

      const merged = yield* executor.graphql.configureAuth("cfg_append", {
        authenticationTemplate: [customApiKey],
      });

      expect(merged.map((m: GraphqlAuthMethod) => m.slug)).toEqual(["seed", "my_custom"]);
    }),
  );

  it.effect("generates a custom_<id> slug for a method submitted with a blank slug", () =>
    Effect.gen(function* () {
      const executor = yield* makeExecutor();

      yield* executor.graphql.addIntegration({
        endpoint: "https://x.example/graphql",
        slug: "cfg_genslug",
      });

      const merged = yield* executor.graphql.configureAuth("cfg_genslug", {
        authenticationTemplate: [
          { slug: "", kind: "apikey", placements: [{ carrier: "header", name: "X-Api-Key" }] },
        ],
      });

      expect(merged).toHaveLength(1);
      expect(merged[0]!.slug).toMatch(/^custom_[a-z0-9]+$/);
    }),
  );

  it.effect("dedupes: a matching slug replaces in place; two slug-less get distinct slugs", () =>
    Effect.gen(function* () {
      const executor = yield* makeExecutor();

      yield* executor.graphql.addIntegration({
        endpoint: "https://x.example/graphql",
        slug: "cfg_dedupe",
        authenticationTemplate: [customApiKey],
      });

      const merged = yield* executor.graphql.configureAuth("cfg_dedupe", {
        authenticationTemplate: [
          // Re-submit the same slug with a different placement → replace in place.
          {
            slug: "my_custom",
            kind: "apikey",
            placements: [{ carrier: "header", name: "X-Other" }],
          },
          { slug: "", kind: "apikey", placements: [{ carrier: "query", name: "api_key" }] },
          { slug: "", kind: "apikey", placements: [{ carrier: "query", name: "token" }] },
        ],
      });

      const slugs = merged.map((m: GraphqlAuthMethod) => m.slug);
      expect(slugs.filter((s: string) => s === "my_custom")).toHaveLength(1);
      expect(merged).toHaveLength(3);
      const generated = slugs.filter((s: string) => s.startsWith("custom_"));
      expect(generated).toHaveLength(2);
      expect(new Set(generated).size).toBe(2);

      const replaced = merged.find((m: GraphqlAuthMethod) => m.slug === "my_custom")!;
      expect(replaced).toEqual({
        slug: "my_custom",
        kind: "apikey",
        placements: [{ carrier: "header", name: "X-Other" }],
      });
    }),
  );

  it.effect("replace mode overwrites the auth template array", () =>
    Effect.gen(function* () {
      const executor = yield* makeExecutor();

      yield* executor.graphql.addIntegration({
        endpoint: "https://x.example/graphql",
        slug: "cfg_replace",
        authenticationTemplate: [
          { slug: "seed", kind: "apikey", placements: [{ carrier: "header", name: "X-Seed" }] },
          customApiKey,
        ],
      });

      const merged = yield* executor.graphql.configureAuth("cfg_replace", {
        authenticationTemplate: [
          { slug: "seed", kind: "apikey", placements: [{ carrier: "header", name: "X-Seed" }] },
        ],
        mode: "replace",
      });

      expect(merged.map((m: GraphqlAuthMethod) => m.slug)).toEqual(["seed"]);
    }),
  );

  it.effect("configureAuth is a no-op for an unknown integration", () =>
    Effect.gen(function* () {
      const executor = yield* makeExecutor();
      const merged = yield* executor.graphql.configureAuth("nope", {
        authenticationTemplate: [customApiKey],
      });
      expect(merged).toEqual([]);
    }),
  );

  it.effect("getConfig returns null for an unknown integration", () =>
    Effect.gen(function* () {
      const executor = yield* makeExecutor();
      expect(yield* executor.graphql.getConfig("nope")).toBeNull();
    }),
  );
});
