/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { vi, describe, it, expect, beforeEach, afterEach, } from 'vitest';
import { format } from 'node:util';
import {} from 'yargs';
import { handleUpdate, updateCommand } from './update.js';
import { ExtensionManager } from '../../config/extension-manager.js';
import { loadSettings } from '../../config/settings.js';
import * as update from '../../config/extensions/update.js';
import * as github from '../../config/extensions/github.js';
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
// Mock dependencies
const emitConsoleLog = vi.hoisted(() => vi.fn());
const emitFeedback = vi.hoisted(() => vi.fn());
const debugLogger = vi.hoisted(() => ({
    log: vi.fn((message, ...args) => {
        emitConsoleLog('log', format(message, ...args));
    }),
    error: vi.fn((message, ...args) => {
        emitConsoleLog('error', format(message, ...args));
    }),
}));
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        coreEvents: {
            emitConsoleLog,
            emitFeedback,
        },
        debugLogger,
    };
});
vi.mock('../../config/extension-manager.js');
vi.mock('../../config/settings.js');
vi.mock('../../utils/errors.js');
vi.mock('../../config/extensions/update.js');
vi.mock('../../config/extensions/github.js');
vi.mock('../../config/extensions/consent.js', () => ({
    requestConsentNonInteractive: vi.fn(),
}));
vi.mock('../../config/extensions/extensionSettings.js', () => ({
    promptForSetting: vi.fn(),
}));
vi.mock('../utils.js', () => ({
    exitCli: vi.fn(),
}));
describe('extensions update command', () => {
    const mockLoadSettings = vi.mocked(loadSettings);
    const mockExtensionManager = vi.mocked(ExtensionManager);
    const mockUpdateExtension = vi.mocked(update.updateExtension);
    const mockCheckForExtensionUpdate = vi.mocked(github.checkForExtensionUpdate);
    const mockCheckForAllExtensionUpdates = vi.mocked(update.checkForAllExtensionUpdates);
    const mockUpdateAllUpdatableExtensions = vi.mocked(update.updateAllUpdatableExtensions);
    beforeEach(async () => {
        vi.clearAllMocks();
        mockLoadSettings.mockReturnValue({
            merged: { experimental: { extensionReloading: true } },
        });
    });
    afterEach(() => {
        vi.restoreAllMocks();
    });
    describe('handleUpdate', () => {
        it('should list installed extensions when requested extension is not found', async () => {
            const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir');
            const extensions = [
                { name: 'ext1', version: '1.0.0' },
                { name: 'ext2', version: '2.0.0' },
            ];
            mockExtensionManager.prototype.loadExtensions = vi
                .fn()
                .mockResolvedValue(extensions);
            await handleUpdate({ name: 'missing-extension' });
            expect(emitFeedback).toHaveBeenCalledWith('error', 'Extension "missing-extension" not found.\n\nInstalled extensions:\next1 (1.0.0)\next2 (2.0.0)\n\nRun "gemini extensions list" for details.');
            expect(mockUpdateExtension).not.toHaveBeenCalled();
            mockCwd.mockRestore();
        });
        it('should log a helpful message when no extensions are installed and requested extension is not found', async () => {
            const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir');
            mockExtensionManager.prototype.loadExtensions = vi
                .fn()
                .mockResolvedValue([]);
            await handleUpdate({ name: 'missing-extension' });
            expect(emitFeedback).toHaveBeenCalledWith('error', 'Extension "missing-extension" not found.\n\nNo extensions installed.');
            expect(mockUpdateExtension).not.toHaveBeenCalled();
            mockCwd.mockRestore();
        });
        it.each([
            {
                state: ExtensionUpdateState.UPDATE_AVAILABLE,
                expectedLog: 'Extension "my-extension" successfully updated: 1.0.0 → 1.1.0.',
                shouldCallUpdateExtension: true,
            },
            {
                state: ExtensionUpdateState.UP_TO_DATE,
                expectedLog: 'Extension "my-extension" is already up to date.',
                shouldCallUpdateExtension: false,
            },
        ])('should handle single extension update state: $state', async ({ state, expectedLog, shouldCallUpdateExtension }) => {
            const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir');
            const extensions = [{ name: 'my-extension', installMetadata: {} }];
            mockExtensionManager.prototype.loadExtensions = vi
                .fn()
                .mockResolvedValue(extensions);
            mockCheckForExtensionUpdate.mockResolvedValue(state);
            mockUpdateExtension.mockResolvedValue({
                name: 'my-extension',
                originalVersion: '1.0.0',
                updatedVersion: '1.1.0',
            });
            await handleUpdate({ name: 'my-extension' });
            expect(emitConsoleLog).toHaveBeenCalledWith('log', expectedLog);
            if (shouldCallUpdateExtension) {
                expect(mockUpdateExtension).toHaveBeenCalled();
            }
            else {
                expect(mockUpdateExtension).not.toHaveBeenCalled();
            }
            mockCwd.mockRestore();
        });
        it.each([
            {
                updatedExtensions: [
                    { name: 'ext1', originalVersion: '1.0.0', updatedVersion: '1.1.0' },
                    { name: 'ext2', originalVersion: '2.0.0', updatedVersion: '2.1.0' },
                ],
                expectedLog: 'Extension "ext1" successfully updated: 1.0.0 → 1.1.0.\nExtension "ext2" successfully updated: 2.0.0 → 2.1.0.',
            },
            {
                updatedExtensions: [],
                expectedLog: 'No extensions to update.',
            },
        ])('should handle updating all extensions: %s', async ({ updatedExtensions, expectedLog }) => {
            const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir');
            mockExtensionManager.prototype.loadExtensions = vi
                .fn()
                .mockResolvedValue([]);
            mockCheckForAllExtensionUpdates.mockResolvedValue(undefined);
            mockUpdateAllUpdatableExtensions.mockResolvedValue(updatedExtensions);
            await handleUpdate({ all: true });
            expect(emitConsoleLog).toHaveBeenCalledWith('log', expectedLog);
            mockCwd.mockRestore();
        });
    });
    describe('updateCommand', () => {
        const command = updateCommand;
        it('should have correct command and describe', () => {
            expect(command.command).toBe('update [<name>] [--all]');
            expect(command.describe).toBe('Updates all extensions or a named extension to the latest version.');
        });
        describe('builder', () => {
            let yargsMock;
            beforeEach(() => {
                yargsMock = {
                    positional: vi.fn().mockReturnThis(),
                    option: vi.fn().mockReturnThis(),
                    conflicts: vi.fn().mockReturnThis(),
                    check: vi.fn().mockReturnThis(),
                };
            });
            it('should configure arguments', () => {
                command.builder(yargsMock);
                expect(yargsMock.positional).toHaveBeenCalledWith('name', expect.any(Object));
                expect(yargsMock.option).toHaveBeenCalledWith('all', expect.any(Object));
                expect(yargsMock.conflicts).toHaveBeenCalledWith('name', 'all');
                expect(yargsMock.check).toHaveBeenCalled();
            });
            it('check function should throw an error if neither a name nor --all is provided', () => {
                command.builder(yargsMock);
                const checkCallback = yargsMock.check.mock.calls[0][0];
                expect(() => checkCallback({ name: undefined, all: false })).toThrow('Either an extension name or --all must be provided');
            });
        });
        it('handler should call handleUpdate', async () => {
            const extensions = [{ name: 'my-extension', installMetadata: {} }];
            mockExtensionManager.prototype.loadExtensions = vi
                .fn()
                .mockResolvedValue(extensions);
            mockCheckForExtensionUpdate.mockResolvedValue(ExtensionUpdateState.UPDATE_AVAILABLE);
            mockUpdateExtension.mockResolvedValue({
                name: 'my-extension',
                originalVersion: '1.0.0',
                updatedVersion: '1.1.0',
            });
            await command.handler({
                name: 'my-extension',
            });
            expect(mockUpdateExtension).toHaveBeenCalled();
        });
    });
});
//# sourceMappingURL=update.test.js.map