/**
 * Tests for the login command
 */
import * as fs from "fs/promises";
import * as os from "os";
import * as path from "path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { getConfigPath, loadConfig } from "../../../cli/config.js";
// Mock os.homedir so getConfigPath resolves to test directories
vi.mock("os", async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        homedir: vi.fn().mockReturnValue(actual.homedir()),
    };
});
// Mock global fetch
const mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
import { loginMain } from "./login.js";
// Mock @clack/prompts for interactive UI in loginMain
vi.mock("@clack/prompts", () => ({
    select: vi.fn(),
    isCancel: vi.fn(() => false),
    cancel: vi.fn(),
    intro: vi.fn(),
    outro: vi.fn(),
    note: vi.fn(),
    spinner: vi.fn(() => ({
        start: vi.fn(),
        stop: vi.fn(),
        message: vi.fn(),
    })),
    log: {
        info: vi.fn(),
        success: vi.fn(),
        warn: vi.fn(),
        error: vi.fn(),
        message: vi.fn(),
        step: vi.fn(),
    },
}));
// Mock loginFlow, confirmAction, and promptPassword for interactive authentication
vi.mock("@/cli/prompts/index.js", () => ({
    loginFlow: vi.fn(),
    confirmAction: vi.fn(),
    promptPassword: vi.fn(),
}));
// Mock Firebase SDK
vi.mock("firebase/auth", () => ({
    signInWithEmailAndPassword: vi.fn(),
    signInWithCredential: vi.fn(),
    GoogleAuthProvider: {
        credential: vi.fn().mockReturnValue({ providerId: "google.com" }),
    },
    AuthErrorCodes: {
        INVALID_PASSWORD: "auth/wrong-password",
        INVALID_LOGIN_CREDENTIALS: "auth/invalid-login-credentials",
        USER_DELETED: "auth/user-not-found",
        TOO_MANY_ATTEMPTS_TRY_LATER: "auth/too-many-requests",
        NETWORK_REQUEST_FAILED: "auth/network-request-failed",
    },
}));
// Mock Firebase provider
vi.mock("@/providers/firebase.js", () => ({
    configureFirebase: vi.fn(),
    getFirebase: vi.fn().mockReturnValue({
        auth: {},
        app: { options: { projectId: "test-project" } },
    }),
}));
// Mock logger to suppress output during tests
vi.mock("@/cli/logger.js", () => ({
    info: vi.fn(),
    success: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
    debug: vi.fn(),
    newline: vi.fn(),
    raw: vi.fn(),
}));
// Mock the open package
vi.mock("open", () => ({
    default: vi.fn(),
}));
// Mock the googleAuth module
vi.mock("./googleAuth.js", () => ({
    AUTH_WARNING_MS: 60 * 1000,
    findAvailablePort: vi.fn(),
    getGoogleAuthUrl: vi.fn(),
    startAuthServer: vi.fn(),
    exchangeCodeForTokens: vi.fn(),
    generateState: vi.fn(),
    validateOAuthCredentials: vi.fn(),
    validateWebOAuthCredentials: vi.fn(),
    isHeadlessEnvironment: vi.fn(),
    GOOGLE_OAUTH_CLIENT_ID: "test-client-id",
    GOOGLE_OAUTH_CLIENT_SECRET: "test-client-secret",
    GOOGLE_OAUTH_WEB_CLIENT_ID: "test-web-client-id",
}));
describe("login command", () => {
    let tempDir;
    beforeEach(async () => {
        tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "login-test-"));
        vi.mocked(os.homedir).mockReturnValue(tempDir);
        vi.clearAllMocks();
        // Restore default isCancel behavior after clearAllMocks removes it
        const clack = await import("@clack/prompts");
        vi.mocked(clack.isCancel).mockReturnValue(false);
    });
    afterEach(async () => {
        await fs.rm(tempDir, { recursive: true, force: true });
    });
    describe("loginMain interactive mode", () => {
        it("should authenticate with Firebase and save credentials to config", async () => {
            const { signInWithEmailAndPassword } = await import("firebase/auth");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            // Mock select to choose email/password
            vi.mocked(clack.select).mockResolvedValue("email");
            // Mock loginFlow to simulate the flow calling the authenticate callback
            vi.mocked(loginFlow).mockImplementation(async (args) => {
                const result = await args.callbacks.onAuthenticate({
                    email: "user@example.com",
                    password: "password123",
                });
                if (!result.success) {
                    return null;
                }
                return {
                    email: "user@example.com",
                    refreshToken: result.refreshToken,
                    idToken: result.idToken,
                    organizations: result.organizations,
                    isAdmin: result.isAdmin,
                    statusMessage: `Logged in as ${result.userEmail}`,
                };
            });
            // Mock Firebase sign in
            vi.mocked(signInWithEmailAndPassword).mockResolvedValue({
                user: {
                    refreshToken: "mock-refresh-token",
                    getIdToken: vi.fn().mockResolvedValue("mock-id-token"),
                },
            });
            // Mock check-access endpoint
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: ["acme", "orderco"],
                    isAdmin: true,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Verify loginFlow was called
            expect(loginFlow).toHaveBeenCalled();
            // Verify config was saved correctly
            const config = await loadConfig();
            expect(config).not.toBeNull();
            expect(config?.auth?.username).toBe("user@example.com");
            expect(config?.auth?.refreshToken).toBe("mock-refresh-token");
            expect(config?.auth?.organizationUrl).toBe("https://noriskillsets.dev");
            expect(config?.auth?.organizations).toEqual(["acme", "orderco"]);
            expect(config?.auth?.isAdmin).toBe(true);
        });
        it("should use provided email and password in non-interactive mode", async () => {
            const { signInWithEmailAndPassword } = await import("firebase/auth");
            // Mock Firebase sign in
            vi.mocked(signInWithEmailAndPassword).mockResolvedValue({
                user: {
                    refreshToken: "mock-refresh-token",
                    getIdToken: vi.fn().mockResolvedValue("mock-id-token"),
                },
            });
            // Mock check-access endpoint
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: ["myorg"],
                    isAdmin: false,
                }),
            });
            await loginMain({
                installDir: tempDir,
                nonInteractive: true,
                email: "test@example.com",
                password: "testpass",
            });
            // Verify Firebase was called with correct credentials
            expect(signInWithEmailAndPassword).toHaveBeenCalledWith(expect.anything(), "test@example.com", "testpass");
            // Verify config was saved
            const config = await loadConfig();
            expect(config?.auth?.username).toBe("test@example.com");
        });
        it("should show error for invalid credentials", async () => {
            const { signInWithEmailAndPassword, AuthErrorCodes } = await import("firebase/auth");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            // Mock select to choose email/password
            vi.mocked(clack.select).mockResolvedValue("email");
            // Mock Firebase to throw invalid credentials error
            const authError = new Error("Invalid credentials");
            authError.code = AuthErrorCodes.INVALID_LOGIN_CREDENTIALS;
            vi.mocked(signInWithEmailAndPassword).mockRejectedValue(authError);
            // Mock loginFlow to call authenticate callback which will fail
            vi.mocked(loginFlow).mockImplementation(async (args) => {
                const result = await args.callbacks.onAuthenticate({
                    email: "user@example.com",
                    password: "wrongpassword",
                });
                // Flow returns null on auth failure
                if (!result.success) {
                    return null;
                }
                return {
                    email: "user@example.com",
                    refreshToken: result.refreshToken,
                    idToken: result.idToken,
                    organizations: result.organizations,
                    isAdmin: result.isAdmin,
                    statusMessage: `Logged in as ${result.userEmail}`,
                };
            });
            await loginMain({ installDir: tempDir });
            // Verify loginFlow was called and returned null (indicating failure)
            expect(loginFlow).toHaveBeenCalled();
            // Verify no config was saved
            const config = await loadConfig();
            expect(config?.auth).toBeUndefined();
        });
        it("should preserve existing config fields when logging in", async () => {
            const { signInWithEmailAndPassword } = await import("firebase/auth");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            // Mock select to choose email/password
            vi.mocked(clack.select).mockResolvedValue("email");
            // Create existing config with agents and settings
            const existingConfigPath = getConfigPath();
            await fs.writeFile(existingConfigPath, JSON.stringify({
                activeSkillset: "senior-swe",
                autoupdate: "enabled",
                installDir: tempDir,
            }));
            // Mock loginFlow
            vi.mocked(loginFlow).mockImplementation(async (args) => {
                const result = await args.callbacks.onAuthenticate({
                    email: "user@example.com",
                    password: "password123",
                });
                if (!result.success) {
                    return null;
                }
                return {
                    email: "user@example.com",
                    refreshToken: result.refreshToken,
                    idToken: result.idToken,
                    organizations: result.organizations,
                    isAdmin: result.isAdmin,
                    statusMessage: `Logged in as ${result.userEmail}`,
                };
            });
            vi.mocked(signInWithEmailAndPassword).mockResolvedValue({
                user: {
                    refreshToken: "mock-refresh-token",
                    getIdToken: vi.fn().mockResolvedValue("mock-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Verify existing fields are preserved
            const config = await loadConfig();
            expect(config?.activeSkillset).toBe("senior-swe");
            expect(config?.autoupdate).toBe("enabled");
            // And new auth was added
            expect(config?.auth?.username).toBe("user@example.com");
        });
        it("should preserve transcriptDestination when logging in", async () => {
            const { signInWithEmailAndPassword } = await import("firebase/auth");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            // Create existing config with transcriptDestination
            const existingConfigPath = getConfigPath();
            await fs.writeFile(existingConfigPath, JSON.stringify({
                activeSkillset: "senior-swe",
                transcriptDestination: "myorg",
                installDir: tempDir,
            }));
            // Mock loginFlow
            vi.mocked(loginFlow).mockImplementation(async (args) => {
                const result = await args.callbacks.onAuthenticate({
                    email: "user@example.com",
                    password: "password123",
                });
                if (!result.success) {
                    return null;
                }
                return {
                    email: "user@example.com",
                    refreshToken: result.refreshToken,
                    idToken: result.idToken,
                    organizations: result.organizations,
                    isAdmin: result.isAdmin,
                    statusMessage: `Logged in as ${result.userEmail}`,
                };
            });
            vi.mocked(signInWithEmailAndPassword).mockResolvedValue({
                user: {
                    refreshToken: "mock-refresh-token",
                    getIdToken: vi.fn().mockResolvedValue("mock-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: ["acme"],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Verify transcriptDestination was preserved
            const config = await loadConfig();
            expect(config?.transcriptDestination).toBe("myorg");
            // And new auth was added
            expect(config?.auth?.username).toBe("user@example.com");
        });
        it("should save auth with empty organizations if check-access fails", async () => {
            const { signInWithEmailAndPassword } = await import("firebase/auth");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            // Mock select to choose email/password
            vi.mocked(clack.select).mockResolvedValue("email");
            // Mock loginFlow
            vi.mocked(loginFlow).mockImplementation(async (args) => {
                const result = await args.callbacks.onAuthenticate({
                    email: "user@example.com",
                    password: "password123",
                });
                if (!result.success) {
                    return null;
                }
                return {
                    email: "user@example.com",
                    refreshToken: result.refreshToken,
                    idToken: result.idToken,
                    organizations: result.organizations,
                    isAdmin: result.isAdmin,
                    statusMessage: `Logged in as ${result.userEmail}`,
                };
            });
            vi.mocked(signInWithEmailAndPassword).mockResolvedValue({
                user: {
                    refreshToken: "mock-refresh-token",
                    getIdToken: vi.fn().mockResolvedValue("mock-id-token"),
                },
            });
            // Mock check-access to fail
            mockFetch.mockResolvedValue({
                ok: false,
                status: 500,
                json: () => Promise.resolve({ error: "Internal server error" }),
            });
            await loginMain({ installDir: tempDir });
            // Verify auth was saved with empty organizations
            const config = await loadConfig();
            expect(config?.auth?.username).toBe("user@example.com");
            expect(config?.auth?.refreshToken).toBe("mock-refresh-token");
            expect(config?.auth?.organizations).toEqual([]);
        });
        it("should handle network failure during check-access gracefully", async () => {
            const { signInWithEmailAndPassword } = await import("firebase/auth");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            // Mock select to choose email/password
            vi.mocked(clack.select).mockResolvedValue("email");
            // Mock loginFlow
            vi.mocked(loginFlow).mockImplementation(async (args) => {
                const result = await args.callbacks.onAuthenticate({
                    email: "user@example.com",
                    password: "password123",
                });
                if (!result.success) {
                    return null;
                }
                return {
                    email: "user@example.com",
                    refreshToken: result.refreshToken,
                    idToken: result.idToken,
                    organizations: result.organizations,
                    isAdmin: result.isAdmin,
                    statusMessage: `Logged in as ${result.userEmail}`,
                };
            });
            vi.mocked(signInWithEmailAndPassword).mockResolvedValue({
                user: {
                    refreshToken: "mock-refresh-token",
                    getIdToken: vi.fn().mockResolvedValue("mock-id-token"),
                },
            });
            // Mock fetch to throw network error
            mockFetch.mockRejectedValue(new Error("Network error"));
            await loginMain({ installDir: tempDir });
            // Verify auth was saved with empty organizations
            const config = await loadConfig();
            expect(config?.auth?.username).toBe("user@example.com");
            expect(config?.auth?.organizations).toEqual([]);
        });
        it("should require email and password in non-interactive mode", async () => {
            const result = await loginMain({
                installDir: tempDir,
                nonInteractive: true,
                // Missing email and password
            });
            expect(result.success).toBe(false);
            expect(result.message).toContain("--email");
        });
        it("should handle login flow cancellation", async () => {
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            // Mock select to choose email/password
            vi.mocked(clack.select).mockResolvedValue("email");
            // Mock loginFlow to return null (cancelled)
            vi.mocked(loginFlow).mockResolvedValue(null);
            await loginMain({ installDir: tempDir });
            // Verify loginFlow was called
            expect(loginFlow).toHaveBeenCalled();
            // Verify no config was saved
            const config = await loadConfig();
            expect(config?.auth).toBeUndefined();
        });
        it("should call loginFlow with callbacks when email auth method selected", async () => {
            const { signInWithEmailAndPassword } = await import("firebase/auth");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            // Mock select to choose email/password
            vi.mocked(clack.select).mockResolvedValue("email");
            // Mock loginFlow
            vi.mocked(loginFlow).mockImplementation(async (args) => {
                const result = await args.callbacks.onAuthenticate({
                    email: "user@example.com",
                    password: "password123",
                });
                if (!result.success) {
                    return null;
                }
                return {
                    email: "user@example.com",
                    refreshToken: result.refreshToken,
                    idToken: result.idToken,
                    organizations: result.organizations,
                    isAdmin: result.isAdmin,
                    statusMessage: `Logged in as user@example.com`,
                };
            });
            vi.mocked(signInWithEmailAndPassword).mockResolvedValue({
                user: {
                    refreshToken: "mock-refresh-token",
                    getIdToken: vi.fn().mockResolvedValue("mock-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Verify loginFlow was called with callbacks (no skipIntro)
            expect(loginFlow).toHaveBeenCalledWith(expect.objectContaining({
                callbacks: expect.any(Object),
            }));
        });
        it("should show select prompt for auth method", async () => {
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            // Mock select to choose email/password
            vi.mocked(clack.select).mockResolvedValue("email");
            // Mock loginFlow to return null
            vi.mocked(loginFlow).mockResolvedValue(null);
            await loginMain({ installDir: tempDir });
            // Verify select was called with email, google, and token options
            expect(clack.select).toHaveBeenCalledWith(expect.objectContaining({
                message: expect.any(String),
                options: expect.arrayContaining([
                    expect.objectContaining({ value: "email" }),
                    expect.objectContaining({ value: "google" }),
                    expect.objectContaining({ value: "token" }),
                ]),
            }));
        });
        it("should handle cancellation at auth method select prompt", async () => {
            const clack = await import("@clack/prompts");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            // Mock select to return cancel symbol
            const cancelSymbol = Symbol.for("cancel");
            vi.mocked(clack.select).mockResolvedValue(cancelSymbol);
            vi.mocked(clack.isCancel).mockReturnValue(true);
            await loginMain({ installDir: tempDir });
            // Verify cancel was called
            expect(clack.cancel).toHaveBeenCalled();
            // Verify loginFlow was NOT called
            expect(loginFlow).not.toHaveBeenCalled();
            // Verify no config was saved
            const config = await loadConfig();
            expect(config?.auth).toBeUndefined();
        });
        it("should use Google SSO flow when user selects google from auth method prompt", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { findAvailablePort, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            // Mock select to choose google SSO
            vi.mocked(clack.select).mockResolvedValue("google");
            // Mock Google OAuth flow
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false);
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "code",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "google-id-token",
                accessToken: "access-token",
            });
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "google-refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: ["org1"],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Verify loginFlow was NOT called (SSO path, not email/password)
            expect(loginFlow).not.toHaveBeenCalled();
            // Verify Google OAuth flow was used
            expect(findAvailablePort).toHaveBeenCalled();
            expect(startAuthServer).toHaveBeenCalled();
            // Verify config was saved
            const config = await loadConfig();
            expect(config?.auth?.username).toBe("user@gmail.com");
        });
        it("should use confirmAction for headless detection when SSO selected via interactive UI", async () => {
            const { signInWithCredential, GoogleAuthProvider } = await import("firebase/auth");
            const { confirmAction, promptPassword } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { getGoogleAuthUrl, generateState, isHeadlessEnvironment } = await import("./googleAuth.js");
            // Mock select to choose google SSO
            vi.mocked(clack.select).mockResolvedValue("google");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(true);
            vi.mocked(confirmAction).mockResolvedValue(true);
            vi.mocked(promptPassword).mockResolvedValue("id-token-from-server");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Verify confirmAction was used
            expect(confirmAction).toHaveBeenCalledWith(expect.objectContaining({
                message: expect.stringContaining("headless"),
            }));
            // Verify headless flow was used
            expect(GoogleAuthProvider.credential).toHaveBeenCalledWith("id-token-from-server");
        });
        it("should use confirmAction for headless when --google flag is used", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { confirmAction, promptPassword } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { getGoogleAuthUrl, generateState, isHeadlessEnvironment } = await import("./googleAuth.js");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(true);
            vi.mocked(confirmAction).mockResolvedValue(true);
            vi.mocked(promptPassword).mockResolvedValue("id-token-from-server");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({
                installDir: tempDir,
                google: true,
            });
            // Verify confirmAction was used
            expect(confirmAction).toHaveBeenCalledWith(expect.objectContaining({
                message: expect.stringContaining("headless"),
            }));
            // Verify select was NOT called (--google flag bypasses the select prompt)
            expect(clack.select).not.toHaveBeenCalled();
        });
        it("should show error when --no-localhost is used without --google", async () => {
            const result = await loginMain({
                installDir: tempDir,
                noLocalhost: true,
            });
            // Should return failure with --no-localhost error
            expect(result.success).toBe(false);
            expect(result.message).toContain("--no-localhost");
            // No config should be saved
            const config = await loadConfig();
            expect(config?.auth).toBeUndefined();
        });
    });
    describe("loginMain with --google", () => {
        it("should authenticate via Google SSO and save credentials to config", async () => {
            const { signInWithCredential, GoogleAuthProvider } = await import("firebase/auth");
            const { findAvailablePort, getGoogleAuthUrl, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            // Mock the Google OAuth flow
            vi.mocked(generateState).mockReturnValue("test-state-nonce");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/o/oauth2/v2/auth?fake=true");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false); // Not headless
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "google-auth-code-123",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "google-id-token-abc",
                accessToken: "google-access-token-xyz",
            });
            // Mock Firebase signInWithCredential
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "firebase-refresh-token-from-google",
                    email: "googleuser@gmail.com",
                    getIdToken: vi
                        .fn()
                        .mockResolvedValue("firebase-id-token-from-google"),
                },
            });
            // Mock check-access endpoint
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: ["google-org"],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true });
            // Verify Google OAuth flow was called
            expect(findAvailablePort).toHaveBeenCalled();
            expect(startAuthServer).toHaveBeenCalledWith(expect.objectContaining({
                port: 9876,
                expectedState: "test-state-nonce",
            }));
            expect(exchangeCodeForTokens).toHaveBeenCalledWith(expect.objectContaining({
                code: "google-auth-code-123",
            }));
            // Verify Firebase signInWithCredential was called with Google credential
            expect(GoogleAuthProvider.credential).toHaveBeenCalledWith("google-id-token-abc");
            expect(signInWithCredential).toHaveBeenCalled();
            // Verify config was saved correctly with Google user's email
            const config = await loadConfig();
            expect(config).not.toBeNull();
            expect(config?.auth?.username).toBe("googleuser@gmail.com");
            expect(config?.auth?.refreshToken).toBe("firebase-refresh-token-from-google");
            expect(config?.auth?.organizationUrl).toBe("https://noriskillsets.dev");
            expect(config?.auth?.organizations).toEqual(["google-org"]);
        });
        it("should not prompt for email or password when --google is used", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { loginFlow } = await import("../../../cli/prompts/index.js");
            const { findAvailablePort, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false); // Not headless
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "code",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "id-token",
                accessToken: "access-token",
            });
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true });
            // loginFlow should NOT have been called (SSO path, not email/password)
            expect(loginFlow).not.toHaveBeenCalled();
        });
        it("should show error when --google is used with --email", async () => {
            const result = await loginMain({
                installDir: tempDir,
                google: true,
                email: "user@example.com",
            });
            expect(result.success).toBe(false);
            expect(result.message).toContain("--google");
            // No config should be saved
            const config = await loadConfig();
            expect(config?.auth).toBeUndefined();
        });
        it("should show error when --google is used with --password", async () => {
            const result = await loginMain({
                installDir: tempDir,
                google: true,
                password: "secret",
            });
            expect(result.success).toBe(false);
            expect(result.message).toContain("--google");
        });
        it("should show error when --google is used with --non-interactive", async () => {
            const result = await loginMain({
                installDir: tempDir,
                google: true,
                nonInteractive: true,
            });
            expect(result.success).toBe(false);
            expect(result.message).toContain("--non-interactive");
            // No config should be saved
            const config = await loadConfig();
            expect(config?.auth).toBeUndefined();
        });
        it("should handle auth server timeout gracefully", async () => {
            const clack = await import("@clack/prompts");
            const { findAvailablePort, startAuthServer, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false); // Not headless
            vi.mocked(startAuthServer).mockRejectedValue(new Error("Authentication timed out"));
            await loginMain({ installDir: tempDir, google: true });
            expect(clack.log.error).toHaveBeenCalledWith(expect.stringContaining("Authentication failed"));
            // No config should be saved
            const config = await loadConfig();
            expect(config?.auth).toBeUndefined();
        });
        it("should handle Firebase signInWithCredential failure", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const clack = await import("@clack/prompts");
            const { findAvailablePort, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false); // Not headless
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "code",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "google-id-token",
                accessToken: "access-token",
            });
            // Firebase rejects the credential
            const authError = new Error("Google sign-in not enabled");
            authError.code = "auth/operation-not-allowed";
            vi.mocked(signInWithCredential).mockRejectedValue(authError);
            await loginMain({ installDir: tempDir, google: true });
            expect(clack.log.error).toHaveBeenCalledWith(expect.stringContaining("Authentication failed"));
        });
        it("should preserve existing config fields when logging in via Google", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { findAvailablePort, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            // Create existing config with agents and settings
            const existingConfigPath = getConfigPath();
            await fs.writeFile(existingConfigPath, JSON.stringify({
                activeSkillset: "senior-swe",
                autoupdate: "enabled",
                installDir: tempDir,
            }));
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false); // Not headless
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "code",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "google-id-token",
                accessToken: "access-token",
            });
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "google-refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true });
            const config = await loadConfig();
            expect(config?.activeSkillset).toBe("senior-swe");
            expect(config?.autoupdate).toBe("enabled");
            expect(config?.auth?.username).toBe("user@gmail.com");
        });
        it("should always display auth URL before opening browser", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const clack = await import("@clack/prompts");
            const { findAvailablePort, getGoogleAuthUrl, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            const testAuthUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=test";
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(getGoogleAuthUrl).mockReturnValue(testAuthUrl);
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false);
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "code",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "id-token",
                accessToken: "access-token",
            });
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true });
            // Verify the auth URL was displayed via log.step
            expect(clack.log.step).toHaveBeenCalledWith(expect.stringContaining(testAuthUrl));
        });
        it("should prompt user in headless environment and use headless flow when confirmed", async () => {
            const { signInWithCredential, GoogleAuthProvider } = await import("firebase/auth");
            const { confirmAction, promptPassword } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { getGoogleAuthUrl, generateState, isHeadlessEnvironment } = await import("./googleAuth.js");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(true); // Simulate SSH environment
            vi.mocked(confirmAction).mockResolvedValue(true); // User confirms headless flow
            vi.mocked(promptPassword).mockResolvedValue("id-token-from-server");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true });
            // Verify user was prompted about headless flow via confirmAction
            expect(confirmAction).toHaveBeenCalledWith(expect.objectContaining({
                message: expect.stringContaining("headless"),
            }));
            // Verify headless flow was used (promptPassword for token)
            expect(promptPassword).toHaveBeenCalledWith(expect.objectContaining({
                message: expect.stringContaining("token"),
            }));
            // Verify GoogleAuthProvider.credential was called with the pasted token
            expect(GoogleAuthProvider.credential).toHaveBeenCalledWith("id-token-from-server");
            // Verify headless environment warning was shown via clack
            expect(clack.log.warn).toHaveBeenCalledWith(expect.stringContaining("headless"));
        });
        it("should display SSH port forwarding instructions when user declines headless flow", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { confirmAction } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { findAvailablePort, getGoogleAuthUrl, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(true); // Simulate SSH environment
            vi.mocked(confirmAction).mockResolvedValue(false); // User declines headless flow
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "code",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "id-token",
                accessToken: "access-token",
            });
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true });
            // Verify SSH port forwarding instructions were displayed in a note
            const noteCalls = vi.mocked(clack.note).mock.calls;
            const portForwardingNote = noteCalls.find((call) => typeof call[0] === "string" &&
                call[0].includes("ssh") &&
                call[0].includes("9876"));
            expect(portForwardingNote).toBeDefined();
        });
    });
    describe("loginMain with --google --no-localhost", () => {
        it("should use web client ID and noriskillsets.dev callback URL when --no-localhost is set", async () => {
            const { signInWithCredential, GoogleAuthProvider } = await import("firebase/auth");
            const { promptPassword } = await import("../../../cli/prompts/index.js");
            const { getGoogleAuthUrl, generateState } = await import("./googleAuth.js");
            vi.mocked(generateState).mockReturnValue("test-state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test?redirect_uri=https://noriskillsets.dev/oauth/callback");
            // User pastes the id_token from the server (not an auth code)
            vi.mocked(promptPassword).mockResolvedValue("id-token-from-server-page");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh-token",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true, noLocalhost: true });
            // Verify getGoogleAuthUrl was called with web client ID and noriskillsets.dev redirect URI
            expect(getGoogleAuthUrl).toHaveBeenCalledWith(expect.objectContaining({
                clientId: "test-web-client-id",
                redirectUri: "https://noriskillsets.dev/oauth/callback",
            }));
            // Verify user was prompted to paste the token with masked input
            expect(promptPassword).toHaveBeenCalledWith(expect.objectContaining({
                message: expect.stringContaining("token"),
            }));
            // Verify GoogleAuthProvider.credential was called with the pasted token directly
            expect(GoogleAuthProvider.credential).toHaveBeenCalledWith("id-token-from-server-page");
            // Verify config was saved
            const config = await loadConfig();
            expect(config?.auth?.username).toBe("user@gmail.com");
        });
        it("should not start local auth server or exchange tokens when --no-localhost is set", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { getGoogleAuthUrl, exchangeCodeForTokens, generateState, startAuthServer, findAvailablePort, } = await import("./googleAuth.js");
            const { promptPassword } = await import("../../../cli/prompts/index.js");
            vi.mocked(generateState).mockReturnValue("test-state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(promptPassword).mockResolvedValue("id-token-from-server");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh-token",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true, noLocalhost: true });
            // Verify local server functions were NOT called
            expect(findAvailablePort).not.toHaveBeenCalled();
            expect(startAuthServer).not.toHaveBeenCalled();
            // Verify token exchange was NOT called (server handles this)
            expect(exchangeCodeForTokens).not.toHaveBeenCalled();
        });
        it("should show error when --no-localhost is used without --google", async () => {
            const result = await loginMain({
                installDir: tempDir,
                noLocalhost: true,
            });
            // Should return failure with --no-localhost error
            expect(result.success).toBe(false);
            expect(result.message).toContain("--no-localhost");
            // No config should be saved
            const config = await loadConfig();
            expect(config?.auth).toBeUndefined();
        });
        it("should display instructions to copy the token from the callback page", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { promptPassword } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { getGoogleAuthUrl, generateState } = await import("./googleAuth.js");
            vi.mocked(generateState).mockReturnValue("test-state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(promptPassword).mockResolvedValue("id-token");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh-token",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true, noLocalhost: true });
            // Verify instructions were displayed in a note
            const noteCalls = vi.mocked(clack.note).mock.calls;
            const hasInstructions = noteCalls.some((call) => typeof call[0] === "string" &&
                (call[0].includes("Copy") || call[0].includes("token")));
            expect(hasInstructions).toBe(true);
        });
        it("should handle empty token input gracefully", async () => {
            const clack = await import("@clack/prompts");
            const { getGoogleAuthUrl, generateState } = await import("./googleAuth.js");
            const { promptPassword } = await import("../../../cli/prompts/index.js");
            vi.mocked(generateState).mockReturnValue("test-state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(promptPassword).mockResolvedValue(""); // Empty input
            await loginMain({ installDir: tempDir, google: true, noLocalhost: true });
            expect(clack.log.error).toHaveBeenCalledWith(expect.stringContaining("token"));
            // No config should be saved
            const config = await loadConfig();
            expect(config?.auth).toBeUndefined();
        });
        it("should validate web OAuth credentials for headless flow", async () => {
            const { validateWebOAuthCredentials } = await import("./googleAuth.js");
            const { promptPassword } = await import("../../../cli/prompts/index.js");
            const { signInWithCredential } = await import("firebase/auth");
            vi.mocked(promptPassword).mockResolvedValue("id-token");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh-token",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir, google: true, noLocalhost: true });
            // Verify web OAuth credentials were validated
            expect(validateWebOAuthCredentials).toHaveBeenCalled();
        });
    });
    describe("clack output for Google SSO flows", () => {
        it("should show auth URL via log.step when using headless flow", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { confirmAction, promptPassword } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { getGoogleAuthUrl, generateState, isHeadlessEnvironment } = await import("./googleAuth.js");
            vi.mocked(clack.select).mockResolvedValue("google");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test-auth-url");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(true);
            vi.mocked(confirmAction).mockResolvedValue(true);
            vi.mocked(promptPassword).mockResolvedValue("id-token-from-server");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Auth URL should be shown via log.step for wrapping/clickability, not in a note box
            expect(clack.log.step).toHaveBeenCalledWith(expect.stringContaining("https://accounts.google.com/test-auth-url"));
        });
        it("should use promptPassword for token input in headless flow", async () => {
            const { signInWithCredential, GoogleAuthProvider } = await import("firebase/auth");
            const { confirmAction, promptPassword } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { getGoogleAuthUrl, generateState, isHeadlessEnvironment } = await import("./googleAuth.js");
            vi.mocked(clack.select).mockResolvedValue("google");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(true);
            vi.mocked(confirmAction).mockResolvedValue(true);
            vi.mocked(promptPassword).mockResolvedValue("clack-id-token");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Should use clack promptPassword for token input
            expect(promptPassword).toHaveBeenCalledWith(expect.objectContaining({
                message: expect.stringContaining("token"),
            }));
            // Token should be passed to Firebase
            expect(GoogleAuthProvider.credential).toHaveBeenCalledWith("clack-id-token");
        });
        it("should use spinner for signing in during headless flow", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { confirmAction, promptPassword } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { getGoogleAuthUrl, generateState, isHeadlessEnvironment } = await import("./googleAuth.js");
            const spinnerMock = {
                start: vi.fn(),
                stop: vi.fn(),
                message: vi.fn(),
            };
            vi.mocked(clack.spinner).mockReturnValue(spinnerMock);
            vi.mocked(clack.select).mockResolvedValue("google");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(true);
            vi.mocked(confirmAction).mockResolvedValue(true);
            vi.mocked(promptPassword).mockResolvedValue("id-token");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Should use spinner for "Signing in..."
            expect(spinnerMock.start).toHaveBeenCalledWith(expect.stringContaining("Signing in"));
            expect(spinnerMock.stop).toHaveBeenCalled();
        });
        it("should show auth URL via log.step for localhost flow", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const clack = await import("@clack/prompts");
            const { findAvailablePort, getGoogleAuthUrl, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            vi.mocked(clack.select).mockResolvedValue("google");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/localhost-auth-url");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false);
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "code",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "google-id-token",
                accessToken: "access-token",
            });
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Auth URL should be shown via log.step for wrapping/clickability, not in a note box
            expect(clack.log.step).toHaveBeenCalledWith(expect.stringContaining("https://accounts.google.com/localhost-auth-url"));
        });
        it("should use spinner for exchanging authorization code in localhost flow", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const clack = await import("@clack/prompts");
            const { findAvailablePort, getGoogleAuthUrl, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            const spinnerMock = {
                start: vi.fn(),
                stop: vi.fn(),
                message: vi.fn(),
            };
            vi.mocked(clack.spinner).mockReturnValue(spinnerMock);
            vi.mocked(clack.select).mockResolvedValue("google");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false);
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "code",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "google-id-token",
                accessToken: "access-token",
            });
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Should use spinner for exchanging code
            expect(spinnerMock.start).toHaveBeenCalledWith(expect.stringContaining("Exchanging"));
        });
        it("should show headless detection warning via clack log.warn", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { confirmAction, promptPassword } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { getGoogleAuthUrl, generateState, isHeadlessEnvironment } = await import("./googleAuth.js");
            vi.mocked(clack.select).mockResolvedValue("google");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(true);
            vi.mocked(confirmAction).mockResolvedValue(true);
            vi.mocked(promptPassword).mockResolvedValue("id-token");
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Headless detection should use clack log.warn
            expect(clack.log.warn).toHaveBeenCalledWith(expect.stringContaining("headless"));
        });
        it("should show port forwarding instructions in a note when user declines headless", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const { confirmAction } = await import("../../../cli/prompts/index.js");
            const clack = await import("@clack/prompts");
            const { findAvailablePort, getGoogleAuthUrl, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            vi.mocked(clack.select).mockResolvedValue("google");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(true);
            vi.mocked(confirmAction).mockResolvedValue(false); // Decline headless
            vi.mocked(startAuthServer).mockResolvedValue({
                code: "code",
                server: { close: vi.fn() },
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "id-token",
                accessToken: "access-token",
            });
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Port forwarding instructions should be in a note
            const noteCalls = vi.mocked(clack.note).mock.calls;
            const portForwardingNote = noteCalls.find((call) => typeof call[0] === "string" &&
                call[0].includes("ssh") &&
                call[0].includes("9876"));
            expect(portForwardingNote).toBeDefined();
        });
        it("should use clack log.warn for timeout warning in localhost flow", async () => {
            const { signInWithCredential } = await import("firebase/auth");
            const clack = await import("@clack/prompts");
            const { findAvailablePort, getGoogleAuthUrl, startAuthServer, exchangeCodeForTokens, generateState, isHeadlessEnvironment, } = await import("./googleAuth.js");
            vi.mocked(clack.select).mockResolvedValue("google");
            vi.mocked(generateState).mockReturnValue("state");
            vi.mocked(findAvailablePort).mockResolvedValue(9876);
            vi.mocked(getGoogleAuthUrl).mockReturnValue("https://accounts.google.com/test");
            vi.mocked(isHeadlessEnvironment).mockReturnValue(false);
            // Capture the onTimeoutWarning callback and invoke it
            vi.mocked(startAuthServer).mockImplementation(async (args) => {
                if (args.onTimeoutWarning) {
                    args.onTimeoutWarning();
                }
                return {
                    code: "code",
                    server: { close: vi.fn() },
                };
            });
            vi.mocked(exchangeCodeForTokens).mockResolvedValue({
                idToken: "google-id-token",
                accessToken: "access-token",
            });
            vi.mocked(signInWithCredential).mockResolvedValue({
                user: {
                    refreshToken: "refresh",
                    email: "user@gmail.com",
                    getIdToken: vi.fn().mockResolvedValue("firebase-id-token"),
                },
            });
            mockFetch.mockResolvedValue({
                ok: true,
                json: () => Promise.resolve({
                    authorized: true,
                    organizations: [],
                    isAdmin: false,
                }),
            });
            await loginMain({ installDir: tempDir });
            // Timeout warning should use clack log.warn
            expect(clack.log.warn).toHaveBeenCalledWith(expect.stringContaining("timeout"));
        });
    });
});
describe("loginMain interactive API token flow", () => {
    let tempDir;
    const VALID_TOKEN = `nori_acme_${"a".repeat(64)}`;
    const PUBLIC_TOKEN = `nori_public_${"b".repeat(64)}`;
    beforeEach(async () => {
        tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "login-token-itx-"));
        vi.mocked(os.homedir).mockReturnValue(tempDir);
        vi.clearAllMocks();
        const clack = await import("@clack/prompts");
        vi.mocked(clack.isCancel).mockReturnValue(false);
    });
    afterEach(async () => {
        await fs.rm(tempDir, { recursive: true, force: true });
    });
    it("should prompt for token and save config when user selects 'token' with a valid token", async () => {
        const clack = await import("@clack/prompts");
        const { promptPassword } = await import("../../../cli/prompts/index.js");
        vi.mocked(clack.select).mockResolvedValue("token");
        vi.mocked(promptPassword).mockResolvedValue(VALID_TOKEN);
        const result = await loginMain({ installDir: tempDir });
        expect(result.success).toBe(true);
        const config = await loadConfig();
        expect(config?.auth?.apiToken).toBe(VALID_TOKEN);
        expect(config?.auth?.organizationUrl).toBe("https://acme.noriskillsets.dev");
        expect(config?.auth?.organizations).toEqual(["acme"]);
        expect(config?.auth?.refreshToken ?? null).toBeNull();
        expect(config?.auth?.password ?? null).toBeNull();
        expect(config?.auth?.username ?? null).toBeNull();
    });
    it("should prompt for token and save public registry config when token embeds public org", async () => {
        const clack = await import("@clack/prompts");
        const { promptPassword } = await import("../../../cli/prompts/index.js");
        vi.mocked(clack.select).mockResolvedValue("token");
        vi.mocked(promptPassword).mockResolvedValue(PUBLIC_TOKEN);
        const result = await loginMain({ installDir: tempDir });
        expect(result.success).toBe(true);
        const config = await loadConfig();
        expect(config?.auth?.apiToken).toBe(PUBLIC_TOKEN);
        expect(config?.auth?.organizationUrl).toBe("https://noriskillsets.dev");
        expect(config?.auth?.organizations).toEqual(["public"]);
        expect(config?.auth?.refreshToken ?? null).toBeNull();
        expect(config?.auth?.password ?? null).toBeNull();
        expect(config?.auth?.username ?? null).toBeNull();
    });
});
describe("loginMain with --token", () => {
    let tempDir;
    beforeEach(async () => {
        tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "login-token-test-"));
        vi.mocked(os.homedir).mockReturnValue(tempDir);
        vi.clearAllMocks();
        const clack = await import("@clack/prompts");
        vi.mocked(clack.isCancel).mockReturnValue(false);
    });
    afterEach(async () => {
        await fs.rm(tempDir, { recursive: true, force: true });
    });
    const VALID_TOKEN = `nori_acme_${"a".repeat(64)}`;
    const PUBLIC_TOKEN = `nori_public_${"b".repeat(64)}`;
    it("should save apiToken + derived organizationUrl to config (org parsed from token)", async () => {
        const result = await loginMain({
            installDir: tempDir,
            token: VALID_TOKEN,
        });
        expect(result.success).toBe(true);
        const config = await loadConfig();
        expect(config?.auth?.apiToken).toBe(VALID_TOKEN);
        expect(config?.auth?.organizationUrl).toBe("https://acme.noriskillsets.dev");
        // Firebase fields should be cleared
        expect(config?.auth?.refreshToken ?? null).toBeNull();
        expect(config?.auth?.password ?? null).toBeNull();
        expect(config?.auth?.username ?? null).toBeNull();
    });
    it("should save public apiToken + apex organizationUrl to config", async () => {
        const result = await loginMain({
            installDir: tempDir,
            token: PUBLIC_TOKEN,
        });
        expect(result.success).toBe(true);
        const config = await loadConfig();
        expect(config?.auth?.apiToken).toBe(PUBLIC_TOKEN);
        expect(config?.auth?.organizationUrl).toBe("https://noriskillsets.dev");
        expect(config?.auth?.organizations).toEqual(["public"]);
        expect(config?.auth?.refreshToken ?? null).toBeNull();
        expect(config?.auth?.password ?? null).toBeNull();
        expect(config?.auth?.username ?? null).toBeNull();
    });
    it("should reject malformed --token (missing org segment)", async () => {
        const result = await loginMain({
            installDir: tempDir,
            token: `nori_${"a".repeat(64)}`,
        });
        expect(result.success).toBe(false);
        expect(result.message).toMatch(/nori_/);
        const config = await loadConfig();
        expect(config?.auth).toBeUndefined();
    });
    it("should reject completely malformed --token", async () => {
        const result = await loginMain({
            installDir: tempDir,
            token: "notatoken",
        });
        expect(result.success).toBe(false);
        expect(result.message).toMatch(/nori_/);
        const config = await loadConfig();
        expect(config?.auth).toBeUndefined();
    });
    it("should reject --token combined with --email", async () => {
        const result = await loginMain({
            installDir: tempDir,
            token: VALID_TOKEN,
            email: "user@example.com",
        });
        expect(result.success).toBe(false);
        expect(result.message).toMatch(/--token/);
    });
    it("should reject --token combined with --password", async () => {
        const result = await loginMain({
            installDir: tempDir,
            token: VALID_TOKEN,
            password: "secret",
        });
        expect(result.success).toBe(false);
        expect(result.message).toMatch(/--token/);
    });
    it("should reject --token combined with --google", async () => {
        const result = await loginMain({
            installDir: tempDir,
            token: VALID_TOKEN,
            google: true,
        });
        expect(result.success).toBe(false);
        expect(result.message).toMatch(/--token/);
    });
    it("should not call Firebase when using --token", async () => {
        const { signInWithEmailAndPassword } = await import("firebase/auth");
        await loginMain({
            installDir: tempDir,
            token: VALID_TOKEN,
        });
        expect(signInWithEmailAndPassword).not.toHaveBeenCalled();
    });
    it("should warn that Firebase session was cleared when --token overwrites an existing session", async () => {
        const clack = await import("@clack/prompts");
        const { saveTestingConfig } = await import("../../../cli/test-utils/config.js");
        // Seed a Firebase-style session
        await saveTestingConfig({
            username: "user@example.com",
            refreshToken: "existing-refresh",
            organizationUrl: "https://noriskillsets.dev",
            installDir: tempDir,
        });
        await loginMain({
            installDir: tempDir,
            token: VALID_TOKEN,
        });
        expect(clack.log.warn).toHaveBeenCalledWith(expect.stringContaining("Firebase"));
    });
});
//# sourceMappingURL=login.test.js.map