/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { ProjectIdRequiredError, setupUser, ValidationCancelledError, resetUserDataCacheForTesting, } from './setup.js';
import { ValidationRequiredError } from '../utils/googleQuotaErrors.js';
import { CodeAssistServer } from '../code_assist/server.js';
import { UserTierId } from './types.js';
vi.mock('../code_assist/server.js');
const mockPaidTier = {
    id: UserTierId.STANDARD,
    name: 'paid',
    description: 'Paid tier',
    isDefault: true,
};
const mockFreeTier = {
    id: UserTierId.FREE,
    name: 'free',
    description: 'Free tier',
    isDefault: true,
};
describe('setupUser', () => {
    let mockLoad;
    let mockOnboardUser;
    let mockGetOperation;
    let mockConfig;
    let mockValidationHandler;
    beforeEach(() => {
        vi.resetAllMocks();
        resetUserDataCacheForTesting();
        vi.useFakeTimers();
        mockLoad = vi.fn();
        mockOnboardUser = vi.fn().mockResolvedValue({
            done: true,
            response: {
                cloudaicompanionProject: {
                    id: 'server-project',
                },
            },
        });
        mockGetOperation = vi.fn();
        vi.mocked(CodeAssistServer).mockImplementation(() => ({
            loadCodeAssist: mockLoad,
            onboardUser: mockOnboardUser,
            getOperation: mockGetOperation,
        }));
        mockValidationHandler = vi.fn();
        mockConfig = {
            getValidationHandler: () => mockValidationHandler,
            getUsageStatisticsEnabled: () => true,
            getSessionId: () => 'test-session-id',
            getContentGeneratorConfig: () => ({
                authType: 'google-login',
            }),
            isInteractive: () => false,
            getExperiments: () => undefined,
        };
    });
    afterEach(() => {
        vi.useRealTimers();
        vi.unstubAllEnvs();
    });
    describe('caching', () => {
        it('should cache setup result for same client and projectId', async () => {
            mockLoad.mockResolvedValue({
                currentTier: mockPaidTier,
                cloudaicompanionProject: 'server-project',
            });
            const client = {};
            // First call
            await setupUser(client, mockConfig);
            // Second call
            await setupUser(client, mockConfig);
            expect(mockLoad).toHaveBeenCalledTimes(1);
        });
        it('should re-fetch if projectId changes', async () => {
            mockLoad.mockResolvedValue({
                currentTier: mockPaidTier,
                cloudaicompanionProject: 'server-project',
            });
            const client = {};
            vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'p1');
            await setupUser(client, mockConfig);
            vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'p2');
            await setupUser(client, mockConfig);
            expect(mockLoad).toHaveBeenCalledTimes(2);
        });
        it('should re-fetch if cache expires', async () => {
            mockLoad.mockResolvedValue({
                currentTier: mockPaidTier,
                cloudaicompanionProject: 'server-project',
            });
            const client = {};
            await setupUser(client, mockConfig);
            vi.advanceTimersByTime(31000); // 31s > 30s expiration
            await setupUser(client, mockConfig);
            expect(mockLoad).toHaveBeenCalledTimes(2);
        });
        it('should retry if previous attempt failed', async () => {
            mockLoad.mockRejectedValueOnce(new Error('Network error'));
            mockLoad.mockResolvedValueOnce({
                currentTier: mockPaidTier,
                cloudaicompanionProject: 'server-project',
            });
            const client = {};
            await expect(setupUser(client, mockConfig)).rejects.toThrow('Network error');
            await setupUser(client, mockConfig);
            expect(mockLoad).toHaveBeenCalledTimes(2);
        });
    });
    describe('existing user', () => {
        it('should use GOOGLE_CLOUD_PROJECT when set and project from server is undefined', async () => {
            vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
            mockLoad.mockResolvedValue({
                currentTier: mockPaidTier,
            });
            await setupUser({}, mockConfig);
            expect(CodeAssistServer).toHaveBeenCalledWith({}, 'test-project', {}, '', undefined, undefined);
        });
        it('should pass httpOptions to CodeAssistServer when provided', async () => {
            vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
            mockLoad.mockResolvedValue({
                currentTier: mockPaidTier,
            });
            const httpOptions = {
                headers: {
                    'User-Agent': 'GeminiCLI/1.0.0/gemini-2.0-flash (darwin; arm64)',
                },
            };
            await setupUser({}, mockConfig, httpOptions);
            expect(CodeAssistServer).toHaveBeenCalledWith({}, 'test-project', httpOptions, '', undefined, undefined);
        });
        it('should ignore GOOGLE_CLOUD_PROJECT when project from server is set', async () => {
            vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
            mockLoad.mockResolvedValue({
                cloudaicompanionProject: 'server-project',
                currentTier: mockPaidTier,
            });
            const result = await setupUser({}, mockConfig);
            expect(result.projectId).toBe('server-project');
        });
        it('should throw ProjectIdRequiredError when no project ID is available', async () => {
            vi.stubEnv('GOOGLE_CLOUD_PROJECT', '');
            // And the server itself requires a project ID internally
            vi.mocked(CodeAssistServer).mockImplementation(() => {
                throw new ProjectIdRequiredError();
            });
            await expect(setupUser({}, mockConfig)).rejects.toThrow(ProjectIdRequiredError);
        });
    });
    describe('new user', () => {
        it('should onboard a new paid user with GOOGLE_CLOUD_PROJECT', async () => {
            vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
            mockLoad.mockResolvedValue({
                allowedTiers: [mockPaidTier],
            });
            const userData = await setupUser({}, mockConfig);
            expect(mockOnboardUser).toHaveBeenCalledWith(expect.objectContaining({
                tierId: UserTierId.STANDARD,
                cloudaicompanionProject: 'test-project',
            }));
            expect(userData).toEqual({
                projectId: 'server-project',
                userTier: UserTierId.STANDARD,
                userTierName: 'paid',
                hasOnboardedPreviously: false,
            });
        });
        it('should onboard a new free user when project ID is not set', async () => {
            vi.stubEnv('GOOGLE_CLOUD_PROJECT', '');
            mockLoad.mockResolvedValue({
                allowedTiers: [mockFreeTier],
            });
            const userData = await setupUser({}, mockConfig);
            expect(mockOnboardUser).toHaveBeenCalledWith(expect.objectContaining({
                tierId: UserTierId.FREE,
                cloudaicompanionProject: undefined,
            }));
            expect(userData).toEqual({
                projectId: 'server-project',
                userTier: UserTierId.FREE,
                userTierName: 'free',
                hasOnboardedPreviously: false,
            });
        });
        it('should use GOOGLE_CLOUD_PROJECT when onboard response has no project ID', async () => {
            vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
            mockLoad.mockResolvedValue({
                allowedTiers: [mockPaidTier],
            });
            mockOnboardUser.mockResolvedValue({
                done: true,
                response: {
                    cloudaicompanionProject: undefined,
                },
            });
            const userData = await setupUser({}, mockConfig);
            expect(userData).toEqual({
                projectId: 'test-project',
                userTier: UserTierId.STANDARD,
                userTierName: 'paid',
                hasOnboardedPreviously: false,
            });
        });
        it('should poll getOperation when onboardUser returns done=false', async () => {
            mockLoad.mockResolvedValue({
                allowedTiers: [mockPaidTier],
            });
            const operationName = 'operations/123';
            mockOnboardUser.mockResolvedValueOnce({
                name: operationName,
                done: false,
            });
            mockGetOperation
                .mockResolvedValueOnce({
                name: operationName,
                done: false,
            })
                .mockResolvedValueOnce({
                name: operationName,
                done: true,
                response: {
                    cloudaicompanionProject: {
                        id: 'server-project',
                    },
                },
            });
            const promise = setupUser({}, mockConfig);
            await vi.advanceTimersByTimeAsync(5000);
            await vi.advanceTimersByTimeAsync(5000);
            const userData = await promise;
            expect(mockGetOperation).toHaveBeenCalledWith(operationName);
            expect(userData.projectId).toBe('server-project');
        });
    });
    describe('validation and errors', () => {
        it('should retry if validation handler returns verify', async () => {
            mockLoad
                .mockResolvedValueOnce({
                currentTier: null,
                ineligibleTiers: [
                    {
                        reasonMessage: 'Verify please',
                        reasonCode: 'VALIDATION_REQUIRED',
                        tierId: UserTierId.STANDARD,
                        tierName: 'standard',
                        validationUrl: 'https://verify',
                    },
                ],
            })
                .mockResolvedValueOnce({
                currentTier: mockPaidTier,
                cloudaicompanionProject: 'p1',
            });
            mockValidationHandler.mockResolvedValue('verify');
            const result = await setupUser({}, mockConfig);
            expect(mockValidationHandler).toHaveBeenCalledWith('https://verify', 'Verify please');
            expect(mockLoad).toHaveBeenCalledTimes(2);
            expect(result.projectId).toBe('p1');
        });
        it('should throw ValidationCancelledError if handler returns cancel', async () => {
            mockLoad.mockResolvedValue({
                currentTier: null,
                ineligibleTiers: [
                    {
                        reasonMessage: 'User is not eligible',
                        reasonCode: 'VALIDATION_REQUIRED',
                        tierId: UserTierId.STANDARD,
                        tierName: 'standard',
                        validationUrl: 'https://example.com/verify',
                    },
                ],
            });
            mockValidationHandler.mockResolvedValue('cancel');
            await expect(setupUser({}, mockConfig)).rejects.toThrow(ValidationCancelledError);
        });
        it('should throw error if LoadCodeAssist returns empty response', async () => {
            mockLoad.mockResolvedValue(null);
            await expect(setupUser({}, mockConfig)).rejects.toThrow('LoadCodeAssist returned empty response');
        });
    });
});
describe('ValidationRequiredError', () => {
    const error = new ValidationRequiredError('Account validation required: Please verify', undefined, 'https://example.com/verify', 'Please verify');
    it('should be an instance of Error', () => {
        expect(error).toBeInstanceOf(Error);
        expect(error).toBeInstanceOf(ValidationRequiredError);
    });
    it('should have the correct properties', () => {
        expect(error.validationLink).toBe('https://example.com/verify');
        expect(error.validationDescription).toBe('Please verify');
    });
});
//# sourceMappingURL=setup.test.js.map