/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { loadSettings, USER_SETTINGS_PATH } from './settings.js';
import { debugLogger, checkPathTrust } from '@google/gemini-cli-core';

const mocks = vi.hoisted(() => {
  const suffix = Math.random().toString(36).slice(2);
  return {
    suffix,
  };
});

vi.mock('node:os', async (importOriginal) => {
  const actual = await importOriginal<typeof import('node:os')>();
  const path = await import('node:path');
  return {
    ...actual,
    homedir: () => path.join(actual.tmpdir(), `gemini-home-${mocks.suffix}`),
  };
});

vi.mock('@google/gemini-cli-core', async (importOriginal) => {
  const actual =
    await importOriginal<typeof import('@google/gemini-cli-core')>();
  const path = await import('node:path');
  const os = await import('node:os');
  return {
    ...actual,
    GEMINI_DIR: '.gemini',
    debugLogger: {
      error: vi.fn(),
    },
    getErrorMessage: (error: unknown) => String(error),
    homedir: () => path.join(os.tmpdir(), `gemini-home-${mocks.suffix}`),
    checkPathTrust: vi.fn(() => ({ isTrusted: false })),
    isHeadlessMode: vi.fn(() => true),
  };
});

describe('loadSettings', () => {
  const mockHomeDir = path.join(os.tmpdir(), `gemini-home-${mocks.suffix}`);
  const mockWorkspaceDir = path.join(
    os.tmpdir(),
    `gemini-workspace-${mocks.suffix}`,
  );
  const mockGeminiHomeDir = path.join(mockHomeDir, '.gemini');
  const mockGeminiWorkspaceDir = path.join(mockWorkspaceDir, '.gemini');

  beforeEach(() => {
    vi.clearAllMocks();
    // Create the directories using the real fs
    if (!fs.existsSync(mockGeminiHomeDir)) {
      fs.mkdirSync(mockGeminiHomeDir, { recursive: true });
    }
    if (!fs.existsSync(mockGeminiWorkspaceDir)) {
      fs.mkdirSync(mockGeminiWorkspaceDir, { recursive: true });
    }

    // Clean up settings files before each test
    if (fs.existsSync(USER_SETTINGS_PATH)) {
      fs.rmSync(USER_SETTINGS_PATH);
    }
    const workspaceSettingsPath = path.join(
      mockGeminiWorkspaceDir,
      'settings.json',
    );
    if (fs.existsSync(workspaceSettingsPath)) {
      fs.rmSync(workspaceSettingsPath);
    }
  });

  afterEach(() => {
    try {
      if (fs.existsSync(mockHomeDir)) {
        fs.rmSync(mockHomeDir, { recursive: true, force: true });
      }
      if (fs.existsSync(mockWorkspaceDir)) {
        fs.rmSync(mockWorkspaceDir, { recursive: true, force: true });
      }
    } catch (e) {
      debugLogger.error('Failed to cleanup temp dirs', e);
    }
    vi.restoreAllMocks();
  });

  it('should load other top-level settings correctly', () => {
    const settings = {
      showMemoryUsage: true,
      tools: {
        core: ['tool1', 'tool2'],
      },
      mcpServers: {
        server1: {
          command: 'cmd',
          args: ['arg'],
        },
      },
      fileFiltering: {
        respectGitIgnore: true,
      },
    };
    fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(settings));

    const result = loadSettings(mockWorkspaceDir);
    expect(result.showMemoryUsage).toBe(true);
    expect(result.tools?.core).toEqual(['tool1', 'tool2']);
    expect(result.mcpServers).toHaveProperty('server1');
    expect(result.fileFiltering?.respectGitIgnore).toBe(true);
  });

  it('should load experimental settings correctly', () => {
    const settings = {
      experimental: {
        enableAgents: true,
      },
    };
    fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(settings));

    const result = loadSettings(mockWorkspaceDir);
    expect(result.experimental?.enableAgents).toBe(true);
  });

  it('should overwrite top-level settings from workspace (shallow merge)', () => {
    const userSettings = {
      showMemoryUsage: false,
      fileFiltering: {
        respectGitIgnore: true,
        enableRecursiveFileSearch: true,
      },
    };
    fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(userSettings));

    const workspaceSettings = {
      showMemoryUsage: true,
      fileFiltering: {
        respectGitIgnore: false,
      },
    };
    const workspaceSettingsPath = path.join(
      mockGeminiWorkspaceDir,
      'settings.json',
    );
    fs.writeFileSync(workspaceSettingsPath, JSON.stringify(workspaceSettings));

    const result = loadSettings(mockWorkspaceDir, true);
    // Primitive value overwritten
    expect(result.showMemoryUsage).toBe(true);

    // Object value completely replaced (shallow merge behavior)
    expect(result.fileFiltering?.respectGitIgnore).toBe(false);
    expect(result.fileFiltering?.enableRecursiveFileSearch).toBeUndefined();
  });

  describe('security', () => {
    it('should NOT load workspace settings if workspace is NOT trusted', () => {
      const userSettings = { showMemoryUsage: false };
      fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(userSettings));

      const workspaceSettings = { showMemoryUsage: true };
      const workspaceSettingsPath = path.join(
        mockGeminiWorkspaceDir,
        'settings.json',
      );
      fs.writeFileSync(
        workspaceSettingsPath,
        JSON.stringify(workspaceSettings),
      );

      // checkPathTrust is mocked to return isTrusted: false by default
      const result = loadSettings(mockWorkspaceDir);
      expect(result.showMemoryUsage).toBe(false);
    });

    it('should load workspace settings if workspace IS trusted', () => {
      vi.mocked(checkPathTrust).mockReturnValueOnce({
        isTrusted: true,
        source: 'file',
      });
      const userSettings = { showMemoryUsage: false };
      fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(userSettings));

      const workspaceSettings = { showMemoryUsage: true };
      const workspaceSettingsPath = path.join(
        mockGeminiWorkspaceDir,
        'settings.json',
      );
      fs.writeFileSync(
        workspaceSettingsPath,
        JSON.stringify(workspaceSettings),
      );

      const result = loadSettings(mockWorkspaceDir);
      expect(result.showMemoryUsage).toBe(true);
    });

    it('should NOT allow workspace settings to override adminPolicyPaths or policyPaths even if trusted', () => {
      vi.mocked(checkPathTrust).mockReturnValueOnce({
        isTrusted: true,
        source: 'file',
      });
      const userSettings = {
        adminPolicyPaths: ['/trusted/admin'],
        policyPaths: ['/trusted/user'],
      };
      fs.writeFileSync(USER_SETTINGS_PATH, JSON.stringify(userSettings));

      const workspaceSettings = {
        adminPolicyPaths: ['./malicious/admin'],
        policyPaths: ['./malicious/user'],
        showMemoryUsage: true,
      };
      const workspaceSettingsPath = path.join(
        mockGeminiWorkspaceDir,
        'settings.json',
      );
      fs.writeFileSync(
        workspaceSettingsPath,
        JSON.stringify(workspaceSettings),
      );

      const result = loadSettings(mockWorkspaceDir);
      expect(result.showMemoryUsage).toBe(true);
      expect(result.adminPolicyPaths).toEqual(['/trusted/admin']);
      expect(result.policyPaths).toEqual(['/trusted/user']);
    });
  });
});
