import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { buildOpenAICodexProviderPlugin } from "./openai-codex-provider.js";
import { buildOpenAIProvider } from "./openai-provider.js";
import { buildOpenAICodexSetupProvider, buildOpenAISetupProvider } from "./setup-api.js";

const manifest = JSON.parse(
  readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"),
) as {
  mediaUnderstandingProviderMetadata?: Record<
    string,
    {
      capabilities?: string[];
      defaultModels?: Record<string, string>;
      autoPriority?: Record<string, number>;
    }
  >;
  providerAuthChoices?: Array<{
    provider?: string;
    method?: string;
    choiceLabel?: string;
    choiceHint?: string;
    choiceId?: string;
    deprecatedChoiceIds?: string[];
    assistantVisibility?: string;
    groupId?: string;
    groupLabel?: string;
    groupHint?: string;
  }>;
  modelCatalog?: {
    suppressions?: Array<{
      provider?: string;
      model?: string;
      reason?: string;
    }>;
  };
};

const packageJson = JSON.parse(
  readFileSync(new URL("./package.json", import.meta.url), "utf8"),
) as {
  dependencies?: Record<string, string>;
};

function manifestComparableWizardFields(choice: {
  choiceId?: string;
  choiceLabel?: string;
  choiceHint?: string;
  assistantVisibility?: string;
  groupId?: string;
  groupLabel?: string;
  groupHint?: string;
}) {
  return Object.fromEntries(
    Object.entries({
      choiceId: choice.choiceId,
      choiceLabel: choice.choiceLabel,
      choiceHint: choice.choiceHint,
      assistantVisibility: choice.assistantVisibility,
      groupId: choice.groupId,
      groupLabel: choice.groupLabel,
      groupHint: choice.groupHint,
    }).filter(([, value]) => value !== undefined),
  );
}

function providerWizardByKey() {
  const providers = [
    buildOpenAIProvider(),
    buildOpenAICodexProviderPlugin(),
    buildOpenAISetupProvider(),
    buildOpenAICodexSetupProvider(),
  ];
  const wizards = new Map<string, Record<string, unknown>>();

  for (const provider of providers) {
    for (const authMethod of provider.auth ?? []) {
      if (authMethod.wizard) {
        wizards.set(`${provider.id}:${authMethod.id}`, authMethod.wizard);
      }
    }
  }

  return wizards;
}

function expectWizardFields(
  wizard: Record<string, unknown> | undefined,
  choice: ReturnType<typeof manifestComparableWizardFields>,
  key: string,
) {
  if (!wizard) {
    throw new Error(`Missing wizard for ${key}`);
  }
  for (const [field, value] of Object.entries(choice)) {
    expect(wizard[field], `${key}.${field}`).toBe(value);
  }
}

describe("OpenAI plugin manifest", () => {
  it("keeps runtime dependencies in the package manifest", () => {
    expect(packageJson.dependencies?.["@earendil-works/pi-ai"]).toBe("0.75.5");
    expect(packageJson.dependencies?.ws).toBe("8.21.0");
  });

  it("does not suppress Codex Spark for the Codex catalog", () => {
    const suppressionRefs = new Set(
      (manifest.modelCatalog?.suppressions ?? []).map(
        (suppression) => `${suppression.provider}/${suppression.model}`,
      ),
    );

    expect(suppressionRefs).toContain("openai/gpt-5.3-codex-spark");
    expect(suppressionRefs).toContain("azure-openai-responses/gpt-5.3-codex-spark");
    expect(suppressionRefs).not.toContain("openai-codex/gpt-5.3-codex-spark");
  });

  it("keeps removed Codex CLI import auth choice as a deprecated browser-login alias", () => {
    const codexBrowserLogin = manifest.providerAuthChoices?.find(
      (choice) => choice.choiceId === "openai-codex",
    );

    expect(codexBrowserLogin?.deprecatedChoiceIds).toContain("openai-codex-import");
  });

  it("keeps Codex media-understanding manifest metadata aligned with runtime audio support", () => {
    const metadata = manifest.mediaUnderstandingProviderMetadata?.["openai-codex"];
    expect(metadata?.capabilities).toEqual(["image", "audio"]);
    expect(metadata?.defaultModels?.image).toBe("gpt-5.5");
    expect(metadata?.defaultModels?.audio).toBe("gpt-4o-transcribe");
    expect(metadata?.autoPriority?.image).toBe(20);
    expect(metadata?.autoPriority?.audio).toBe(20);
  });

  it("labels OpenAI API key and Codex auth choices without stale mixed OAuth wording", () => {
    const choices = manifest.providerAuthChoices ?? [];
    const codexBrowserLogin = choices.find((choice) => choice.choiceId === "openai-codex");
    const codexDeviceCode = choices.find(
      (choice) => choice.choiceId === "openai-codex-device-code",
    );
    const openAiLogin = choices.find((choice) => choice.choiceId === "openai");
    const openAiDeviceCode = choices.find((choice) => choice.choiceId === "openai-device-code");
    const apiKey = choices.find(
      (choice) => choice.provider === "openai" && choice.method === "api-key",
    );
    const codexApiKey = choices.find((choice) => choice.choiceId === "openai-codex-api-key");

    expect(openAiLogin?.choiceLabel).toBe("ChatGPT Login");
    expect(openAiLogin?.choiceHint).toBe("Sign in with your ChatGPT or Codex subscription");
    expect(openAiLogin?.assistantVisibility).toBe("manual-only");
    expect(openAiLogin?.groupId).toBe("openai");
    expect(openAiLogin?.groupLabel).toBe("OpenAI");
    expect(openAiLogin?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
    expect(openAiDeviceCode?.choiceLabel).toBe("ChatGPT Device Pairing");
    expect(openAiDeviceCode?.choiceHint).toBe(
      "Pair your ChatGPT account in browser with a device code",
    );
    expect(openAiDeviceCode?.assistantVisibility).toBe("manual-only");
    expect(openAiDeviceCode?.groupId).toBe("openai");
    expect(openAiDeviceCode?.groupLabel).toBe("OpenAI");
    expect(openAiDeviceCode?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
    expect(codexBrowserLogin?.choiceLabel).toBe("ChatGPT/Codex Browser Login");
    expect(codexBrowserLogin?.choiceHint).toBe("Sign in with OpenAI in your browser");
    expect(codexBrowserLogin?.groupId).toBe("openai");
    expect(codexBrowserLogin?.groupLabel).toBe("OpenAI");
    expect(codexBrowserLogin?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
    expect(codexDeviceCode?.choiceLabel).toBe("ChatGPT/Codex Device Pairing");
    expect(codexDeviceCode?.choiceHint).toBe("Pair in browser with a device code");
    expect(codexDeviceCode?.groupId).toBe("openai");
    expect(codexDeviceCode?.groupLabel).toBe("OpenAI");
    expect(codexDeviceCode?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
    expect(apiKey?.choiceLabel).toBe("OpenAI API Key");
    expect(apiKey?.choiceHint).toBe("Use your OpenAI API key directly");
    expect(apiKey?.groupId).toBe("openai");
    expect(apiKey?.groupLabel).toBe("OpenAI");
    expect(apiKey?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
    expect(codexApiKey?.choiceLabel).toBe("OpenAI API Key Backup");
    expect(codexApiKey?.choiceHint).toBe(
      "Use an OpenAI API key when your Codex subscription is unavailable",
    );
    expect(codexApiKey?.groupId).toBe("openai");
    expect(codexApiKey?.groupLabel).toBe("OpenAI");
    expect(codexApiKey?.groupHint).toBe("ChatGPT/Codex sign-in or API key");
    expect(choices.map((choice) => choice.choiceLabel)).not.toContain(
      "OpenAI Codex (ChatGPT OAuth)",
    );
    expect(choices.map((choice) => choice.groupHint)).not.toContain("Codex OAuth + API key");
    expect(choices.map((choice) => choice.groupHint)).not.toContain("API key or Codex sign-in");
  });

  it("keeps auth choice copy aligned with provider wizard metadata", () => {
    const wizards = providerWizardByKey();

    for (const choice of manifest.providerAuthChoices ?? []) {
      const key = `${choice.provider}:${choice.method}`;

      expectWizardFields(wizards.get(key), manifestComparableWizardFields(choice), key);
    }
  });
});
