/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import {
  describe,
  it,
  expect,
  vi,
  beforeEach,
  afterEach,
  type Mock,
} from 'vitest';
import { configureCommand } from './configure.js';
import yargs from 'yargs';
import { debugLogger } from '@google/gemini-cli-core';
import {
  updateSetting,
  getScopedEnvContents,
  type ExtensionSetting,
} from '../../config/extensions/extensionSettings.js';
import { cleanupTmpDir } from '@google/gemini-cli-test-utils';
import prompts from 'prompts';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';

const { mockExtensionManager, mockGetExtensionManager, mockLoadSettings } =
  vi.hoisted(() => {
    const extensionManager = {
      loadExtensionConfig: vi.fn(),
      getExtensions: vi.fn(),
      loadExtensions: vi.fn(),
      getSettings: vi.fn(),
    };
    return {
      mockExtensionManager: extensionManager,
      mockGetExtensionManager: vi.fn(),
      mockLoadSettings: vi.fn().mockReturnValue({ merged: {} }),
    };
  });

vi.mock('../../config/extension-manager.js', () => ({
  ExtensionManager: vi.fn().mockImplementation(() => mockExtensionManager),
}));

vi.mock('../../config/extensions/extensionSettings.js', () => ({
  updateSetting: vi.fn(),
  promptForSetting: vi.fn(),
  getScopedEnvContents: vi.fn(),
  ExtensionSettingScope: {
    USER: 'user',
    WORKSPACE: 'workspace',
  },
}));

vi.mock('../utils.js', () => ({
  exitCli: vi.fn(),
}));

vi.mock('./utils.js', async (importOriginal) => {
  const actual = await importOriginal<typeof import('./utils.js')>();
  return {
    ...actual,
    getExtensionManager: mockGetExtensionManager,
  };
});

vi.mock('prompts');

vi.mock('../../config/extensions/consent.js', () => ({
  requestConsentNonInteractive: vi.fn(),
}));

import { ExtensionManager } from '../../config/extension-manager.js';

vi.mock('../../config/settings.js', () => ({
  loadSettings: mockLoadSettings,
}));

describe('extensions configure command', () => {
  let tempWorkspaceDir: string;

  beforeEach(() => {
    vi.spyOn(debugLogger, 'log');
    vi.spyOn(debugLogger, 'error');
    vi.clearAllMocks();

    tempWorkspaceDir = fs.mkdtempSync(
      path.join(os.tmpdir(), 'gemini-cli-test-workspace-'),
    );
    vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
    // Default behaviors
    mockLoadSettings.mockReturnValue({ merged: {} });
    mockGetExtensionManager.mockResolvedValue(mockExtensionManager);
    (ExtensionManager as unknown as Mock).mockImplementation(
      () => mockExtensionManager,
    );
  });

  afterEach(async () => {
    await cleanupTmpDir(tempWorkspaceDir);
    vi.restoreAllMocks();
  });

  const runCommand = async (command: string) => {
    const parser = yargs().command(configureCommand).help(false).version(false);
    await parser.parse(command);
  };

  const setupExtension = (
    name: string,
    settings: Array<Partial<ExtensionSetting>> = [],
    id = 'test-id',
    path = '/test/path',
  ) => {
    const extension = { name, path, id };

    mockExtensionManager.getExtensions.mockReturnValue([extension]);
    mockExtensionManager.loadExtensionConfig.mockResolvedValue({
      name,
      settings,
    });
    return extension;
  };

  describe('Specific setting configuration', () => {
    it('should configure a specific setting', async () => {
      setupExtension('test-ext', [
        { name: 'Test Setting', envVar: 'TEST_VAR' },
      ]);
      (updateSetting as Mock).mockResolvedValue(undefined);

      await runCommand('config test-ext TEST_VAR');

      expect(updateSetting).toHaveBeenCalledWith(
        expect.objectContaining({ name: 'test-ext' }),
        'test-id',
        'TEST_VAR',
        expect.any(Function),
        'user',
        tempWorkspaceDir,
      );
    });

    it('should handle missing extension', async () => {
      mockExtensionManager.getExtensions.mockReturnValue([]);

      await runCommand('config missing-ext TEST_VAR');

      expect(updateSetting).not.toHaveBeenCalled();
    });

    it('should reject invalid extension names', async () => {
      await runCommand('config ../invalid TEST_VAR');
      expect(debugLogger.error).toHaveBeenCalledWith(
        expect.stringContaining('Invalid extension name'),
      );

      await runCommand('config ext/with/slash TEST_VAR');
      expect(debugLogger.error).toHaveBeenCalledWith(
        expect.stringContaining('Invalid extension name'),
      );
    });
  });

  describe('Extension configuration (all settings)', () => {
    it('should configure all settings for an extension', async () => {
      const settings = [{ name: 'Setting 1', envVar: 'VAR_1' }];
      setupExtension('test-ext', settings);
      (getScopedEnvContents as Mock).mockResolvedValue({});
      (updateSetting as Mock).mockResolvedValue(undefined);

      await runCommand('config test-ext');

      expect(debugLogger.log).toHaveBeenCalledWith(
        'Configuring settings for "test-ext"...',
      );
      expect(updateSetting).toHaveBeenCalledWith(
        expect.objectContaining({ name: 'test-ext' }),
        'test-id',
        'VAR_1',
        expect.any(Function),
        'user',
        tempWorkspaceDir,
      );
    });

    it('should verify overwrite if setting is already set', async () => {
      const settings = [{ name: 'Setting 1', envVar: 'VAR_1' }];
      setupExtension('test-ext', settings);
      (getScopedEnvContents as Mock).mockImplementation(
        async (_config, _id, scope) => {
          if (scope === 'user') return { VAR_1: 'existing' };
          return {};
        },
      );
      (prompts as unknown as Mock).mockResolvedValue({ confirm: true });
      (updateSetting as Mock).mockResolvedValue(undefined);

      await runCommand('config test-ext');

      expect(prompts).toHaveBeenCalledWith(
        expect.objectContaining({
          type: 'confirm',
          message: expect.stringContaining('is already set. Overwrite?'),
        }),
      );
      expect(updateSetting).toHaveBeenCalled();
    });

    it('should note if setting is configured in workspace', async () => {
      const settings = [{ name: 'Setting 1', envVar: 'VAR_1' }];
      setupExtension('test-ext', settings);
      (getScopedEnvContents as Mock).mockImplementation(
        async (_config, _id, scope) => {
          if (scope === 'workspace') return { VAR_1: 'workspace_value' };
          return {};
        },
      );
      (updateSetting as Mock).mockResolvedValue(undefined);

      await runCommand('config test-ext');

      expect(debugLogger.log).toHaveBeenCalledWith(
        expect.stringContaining('is already configured in the workspace scope'),
      );
    });

    it('should skip update if user denies overwrite', async () => {
      const settings = [{ name: 'Setting 1', envVar: 'VAR_1' }];
      setupExtension('test-ext', settings);
      (getScopedEnvContents as Mock).mockResolvedValue({ VAR_1: 'existing' });
      (prompts as unknown as Mock).mockResolvedValue({ confirm: false });

      await runCommand('config test-ext');

      expect(prompts).toHaveBeenCalled();
      expect(updateSetting).not.toHaveBeenCalled();
    });
  });

  describe('Configure all extensions', () => {
    it('should configure settings for all installed extensions', async () => {
      const ext1 = {
        name: 'ext1',
        path: '/p1',
        id: 'id1',
        settings: [{ envVar: 'V1' }],
      };
      const ext2 = {
        name: 'ext2',
        path: '/p2',
        id: 'id2',
        settings: [{ envVar: 'V2' }],
      };
      mockExtensionManager.getExtensions.mockReturnValue([ext1, ext2]);

      mockExtensionManager.loadExtensionConfig.mockImplementation(
        async (path) => {
          if (path === '/p1')
            return { name: 'ext1', settings: [{ name: 'S1', envVar: 'V1' }] };
          if (path === '/p2')
            return { name: 'ext2', settings: [{ name: 'S2', envVar: 'V2' }] };
          return null;
        },
      );

      (getScopedEnvContents as Mock).mockResolvedValue({});
      (updateSetting as Mock).mockResolvedValue(undefined);

      await runCommand('config');

      expect(debugLogger.log).toHaveBeenCalledWith(
        expect.stringContaining('Configuring settings for "ext1"'),
      );
      expect(debugLogger.log).toHaveBeenCalledWith(
        expect.stringContaining('Configuring settings for "ext2"'),
      );
      expect(updateSetting).toHaveBeenCalledTimes(2);
    });

    it('should log if no extensions installed', async () => {
      mockExtensionManager.getExtensions.mockReturnValue([]);
      await runCommand('config');
      expect(debugLogger.log).toHaveBeenCalledWith('No extensions installed.');
    });
  });
});
