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

import * as fs from 'node:fs';
import * as path from 'node:path';

import {
  type MCPServerConfig,
  debugLogger,
  GEMINI_DIR,
  getErrorMessage,
  type TelemetrySettings,
  homedir,
  checkPathTrust,
  isHeadlessMode,
} from '@google/gemini-cli-core';
import stripJsonComments from 'strip-json-comments';

export const USER_SETTINGS_DIR = path.join(homedir(), GEMINI_DIR);
export const USER_SETTINGS_PATH = path.join(USER_SETTINGS_DIR, 'settings.json');

// TODO: Ensure full compatibility with V2 nested settings structure (settings.schema.json).
// This involves updating the interface and implementing migration logic to support legacy V1 (flat) settings,
// similar to how packages/cli/src/config/settings.ts handles it.
export interface Settings {
  mcpServers?: Record<string, MCPServerConfig>;
  tools?: {
    allowed?: string[];
    exclude?: string[];
    core?: string[];
  };
  telemetry?: TelemetrySettings;
  showMemoryUsage?: boolean;
  checkpointing?: CheckpointingSettings;
  folderTrust?: boolean;
  general?: {
    previewFeatures?: boolean;
  };

  // Git-aware file filtering settings
  fileFiltering?: {
    respectGitIgnore?: boolean;
    respectGeminiIgnore?: boolean;
    enableRecursiveFileSearch?: boolean;
    customIgnoreFilePaths?: string[];
  };
  experimental?: {
    enableAgents?: boolean;
  };
  policyPaths?: string[];
  adminPolicyPaths?: string[];
}

export interface SettingsError {
  message: string;
  path: string;
}

export interface CheckpointingSettings {
  enabled?: boolean;
}

/**
 * Loads settings from user and workspace directories.
 * Project settings override user settings if the workspace is trusted.
 *
 * How is it different to gemini-cli/cli: Returns already merged settings rather
 * than `LoadedSettings` (unnecessary since we are not modifying users
 * settings.json).
 */
export function loadSettings(
  workspaceDir: string,
  isTrustedOverride?: boolean,
): Settings {
  let userSettings: Settings = {};
  let workspaceSettings: Settings = {};
  const settingsErrors: SettingsError[] = [];

  // Load user settings
  try {
    if (fs.existsSync(USER_SETTINGS_PATH)) {
      const userContent = fs.readFileSync(USER_SETTINGS_PATH, 'utf-8');
      // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
      const parsedUserSettings = JSON.parse(
        stripJsonComments(userContent),
      ) as Settings;
      userSettings = resolveEnvVarsInObject(parsedUserSettings);
    }
  } catch (error: unknown) {
    settingsErrors.push({
      message: getErrorMessage(error),
      path: USER_SETTINGS_PATH,
    });
  }

  let isTrusted = isTrustedOverride;
  if (isTrusted === undefined) {
    const { isTrusted: trustResult } = checkPathTrust({
      path: workspaceDir,
      isFolderTrustEnabled: userSettings.folderTrust ?? true,
      isHeadless: isHeadlessMode(),
    });
    isTrusted = trustResult ?? false;
  }

  const workspaceSettingsPath = path.join(
    workspaceDir,
    GEMINI_DIR,
    'settings.json',
  );

  // Load workspace settings only if trusted
  if (isTrusted) {
    try {
      if (fs.existsSync(workspaceSettingsPath)) {
        const projectContent = fs.readFileSync(workspaceSettingsPath, 'utf-8');
        // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
        const parsedWorkspaceSettings = JSON.parse(
          stripJsonComments(projectContent),
        ) as Settings;
        workspaceSettings = resolveEnvVarsInObject(parsedWorkspaceSettings);
      }
    } catch (error: unknown) {
      settingsErrors.push({
        message: getErrorMessage(error),
        path: workspaceSettingsPath,
      });
    }
  }

  if (settingsErrors.length > 0) {
    debugLogger.error('Errors loading settings:');
    for (const error of settingsErrors) {
      debugLogger.error(`  Path: ${error.path}`);
      debugLogger.error(`  Message: ${error.message}`);
    }
  }

  // If there are overlapping keys, the values of workspaceSettings will
  // override values from userSettings
  const mergedSettings = {
    ...userSettings,
    ...workspaceSettings,
  };

  // Security: ensure policyPaths and adminPolicyPaths are only loaded from trusted, user-level
  // configuration and cannot be overridden by workspace-level settings, even if the
  // workspace is trusted.
  mergedSettings.policyPaths = userSettings.policyPaths;
  mergedSettings.adminPolicyPaths = userSettings.adminPolicyPaths;

  return mergedSettings;
}

function resolveEnvVarsInString(value: string): string {
  const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME}
  return value.replace(
    envVarRegex,
    (match: string, varName1: string, varName2: string) => {
      const varName = varName1 || varName2;
      const envValue = process?.env?.[varName];
      if (typeof envValue === 'string') {
        return envValue;
      }
      return match;
    },
  );
}

function resolveEnvVarsInObject<T>(obj: T): T {
  if (
    obj === null ||
    obj === undefined ||
    typeof obj === 'boolean' ||
    typeof obj === 'number'
  ) {
    return obj;
  }

  if (typeof obj === 'string') {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
    return resolveEnvVarsInString(obj) as unknown as T;
  }

  if (Array.isArray(obj)) {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-return
    return obj.map((item) => resolveEnvVarsInObject(item)) as unknown as T;
  }

  if (typeof obj === 'object') {
    const newObj = { ...obj } as T;
    for (const key in newObj) {
      if (Object.prototype.hasOwnProperty.call(newObj, key)) {
        newObj[key] = resolveEnvVarsInObject(newObj[key]);
      }
    }
    return newObj;
  }

  return obj;
}
