import { act, renderHook, waitFor } from "@testing-library/react";
import { toast } from "sonner";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useSession } from "@/lib/auth/auth.query";
import { useMcpInstallOrchestrator } from "./mcp-install-orchestrator.hook";

const {
  mutateAsyncMock,
  openDialogMock,
  redirectBrowserToUrlMock,
  setOAuthCatalogIdMock,
  setOAuthMcpServerIdMock,
  setOAuthReturnUrlMock,
  setOAuthStateMock,
  setOAuthTeamIdMock,
} = vi.hoisted(() => ({
  mutateAsyncMock: vi.fn(),
  openDialogMock: vi.fn(),
  redirectBrowserToUrlMock: vi.fn(),
  setOAuthCatalogIdMock: vi.fn(),
  setOAuthMcpServerIdMock: vi.fn(),
  setOAuthReturnUrlMock: vi.fn(),
  setOAuthStateMock: vi.fn(),
  setOAuthTeamIdMock: vi.fn(),
}));

vi.mock("@/lib/mcp/internal-mcp-catalog.query", () => ({
  useInternalMcpCatalog: () => ({
    data: [
      {
        id: "catalog-posthog",
        name: "PostHog",
        serverType: "remote",
        oauthConfig: { clientId: "client-123" },
      },
    ],
  }),
}));

vi.mock("@/lib/mcp/mcp-server.query", () => ({
  useMcpServers: () => ({ data: [] }),
  useInstallMcpServer: () => ({ mutateAsync: vi.fn(), isPending: false }),
  useReauthenticateMcpServer: () => ({
    mutateAsync: vi.fn(),
    isPending: false,
  }),
}));

vi.mock("@/lib/auth/auth.query");

vi.mock("sonner");

vi.mock("@/lib/auth/oauth.query", () => ({
  useInitiateOAuth: () => ({
    mutateAsync: mutateAsyncMock,
  }),
}));

vi.mock("@/lib/hooks/use-dialog", () => ({
  useDialogs: () => ({
    isDialogOpened: () => false,
    openDialog: openDialogMock,
    closeDialog: vi.fn(),
  }),
}));

vi.mock("@/lib/utils/browser-redirect", () => ({
  redirectBrowserToUrl: redirectBrowserToUrlMock,
}));

vi.mock("@/lib/auth/oauth-session", () => ({
  clearPendingAfterEnvVars: vi.fn(),
  getOAuthPendingAfterEnvVars: vi.fn(() => false),
  setOAuthCatalogId: setOAuthCatalogIdMock,
  setOAuthEnvironmentValues: vi.fn(),
  setOAuthIsFirstInstallation: vi.fn(),
  setOAuthMcpServerId: setOAuthMcpServerIdMock,
  setOAuthPendingAfterEnvVars: vi.fn(),
  setOAuthReturnUrl: setOAuthReturnUrlMock,
  setOAuthScope: vi.fn(),
  setOAuthServerType: vi.fn(),
  setOAuthState: setOAuthStateMock,
  setOAuthTeamId: setOAuthTeamIdMock,
  setOAuthUserConfigValues: vi.fn(),
}));

describe("useMcpInstallOrchestrator", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(useSession).mockReturnValue({
      data: { user: { id: "test-user" } },
    } as unknown as ReturnType<typeof useSession>);
    mutateAsyncMock.mockResolvedValue({
      authorizationUrl: "https://posthog.example.com/oauth/authorize",
      state: "oauth-state-123",
    });
  });

  it("starts OAuth immediately for pure OAuth re-authentication", async () => {
    const { result } = renderHook(() => useMcpInstallOrchestrator());

    act(() => {
      result.current.triggerReauthByCatalogIdAndServerId(
        "catalog-posthog",
        "server-123",
      );
    });

    await waitFor(() => {
      expect(mutateAsyncMock).toHaveBeenCalledWith({
        catalogId: "catalog-posthog",
      });
    });

    expect(openDialogMock).not.toHaveBeenCalled();
    expect(setOAuthStateMock).toHaveBeenCalledWith("oauth-state-123");
    expect(setOAuthCatalogIdMock).toHaveBeenCalledWith("catalog-posthog");
    expect(setOAuthTeamIdMock).toHaveBeenCalledWith(null);
    expect(setOAuthMcpServerIdMock).toHaveBeenCalledWith("server-123");
    expect(setOAuthReturnUrlMock).toHaveBeenCalledWith(window.location.href);
    expect(redirectBrowserToUrlMock).toHaveBeenCalledWith(
      "https://posthog.example.com/oauth/authorize",
    );
  });

  it("captures the return URL when starting OAuth for a first-time install", async () => {
    const { result } = renderHook(() => useMcpInstallOrchestrator());

    // Opening the OAuth confirmation dialog should not start OAuth yet.
    act(() => {
      result.current.triggerInstallByCatalogId("catalog-posthog");
    });
    expect(redirectBrowserToUrlMock).not.toHaveBeenCalled();

    await act(async () => {
      await result.current.handleOAuthConfirm({
        scope: "personal",
        teamId: null,
      });
    });

    await waitFor(() => {
      expect(mutateAsyncMock).toHaveBeenCalledWith({
        catalogId: "catalog-posthog",
      });
    });

    // New behavior: first-time installs remember where they started so the
    // callback can return the user there (e.g. a chat conversation) instead of
    // the registry. This is not a re-auth flow.
    expect(setOAuthReturnUrlMock).toHaveBeenCalledWith(window.location.href);
    expect(setOAuthMcpServerIdMock).not.toHaveBeenCalled();
    expect(redirectBrowserToUrlMock).toHaveBeenCalledWith(
      "https://posthog.example.com/oauth/authorize",
    );
  });

  it("surfaces the backend error message when initiating OAuth fails", async () => {
    mutateAsyncMock.mockRejectedValue(new Error("No client ID available"));

    const { result } = renderHook(() => useMcpInstallOrchestrator());

    act(() => {
      result.current.triggerReauthByCatalogIdAndServerId(
        "catalog-posthog",
        "server-123",
      );
    });

    await waitFor(() => {
      expect(toast.error).toHaveBeenCalledWith("No client ID available");
    });
    expect(redirectBrowserToUrlMock).not.toHaveBeenCalled();
  });
});
