import { describe, expect, it } from "vitest";
import { useProviderDialogs } from "../src/dimsdk/pages/utils/provider-dialogs.ts";

function createStore(
  input?: Partial<{
    activeId: string;
    connections: Record<string, any>;
    customProviders: any[];
    env: Record<string, string>;
  }>,
): any {
  const env = { ...input?.env };
  return {
    state: {
      ui: {
        onboarding: {
          lastSeenVersion: "",
        },
        providerFunnel: {
          counts: {},
          events: [],
        },
        workspaceSelectionByCwd: {},
      },
      provider: {
        activeId: input?.activeId ?? "openai",
        connections: input?.connections ?? {},
        customProviders: input?.customProviders ?? [],
        custom: {
          baseUrl: "",
          apiKey: "",
          models: [],
        },
        activeModelByProvider: {},
      },
      config: { model: "", baseUrl: "", apiKey: "" },
      context: { env, cwd: "." },
      messages: [],
    },
    setEnvVar: (key: string, value: string | undefined) => {
      if (value == null) {
        delete env[key];
      } else {
        env[key] = String(value);
      }
    },
  };
}

function hasManualModelDiscoveryToast(toasts: string[]): boolean {
  return toasts.some(
    (text) => text.includes("automatic model discovery") || text.includes("自动发现模型"),
  );
}

describe("provider dialogs anthropic custom endpoint", () => {
  it("allows builtin anthropic custom host without running official self-check", async () => {
    const prev = process.env.DIMCODE_TEST;
    const originalFetch = globalThis.fetch;
    delete process.env.DIMCODE_TEST;

    try {
      const toasts: string[] = [];
      globalThis.fetch = (async () => {
        throw new Error("fetch should not be called");
      }) as unknown as typeof globalThis.fetch;

      const store = createStore({ activeId: "openai" });
      const dialogs = useProviderDialogs({
        store,
        nodeLike: false,
        showToast: (text: string) => toasts.push(text),
        openCustomSettings: () => {},
      });

      dialogs.openConnect();
      const anthropicOpt = dialogs.providerOptions.value.find(
        (o) => (o.keywords ?? []).includes("anthropic") && o.detail !== "add custom provider",
      );
      expect(anthropicOpt).toBeTruthy();
      dialogs.onSelectProvider(anthropicOpt?.label ?? null);
      expect(dialogs.showConnectDialog.value).toBe(true);

      dialogs.connectApiKeyDraft.value = "anthropic-key";
      dialogs.connectBaseUrl.value = "https://proxy.example.com";
      dialogs.applyConnect();

      await new Promise((resolve) => setTimeout(resolve, 30));

      expect(dialogs.showConnectDialog.value).toBe(false);
      expect(String(store.state.provider.activeId)).toBe("anthropic");
      expect(toasts.some((text) => text.includes("fetch should not be called"))).toBe(false);
      expect(toasts.some((text) => text.includes("401"))).toBe(false);
    } finally {
      globalThis.fetch = originalFetch;
      if (prev == null) {
        delete process.env.DIMCODE_TEST;
      } else {
        process.env.DIMCODE_TEST = prev;
      }
    }
  });

  it("allows apply connect for anthropic custom-path endpoint without failing self-check", async () => {
    const prev = process.env.DIMCODE_TEST;
    delete process.env.DIMCODE_TEST;
    try {
      const toasts: string[] = [];
      const store = createStore({
        activeId: "openai",
        customProviders: [
          {
            id: "cp_minimax",
            name: "MiniMax",
            adapter: "anthropic",
            baseUrl: "https://api.minimaxi.com/anthropic",
            apiKey: "minimax-key",
            models: [{ id: "MiniMax-M2.5" }],
          },
        ],
      });

      const dialogs = useProviderDialogs({
        store,
        nodeLike: false,
        showToast: (text: string) => toasts.push(text),
        openCustomSettings: () => {},
      });

      dialogs.openConnect();
      const customOpt = dialogs.providerOptions.value.find((o) =>
        (o.keywords ?? []).includes("cp_minimax"),
      );
      expect(customOpt).toBeTruthy();
      dialogs.onSelectProvider(customOpt?.label ?? null);
      expect(dialogs.showCustomManage.value).toBe(true);

      const connectOpt = dialogs.customManageOptions.value[0];
      expect(connectOpt).toBeTruthy();
      dialogs.onSelectCustomManage(connectOpt?.label ?? null);
      expect(dialogs.showConnectDialog.value).toBe(true);

      dialogs.connectApiKeyDraft.value = "minimax-key";
      dialogs.connectBaseUrl.value = "https://api.minimaxi.com/anthropic";
      dialogs.applyConnect();

      await new Promise((resolve) => setTimeout(resolve, 30));

      expect(dialogs.showConnectDialog.value).toBe(false);
      expect(String(store.state.provider.activeId)).toBe("cp_minimax");
      expect(
        toasts.some(
          (text) => text.includes("Connection test failed") || text.includes("连接测试失败"),
        ),
      ).toBe(false);
      expect(toasts.some((text) => text.includes("404"))).toBe(false);
    } finally {
      if (prev == null) {
        delete process.env.DIMCODE_TEST;
      } else {
        process.env.DIMCODE_TEST = prev;
      }
    }
  });

  it("prompts manual model entry for anthropic custom-host fetch without calling official discovery", async () => {
    const originalFetch = globalThis.fetch;

    try {
      const toasts: string[] = [];
      globalThis.fetch = (async () => {
        throw new Error("fetch should not be called");
      }) as unknown as typeof globalThis.fetch;

      const store = createStore({ activeId: "openai" });
      const dialogs = useProviderDialogs({
        store,
        nodeLike: false,
        showToast: (text: string) => toasts.push(text),
        openCustomSettings: () => {},
      });

      dialogs.openConnect();
      const addCustomOption = dialogs.providerOptions.value.find(
        (o) => o.detail === "add custom provider",
      );
      expect(addCustomOption).toBeTruthy();
      dialogs.onSelectProvider(addCustomOption?.label ?? null);
      expect(dialogs.showCustomType.value).toBe(true);

      const anthropicOption = dialogs.customTypeOptions.value.find((o) => o.detail === "anthropic");
      expect(anthropicOption).toBeTruthy();
      dialogs.onSelectCustomType(anthropicOption?.label ?? null);
      expect(dialogs.showCustomConnectDialog.value).toBe(true);

      dialogs.customConnectBaseUrl.value = "https://proxy.example.com";
      dialogs.customConnectApiKeyDraft.value = "proxy-key";
      dialogs.applyCustomConnect();

      await new Promise((resolve) => setTimeout(resolve, 30));

      expect(dialogs.showCustomModelsDialog.value).toBe(true);
      expect(dialogs.showCustomModelsFetchDialog.value).toBe(false);
      expect(hasManualModelDiscoveryToast(toasts)).toBe(true);
      expect(toasts.some((text) => text.includes("fetch should not be called"))).toBe(false);
      expect(toasts.some((text) => text.includes("401"))).toBe(false);
    } finally {
      globalThis.fetch = originalFetch;
    }
  });

  it("prompts manual model entry for anthropic custom-path fetch and saves manual models", async () => {
    const toasts: string[] = [];
    const store = createStore({ activeId: "openai" });
    const dialogs = useProviderDialogs({
      store,
      nodeLike: false,
      showToast: (text: string) => toasts.push(text),
      openCustomSettings: () => {},
    });

    dialogs.openConnect();
    const addCustomOption = dialogs.providerOptions.value.find(
      (o) => o.detail === "add custom provider",
    );
    expect(addCustomOption).toBeTruthy();
    dialogs.onSelectProvider(addCustomOption?.label ?? null);
    expect(dialogs.showCustomType.value).toBe(true);

    const anthropicOption = dialogs.customTypeOptions.value.find((o) => o.detail === "anthropic");
    expect(anthropicOption).toBeTruthy();
    dialogs.onSelectCustomType(anthropicOption?.label ?? null);
    expect(dialogs.showCustomConnectDialog.value).toBe(true);

    dialogs.customConnectBaseUrl.value = "https://api.minimaxi.com/anthropic";
    dialogs.customConnectApiKeyDraft.value = "minimax-key";
    dialogs.applyCustomConnect();

    await new Promise((resolve) => setTimeout(resolve, 30));

    expect(dialogs.showCustomModelsDialog.value).toBe(true);
    expect(dialogs.showCustomModelsFetchDialog.value).toBe(false);
    expect(hasManualModelDiscoveryToast(toasts)).toBe(true);

    dialogs.customModelsDraft.value = "MiniMax-M2.5\nMiniMax-M2.5-highspeed";
    dialogs.applyCustomModels();
    expect(dialogs.showCustomNameDialog.value).toBe(true);

    dialogs.customNameDraft.value = "MiniMax Custom";
    dialogs.applyCustomName();

    const customProviders = Array.isArray(store.state.provider.customProviders)
      ? store.state.provider.customProviders
      : [];
    const created = customProviders.find(
      (p: any) => String(p?.name ?? "").trim() === "MiniMax Custom",
    );
    expect(created).toBeTruthy();
    expect(String(created.adapter)).toBe("anthropic");
    expect(String(created.baseUrl)).toBe("https://api.minimaxi.com/anthropic");
    expect(
      Array.isArray(created.models) ? created.models.map((m: any) => String(m?.id ?? "")) : [],
    ).toEqual(["MiniMax-M2.5", "MiniMax-M2.5-highspeed"]);

    const createdId = String(created.id ?? "").trim();
    expect(createdId.startsWith("cp_")).toBe(true);
    expect(String(store.state.provider.activeId)).toBe(createdId);
    expect(String(store.state.provider.activeModelByProvider?.[createdId] ?? "")).toBe(
      "MiniMax-M2.5",
    );
    expect(dialogs.ensureActiveProviderReadyForChat()).toBe(true);
  });

  it("fetches models for official anthropic base url", async () => {
    const originalFetch = globalThis.fetch;

    try {
      const toasts: string[] = [];
      globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
        const request = input instanceof Request ? input : new Request(String(input), init);
        expect(request.url).toBe("https://api.anthropic.com/v1/models");
        expect(request.headers.get("x-api-key")).toBe("anthropic-key");
        expect(request.headers.get("anthropic-version")).toBe("2023-06-01");

        return Response.json({
          data: [{ id: "claude-sonnet-4-5", display_name: "Claude Sonnet 4.5" }],
        });
      }) as typeof globalThis.fetch;

      const store = createStore({ activeId: "openai" });
      const dialogs = useProviderDialogs({
        store,
        nodeLike: false,
        showToast: (text: string) => toasts.push(text),
        openCustomSettings: () => {},
      });

      dialogs.openConnect();
      const addCustomOption = dialogs.providerOptions.value.find(
        (o) => o.detail === "add custom provider",
      );
      expect(addCustomOption).toBeTruthy();
      dialogs.onSelectProvider(addCustomOption?.label ?? null);
      expect(dialogs.showCustomType.value).toBe(true);

      const anthropicOption = dialogs.customTypeOptions.value.find((o) => o.detail === "anthropic");
      expect(anthropicOption).toBeTruthy();
      dialogs.onSelectCustomType(anthropicOption?.label ?? null);
      expect(dialogs.showCustomConnectDialog.value).toBe(true);

      dialogs.customConnectBaseUrl.value = "https://api.anthropic.com";
      dialogs.customConnectApiKeyDraft.value = "anthropic-key";
      dialogs.applyCustomConnect();

      await new Promise((resolve) => setTimeout(resolve, 30));

      expect(dialogs.showCustomModelsDialog.value).toBe(false);
      expect(dialogs.showCustomModelsFetchDialog.value).toBe(true);
      expect(dialogs.customModelsFetchOptions.value).toEqual([
        { label: "Claude Sonnet 4.5", detail: "claude-sonnet-4-5" },
      ]);
      expect(hasManualModelDiscoveryToast(toasts)).toBe(false);
    } finally {
      globalThis.fetch = originalFetch;
    }
  });
});
