// Registry contract tests cover plugin contract registry contents and lookup behavior.
import { describe, expect, it } from "vitest";
import { uniqueSortedStrings } from "../../plugin-sdk/test-helpers/string-utils.js";
import { loadPluginManifestRegistry, type PluginManifestRecord } from "../manifest-registry.js";
import { resolveManifestContractPluginIds } from "../plugin-registry.js";
import { BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS } from "./inventory/bundled-capability-metadata.js";
import {
  pluginRegistrationContractRegistry,
  providerContractLoadError,
  providerContractPluginIds,
} from "./registry.js";

const ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_IDS = ["codex", "qa-lab"] as const;
const ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_ID_SET = new Set<string>(
  ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_IDS,
);

describe("plugin contract registry", () => {
  function expectUniqueIds(ids: readonly string[]) {
    expect(ids).toEqual([...new Set(ids)]);
  }

  function expectRegistryPluginIds(params: {
    actualPluginIds: readonly string[];
    predicate: (plugin: PluginManifestRecord) => boolean;
  }) {
    expect(uniqueSortedStrings(params.actualPluginIds)).toEqual(
      resolveBundledManifestPluginIds(params.predicate),
    );
  }

  function resolveBundledManifestPluginIds(predicate: (plugin: PluginManifestRecord) => boolean) {
    if (process.env.VITEST) {
      return BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.map(
        (entry) =>
          ({
            id: entry.pluginId,
            origin: "bundled",
            providers: entry.providerIds,
            contracts: {
              embeddingProviders: entry.embeddingProviderIds,
              speechProviders: entry.speechProviderIds,
              realtimeTranscriptionProviders: entry.realtimeTranscriptionProviderIds,
              realtimeVoiceProviders: entry.realtimeVoiceProviderIds,
              mediaUnderstandingProviders: entry.mediaUnderstandingProviderIds,
              transcriptSourceProviders: entry.transcriptSourceProviderIds,
              documentExtractors: entry.documentExtractorIds,
              imageGenerationProviders: entry.imageGenerationProviderIds,
              videoGenerationProviders: entry.videoGenerationProviderIds,
              musicGenerationProviders: entry.musicGenerationProviderIds,
              webContentExtractors: entry.webContentExtractorIds,
              webFetchProviders: entry.webFetchProviderIds,
              webSearchProviders: entry.webSearchProviderIds,
              migrationProviders: entry.migrationProviderIds,
              tools: entry.toolNames,
            },
          }) as PluginManifestRecord,
      )
        .filter(predicate)
        .map((plugin) => plugin.id)
        .toSorted((left, right) => left.localeCompare(right));
    }
    const snapshotPluginIds = new Set(
      BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.map((entry) => entry.pluginId),
    );
    return loadPluginManifestRegistry({})
      .plugins.filter((plugin) => snapshotPluginIds.has(plugin.id) && predicate(plugin))
      .map((plugin) => plugin.id)
      .toSorted((left, right) => left.localeCompare(right));
  }

  it("loads bundled non-provider capability registries without import-time failure", () => {
    expect(providerContractLoadError).toBeUndefined();
    expect(Array.from(pluginRegistrationContractRegistry)).toStrictEqual(
      BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS,
    );
  });

  it.each([
    {
      name: "does not duplicate bundled provider ids",
      ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.providerIds),
    },
    {
      name: "does not duplicate bundled web fetch provider ids",
      ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.webFetchProviderIds),
    },
    {
      name: "does not duplicate bundled web search provider ids",
      ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.webSearchProviderIds),
    },
    {
      name: "does not duplicate bundled migration provider ids",
      ids: () => pluginRegistrationContractRegistry.flatMap((entry) => entry.migrationProviderIds),
    },
    {
      name: "does not duplicate bundled media provider ids",
      ids: () =>
        pluginRegistrationContractRegistry.flatMap((entry) => entry.mediaUnderstandingProviderIds),
    },
    {
      name: "does not duplicate bundled transcripts source provider ids",
      ids: () =>
        pluginRegistrationContractRegistry.flatMap((entry) => entry.transcriptSourceProviderIds),
    },
    {
      name: "does not duplicate bundled realtime transcription provider ids",
      ids: () =>
        pluginRegistrationContractRegistry.flatMap(
          (entry) => entry.realtimeTranscriptionProviderIds,
        ),
    },
    {
      name: "does not duplicate bundled realtime voice provider ids",
      ids: () =>
        pluginRegistrationContractRegistry.flatMap((entry) => entry.realtimeVoiceProviderIds),
    },
    {
      name: "does not duplicate bundled image-generation provider ids",
      ids: () =>
        pluginRegistrationContractRegistry.flatMap((entry) => entry.imageGenerationProviderIds),
    },
  ] as const)("$name", ({ ids }) => {
    expectUniqueIds(ids());
  });

  it("does not duplicate bundled speech provider ids", () => {
    expectUniqueIds(pluginRegistrationContractRegistry.flatMap((entry) => entry.speechProviderIds));
  });

  it("covers every bundled provider plugin discovered from manifests", () => {
    expectRegistryPluginIds({
      actualPluginIds: providerContractPluginIds,
      predicate: (plugin) => plugin.origin === "bundled" && plugin.providers.length > 0,
    });
  });

  it("keeps video-only provider auth choices out of text onboarding", () => {
    const registry = loadPluginManifestRegistry({});

    for (const pluginId of ["alibaba", "runway"]) {
      const plugin = registry.plugins.find(
        (entry) => entry.origin === "bundled" && entry.id === pluginId,
      );
      expect(plugin?.providerAuthChoices).toEqual([
        {
          provider: pluginId,
          method: "api-key",
          choiceId: pluginId === "alibaba" ? "alibaba-model-studio-api-key" : "runway-api-key",
          choiceLabel: pluginId === "alibaba" ? "Alibaba Model Studio API key" : "Runway API key",
          groupId: pluginId,
          groupLabel: pluginId === "alibaba" ? "Alibaba Model Studio" : "Runway",
          groupHint: pluginId === "alibaba" ? "DashScope / Model Studio API key" : "API key",
          onboardingScopes: ["image-generation"],
          optionKey: pluginId === "alibaba" ? "alibabaModelStudioApiKey" : "runwayApiKey",
          cliFlag: pluginId === "alibaba" ? "--alibaba-model-studio-api-key" : "--runway-api-key",
          cliOption:
            pluginId === "alibaba"
              ? "--alibaba-model-studio-api-key <key>"
              : "--runway-api-key <key>",
          cliDescription:
            pluginId === "alibaba" ? "Alibaba Model Studio API key" : "Runway API key",
        },
      ]);
    }
  });

  it("exposes the GitHub Copilot non-interactive onboarding token flag from manifest metadata", () => {
    const registry = loadPluginManifestRegistry({});
    const plugin = registry.plugins.find(
      (entry) => entry.origin === "bundled" && entry.id === "github-copilot",
    );

    expect(plugin?.providerAuthChoices).toEqual([
      {
        provider: "github-copilot",
        method: "device",
        choiceId: "github-copilot",
        choiceLabel: "GitHub Copilot",
        choiceHint: "Device login with your GitHub account",
        groupId: "copilot",
        groupLabel: "Copilot",
        groupHint: "GitHub + local proxy",
        optionKey: "githubCopilotToken",
        cliFlag: "--github-copilot-token",
        cliOption: "--github-copilot-token <token>",
        cliDescription: "GitHub Copilot OAuth token",
      },
    ]);
  });

  it("covers every bundled speech plugin discovered from manifests", () => {
    expectRegistryPluginIds({
      actualPluginIds: pluginRegistrationContractRegistry
        .filter((entry) => entry.speechProviderIds.length > 0)
        .map((entry) => entry.pluginId),
      predicate: (plugin) =>
        plugin.origin === "bundled" && (plugin.contracts?.speechProviders?.length ?? 0) > 0,
    });
  });

  it("covers every bundled realtime voice plugin discovered from manifests", () => {
    expectRegistryPluginIds({
      actualPluginIds: pluginRegistrationContractRegistry
        .filter((entry) => entry.realtimeVoiceProviderIds.length > 0)
        .map((entry) => entry.pluginId),
      predicate: (plugin) =>
        plugin.origin === "bundled" && (plugin.contracts?.realtimeVoiceProviders?.length ?? 0) > 0,
    });
  });

  it("covers every bundled realtime transcription plugin discovered from manifests", () => {
    expectRegistryPluginIds({
      actualPluginIds: pluginRegistrationContractRegistry
        .filter((entry) => entry.realtimeTranscriptionProviderIds.length > 0)
        .map((entry) => entry.pluginId),
      predicate: (plugin) =>
        plugin.origin === "bundled" &&
        (plugin.contracts?.realtimeTranscriptionProviders?.length ?? 0) > 0,
    });
  });

  it("covers every bundled transcripts source plugin discovered from manifests", () => {
    expectRegistryPluginIds({
      actualPluginIds: pluginRegistrationContractRegistry
        .filter((entry) => entry.transcriptSourceProviderIds.length > 0)
        .map((entry) => entry.pluginId),
      predicate: (plugin) =>
        plugin.origin === "bundled" &&
        (plugin.contracts?.transcriptSourceProviders?.length ?? 0) > 0,
    });
  });

  it("covers every bundled web fetch plugin from the shared resolver", () => {
    const bundledWebFetchPluginIds = resolveManifestContractPluginIds({
      contract: "webFetchProviders",
      origin: "bundled",
    });

    expect(
      uniqueSortedStrings(
        pluginRegistrationContractRegistry
          .filter((entry) => entry.webFetchProviderIds.length > 0)
          .map((entry) => entry.pluginId),
      ),
    ).toEqual(bundledWebFetchPluginIds);
  });

  it("covers every bundled web search plugin from the shared resolver", () => {
    const snapshotPluginIds = new Set(
      BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.map((entry) => entry.pluginId),
    );
    const bundledWebSearchPluginIds = resolveManifestContractPluginIds({
      contract: "webSearchProviders",
      origin: "bundled",
    }).filter(
      (pluginId) =>
        snapshotPluginIds.has(pluginId) &&
        !ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_ID_SET.has(pluginId),
    );
    const expectedPluginIds = uniqueSortedStrings([
      ...bundledWebSearchPluginIds,
      ...ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_IDS,
    ]);
    const actualPluginIds = uniqueSortedStrings(
      pluginRegistrationContractRegistry
        .filter((entry) => entry.webSearchProviderIds.length > 0)
        .map((entry) => entry.pluginId),
    );

    expect(actualPluginIds).toEqual(expectedPluginIds);
    expect(
      actualPluginIds.filter((pluginId) => !bundledWebSearchPluginIds.includes(pluginId)),
    ).toEqual([...ACTIVATION_SCOPED_WEB_SEARCH_PLUGIN_IDS]);
  });

  it("covers every bundled migration provider plugin discovered from manifests", () => {
    expectRegistryPluginIds({
      actualPluginIds: pluginRegistrationContractRegistry
        .filter((entry) => entry.migrationProviderIds.length > 0)
        .map((entry) => entry.pluginId),
      predicate: (plugin) =>
        plugin.origin === "bundled" && (plugin.contracts?.migrationProviders?.length ?? 0) > 0,
    });
  });
});
