import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// Mock dependencies
const mockStartOAuthServer = vi.fn();
const mockSaveTokens = vi.fn();
const mockLoadTokens = vi.fn();
const mockExchangeCodeViaGateway = vi.fn();
const mockManualCodeEntry = vi.fn();
vi.mock("../oauth-server.js", () => ({
    startOAuthServer: (...args) => mockStartOAuthServer(...args),
}));
vi.mock("../token-storage.js", () => ({
    saveTokens: (...args) => mockSaveTokens(...args),
    loadTokens: (...args) => mockLoadTokens(...args),
}));
vi.mock("../gateway-auth.js", () => ({
    exchangeCodeViaGateway: (...args) => mockExchangeCodeViaGateway(...args),
}));
vi.mock("../manual-flow.js", () => ({
    manualCodeEntry: (...args) => mockManualCodeEntry(...args),
}));
vi.mock("../../utils/logger.js", () => ({
    logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
// Mock crypto.randomUUID to produce deterministic UUIDs
vi.mock("node:crypto", async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        randomUUID: () => "generated-uuid-1234-5678-abcdef",
    };
});
import { login, setOpenBrowser, resetOpenBrowser, setExchangeCodeFn, setGetOAuthUrlFn, } from "../login.js";
describe("login-manager", () => {
    let capturedBrowserUrl = null;
    let mockOAuthServer;
    const mockGetOAuthUrlFn = vi.fn();
    beforeEach(() => {
        vi.clearAllMocks();
        capturedBrowserUrl = null;
        // Set up mock browser opener
        setOpenBrowser((url) => {
            capturedBrowserUrl = url;
        });
        // Wire up OAuth URL provider
        setGetOAuthUrlFn(mockGetOAuthUrlFn);
        // Default mock OAuth server
        mockOAuthServer = {
            port: 12345,
            callbackUrl: "http://127.0.0.1:12345/auth/callback",
            waitForCallback: vi.fn().mockResolvedValue({ code: "test-auth-code" }),
            close: vi.fn(),
        };
        mockStartOAuthServer.mockResolvedValue(mockOAuthServer);
        // Default mock OAuth URL
        mockGetOAuthUrlFn.mockResolvedValue("https://auth.example.com/authorize?provider=github&redirect_to=http://127.0.0.1:12345/auth/callback");
        // Default mock Gateway exchange
        mockExchangeCodeViaGateway.mockResolvedValue({
            access_token: "access-token-123",
            refresh_token: "refresh-token-456",
            expires_at: 1707666000,
            user: {
                id: "user-789",
                email: "test@example.com",
                github_username: "testuser",
            },
        });
        // Default: no existing tokens
        mockLoadTokens.mockResolvedValue(null);
        mockSaveTokens.mockResolvedValue(undefined);
    });
    afterEach(() => {
        resetOpenBrowser();
    });
    it("should complete login flow via Gateway", async () => {
        const result = await login();
        // Verify OAuth server started
        expect(mockStartOAuthServer).toHaveBeenCalledOnce();
        // Verify OAuth URL retrieved with callback URL
        expect(mockGetOAuthUrlFn).toHaveBeenCalledWith("http://127.0.0.1:12345/auth/callback");
        // Verify browser opened
        expect(capturedBrowserUrl).toContain("auth.example.com");
        // Verify waited for callback
        expect(mockOAuthServer.waitForCallback).toHaveBeenCalledOnce();
        // Verify code exchanged via Gateway (not direct Supabase)
        expect(mockExchangeCodeViaGateway).toHaveBeenCalledWith("test-auth-code");
        // Verify result
        expect(result.user_id).toBe("user-789");
        expect(result.email).toBe("test@example.com");
        expect(result.machine_key).toBeDefined();
    });
    it("should handle Gateway auth errors", async () => {
        mockExchangeCodeViaGateway.mockRejectedValue(new Error("Auth exchange failed: OAuth code is invalid or expired"));
        await expect(login()).rejects.toThrow("Auth exchange failed");
        // Code exchange happens after getting the code, so saveTokens should not be called
        expect(mockSaveTokens).not.toHaveBeenCalled();
    });
    it("should save tokens after successful exchange", async () => {
        await login();
        expect(mockSaveTokens).toHaveBeenCalledWith({
            machine_key: "generated-uuid-1234-5678-abcdef",
            access_token: "access-token-123",
            refresh_token: "refresh-token-456",
            expires_at: 1707666000,
            user_id: "user-789",
        });
    });
    it("should generate machineKey on first login", async () => {
        mockLoadTokens.mockResolvedValue(null);
        const result = await login();
        expect(result.machine_key).toBe("generated-uuid-1234-5678-abcdef");
        // Verify saved tokens include generated machine_key
        expect(mockSaveTokens).toHaveBeenCalledWith(expect.objectContaining({
            machine_key: "generated-uuid-1234-5678-abcdef",
        }));
    });
    it("should preserve machineKey on re-login", async () => {
        const existingTokens = {
            machine_key: "existing-machine-key-uuid",
            access_token: "old-access-token",
            refresh_token: "old-refresh-token",
            expires_at: 1000000,
            user_id: "old-user",
        };
        mockLoadTokens.mockResolvedValue(existingTokens);
        const result = await login();
        expect(result.machine_key).toBe("existing-machine-key-uuid");
    });
    it("should fall back to manual mode when OAuth callback times out", async () => {
        mockOAuthServer.waitForCallback.mockRejectedValue(new Error("OAuth callback timed out after 5 minutes"));
        // Manual flow returns a code
        mockManualCodeEntry.mockResolvedValue("manual-code");
        const result = await login();
        expect(mockOAuthServer.close).toHaveBeenCalledOnce();
        expect(mockManualCodeEntry).toHaveBeenCalledOnce();
        expect(mockExchangeCodeViaGateway).toHaveBeenCalledWith("manual-code");
        expect(result.user_id).toBe("user-789");
    });
    it("should throw when both auto and manual modes fail", async () => {
        mockOAuthServer.waitForCallback.mockRejectedValue(new Error("OAuth callback timed out after 5 minutes"));
        mockManualCodeEntry.mockRejectedValue(new Error("No authorization code provided"));
        await expect(login()).rejects.toThrow("No authorization code provided");
        expect(mockOAuthServer.close).toHaveBeenCalledOnce();
        expect(mockSaveTokens).not.toHaveBeenCalled();
    });
    it("should close OAuth server when getOAuthUrl fails and fall back to manual", async () => {
        mockGetOAuthUrlFn
            .mockRejectedValueOnce(new Error("Failed to get OAuth URL: Provider not enabled"))
            .mockRejectedValueOnce(new Error("Failed to get OAuth URL: Provider not enabled"));
        await expect(login()).rejects.toThrow("Failed to get OAuth URL");
        expect(mockOAuthServer.close).toHaveBeenCalledOnce();
    });
    it("should use exchangeCodeViaGateway as default exchange function", async () => {
        // Reset to default exchange function (which is exchangeCodeViaGateway via mock)
        setExchangeCodeFn(mockExchangeCodeViaGateway);
        await login();
        // Should have used the Gateway exchange function
        expect(mockExchangeCodeViaGateway).toHaveBeenCalledWith("test-auth-code");
    });
});
//# sourceMappingURL=login-manager.test.js.map