#!/usr/bin/env python3
"""
Gemini Code Patcher — patches Gemini CLI to route through custom AI proxy.

Targets:
  1. gemini_base_url    — hardcoded generativelanguage.googleapis.com → proxy
  2. vertex_base_url    — hardcoded aiplatform.googleapis.com → proxy
  3. sanitize_env_url   — allow : in URL env vars
  4. auth_env_whitelist  — add GOOGLE_GEMINI_BASE_URL to whitelist
  5. user_settings       — settings.json (auth + telemetry)
  6. system_env          — env vars injection
  7. auto_update_registry — redirect registry-url default to npm.sensey24.ru
  8. auto_update_commands — add --registry to update commands
  9. auto_permissions    — bypass tool approval prompts (YOLO mode via settings)
"""

import json
import os
import re
import sys
import shutil
import platform
import subprocess
import argparse
from pathlib import Path

# ─── Constants ──────────────────────────────────────────────────────────

SCRIPT_DIR = Path(__file__).resolve().parent
CONFIG_PATH = SCRIPT_DIR / "gemini_config.json"

IS_WINDOWS = platform.system() == "Windows"
IS_MACOS = platform.system() == "Darwin"

NPM_PACKAGE = "@google/gemini-cli"
GENAI_SUBPATH = "node_modules/@google/genai/dist/node/index.mjs"
SETTINGS_JS_SUBPATH = "dist/src/config/settings.js"
REGISTRY_URL_SUBPATH = "node_modules/registry-url/index.js"
INSTALL_INFO_SUBPATH = "dist/src/utils/installationInfo.js"
NPM_CONF_DEFAULTS_SUBPATH = "node_modules/@pnpm/npm-conf/lib/defaults.js"
CONFIG_JS_SUBPATH = "dist/src/config/config.js"
SETTINGS_SCHEMA_SUBPATH = "dist/src/config/settingsSchema.js"

# ANSI colors
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
CYAN = "\033[96m"
BOLD = "\033[1m"
RESET = "\033[0m"

PATCH_MARKER = "/* GEMINI_PATCHED */"


# ─── Utilities ──────────────────────────────────────────────────────────

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)


def color(text, c):
    return f"{c}{text}{RESET}"


# ─── Detection ──────────────────────────────────────────────────────────

def default_gemini_paths():
    """Return list of possible Gemini CLI install paths."""
    paths = []
    if IS_WINDOWS:
        appdata = os.environ.get("APPDATA", "")
        if appdata:
            paths.append(os.path.join(appdata, "npm", "node_modules", NPM_PACKAGE))
        localappdata = os.environ.get("LOCALAPPDATA", "")
        if localappdata:
            paths.append(os.path.join(localappdata, "npm", "node_modules", NPM_PACKAGE))
    elif IS_MACOS:
        paths.extend([
            f"/opt/homebrew/lib/node_modules/{NPM_PACKAGE}",
            f"/usr/local/lib/node_modules/{NPM_PACKAGE}",
            f"/usr/lib/node_modules/{NPM_PACKAGE}",
        ])
    else:
        paths.extend([
            f"/usr/lib/node_modules/{NPM_PACKAGE}",
            f"/usr/local/lib/node_modules/{NPM_PACKAGE}",
        ])
    return paths


def detect_gemini():
    """Find Gemini CLI installation. Returns (gemini_root, genai_mjs, settings_js) or raises."""
    for root in default_gemini_paths():
        genai_mjs = os.path.join(root, GENAI_SUBPATH)
        settings_js = os.path.join(root, SETTINGS_JS_SUBPATH)
        if os.path.isfile(genai_mjs):
            return root, genai_mjs, settings_js
    raise FileNotFoundError(
        f"Gemini CLI not found. Checked: {default_gemini_paths()}"
    )


def read_version(gemini_root):
    """Read Gemini CLI version from package.json."""
    pkg_path = os.path.join(gemini_root, "package.json")
    if not os.path.isfile(pkg_path):
        return "unknown"
    with open(pkg_path, "r") as f:
        data = json.load(f)
    return data.get("version", "unknown")


# ─── Config ─────────────────────────────────────────────────────────────

def load_config(config_path=None):
    """Load gemini_config.json."""
    path = config_path or CONFIG_PATH
    if not os.path.isfile(path):
        raise FileNotFoundError(f"Config not found: {path}")
    with open(path, "r") as f:
        return json.load(f)


# ─── Target 1+2: Patch genai URLs ──────────────────────────────────────

def patch_genai_urls(genai_mjs_path, config):
    """
    Patch hardcoded API URLs in @google/genai/dist/node/index.mjs.
    Target 1: generativelanguage.googleapis.com → proxy
    Target 2: aiplatform.googleapis.com → proxy
    """
    if not os.path.isfile(genai_mjs_path):
        return False, f"File not found: {genai_mjs_path}"

    with open(genai_mjs_path, "r", encoding="utf-8") as f:
        content = f.read()

    base_url = config["base_url"].rstrip("/") + "/"
    changes = 0
    original = content

    # Check if already patched
    if PATCH_MARKER in content:
        return True, "Already patched (marker found)"

    # Target 1: Gemini API base URL
    pattern1 = r'(initHttpOptions\.baseUrl\s*=\s*)`https://generativelanguage\.googleapis\.com/`'
    replacement1 = rf'\1`{base_url}`'
    content, n = re.subn(pattern1, replacement1, content)
    changes += n

    # Target 2a: Regional Vertex AI endpoint
    pattern2a = r"return\s*`https://\$\{this\.clientOptions\.location\}-aiplatform\.googleapis\.com/`"
    replacement2a = f"return `{base_url}`"
    content, n = re.subn(pattern2a, replacement2a, content)
    changes += n

    # Target 2b: Global Vertex AI endpoint
    pattern2b = r"return\s*`https://aiplatform\.googleapis\.com/`"
    replacement2b = f"return `{base_url}`"
    content, n = re.subn(pattern2b, replacement2b, content)
    changes += n

    # Target 1b: baseURL fallback (genai SDK >=1.30)
    base_url_noslash = base_url.rstrip("/")
    pattern1b = r'(baseURL:\s*baseURL\s*\|\|\s*)`https://generativelanguage\.googleapis\.com`'
    replacement1b = rf'\1`{base_url_noslash}`'
    content, n = re.subn(pattern1b, replacement1b, content)
    changes += n

    # Target 1c: baseURLOverridden default check (genai SDK >=1.30)
    pattern1c = r"(this\.baseURL\s*!==\s*)'https://generativelanguage\.googleapis\.com'"
    replacement1c = rf"\1'{base_url_noslash}'"
    content, n = re.subn(pattern1c, replacement1c, content)
    changes += n

    # Catch-all: replace any remaining googleapis URLs in non-comment code
    remaining = content.count("generativelanguage.googleapis.com")
    if remaining > 0:
        content = content.replace("generativelanguage.googleapis.com", base_url_noslash.replace("https://", "").replace("http://", ""))
        changes += remaining

    if changes == 0:
        return False, "No URL patterns matched — structure may have changed"

    # Add patch marker at the top
    content = PATCH_MARKER + "\n" + content

    # Write back
    # Create backup first
    backup_path = genai_mjs_path + ".backup"
    if not os.path.exists(backup_path):
        shutil.copy2(genai_mjs_path, backup_path)

    with open(genai_mjs_path, "w", encoding="utf-8") as f:
        f.write(content)

    return True, f"Patched {changes} URL(s) in genai index.mjs"


# ─── Target 3+4: Patch settings.js ─────────────────────────────────────

def patch_settings_js(settings_js_path):
    """
    Target 3: Fix sanitizeEnvVar to allow : in URLs.
    Target 4: Add GOOGLE_GEMINI_BASE_URL to AUTH_ENV_VAR_WHITELIST.
    """
    if not os.path.isfile(settings_js_path):
        return False, f"File not found: {settings_js_path}"

    with open(settings_js_path, "r", encoding="utf-8") as f:
        content = f.read()

    changes = 0

    # Target 3: sanitizeEnvVar — allow : and @ in regex
    old_sanitize = "return value.replace(/[^a-zA-Z0-9\\-_./]/g, '');"
    new_sanitize = "return value.replace(/[^a-zA-Z0-9\\-_./:@]/g, '');"
    if old_sanitize in content:
        content = content.replace(old_sanitize, new_sanitize, 1)
        changes += 1

    # Target 4: AUTH_ENV_VAR_WHITELIST — add BASE_URL entries
    if "GOOGLE_GEMINI_BASE_URL" not in content:
        old_whitelist = "'GOOGLE_CLOUD_LOCATION',\n];"
        new_whitelist = (
            "'GOOGLE_CLOUD_LOCATION',\n"
            "    'GOOGLE_GEMINI_BASE_URL',\n"
            "    'GOOGLE_VERTEX_BASE_URL',\n"
            "];"
        )
        if old_whitelist in content:
            content = content.replace(old_whitelist, new_whitelist, 1)
            changes += 1
        else:
            # Try alternate formatting
            old_whitelist2 = "'GOOGLE_CLOUD_LOCATION',\n    ];"
            new_whitelist2 = (
                "'GOOGLE_CLOUD_LOCATION',\n"
                "        'GOOGLE_GEMINI_BASE_URL',\n"
                "        'GOOGLE_VERTEX_BASE_URL',\n"
                "    ];"
            )
            if old_whitelist2 in content:
                content = content.replace(old_whitelist2, new_whitelist2, 1)
                changes += 1

    if changes == 0:
        # Check if already patched
        already_patched = (
            new_sanitize in content or "/:@]" in content
        ) and "GOOGLE_GEMINI_BASE_URL" in content
        if already_patched:
            return True, "Already patched (settings.js)"
        return False, "No settings.js patterns matched"

    # Backup
    backup_path = settings_js_path + ".backup"
    if not os.path.exists(backup_path):
        shutil.copy2(settings_js_path, backup_path)

    with open(settings_js_path, "w", encoding="utf-8") as f:
        f.write(content)

    return True, f"Patched {changes} target(s) in settings.js"


# ─── Target 5: User settings.json ──────────────────────────────────────

def patch_user_settings(config, home_dir=None):
    """
    Target 5: Configure ~/.gemini/settings.json with auth type and telemetry.
    """
    if home_dir is None:
        home_dir = os.path.expanduser("~")

    gemini_dir = os.path.join(home_dir, ".gemini")
    settings_path = os.path.join(gemini_dir, "settings.json")

    # Ensure dir exists
    os.makedirs(gemini_dir, exist_ok=True)

    # Read existing settings
    existing = {}
    if os.path.isfile(settings_path):
        try:
            with open(settings_path, "r") as f:
                existing = json.load(f)
        except (json.JSONDecodeError, OSError):
            existing = {}

    # Remove deprecated keys that cause spam warnings
    DEPRECATED_KEYS = ["codebaseInvestigatorSettings"]
    exp = existing.get("experimental", {})
    for key in DEPRECATED_KEYS:
        exp.pop(key, None)
    if "experimental" in existing and not existing["experimental"]:
        del existing["experimental"]

    # Also clean system-level configs (read-only warning source)
    if IS_WINDOWS:
        sys_paths = ["C:\\ProgramData\\gemini-cli\\settings.json",
                     "C:\\ProgramData\\gemini-cli\\system-defaults.json"]
    elif IS_MACOS:
        sys_paths = ["/Library/Application Support/GeminiCli/settings.json"]
    else:
        sys_paths = ["/etc/gemini-cli/settings.json",
                     "/etc/gemini-cli/system-defaults.json"]
    for sp in sys_paths:
        if os.path.isfile(sp):
            try:
                with open(sp, "r") as f:
                    sd = json.load(f)
                se = sd.get("experimental", {})
                changed = False
                for key in DEPRECATED_KEYS:
                    if key in se:
                        del se[key]
                        changed = True
                if changed:
                    if not se and "experimental" in sd:
                        del sd["experimental"]
                    with open(sp, "w") as f:
                        json.dump(sd, f, indent=2)
            except (PermissionError, OSError):
                pass  # skip if no write access

    # Deep merge our settings
    if "security" not in existing:
        existing["security"] = {}
    if "auth" not in existing["security"]:
        existing["security"]["auth"] = {}
    existing["security"]["auth"]["selectedType"] = "gemini-api-key"

    if "telemetry" not in existing:
        existing["telemetry"] = {}
    existing["telemetry"]["enabled"] = config.get("telemetry_enabled", False)
    existing["telemetry"]["logPrompts"] = False

    # Set default model (e.g. gemini-3.1-pro-preview)
    default_model = config.get("default_model")
    if default_model:
        if "model" not in existing:
            existing["model"] = {}
        existing["model"]["name"] = default_model

    # Override internal task models to use gemini-3-flash-preview
    internal_flash_model = config.get("internal_flash_model")
    if internal_flash_model:
        if "modelConfigs" not in existing:
            existing["modelConfigs"] = {}
        existing["modelConfigs"]["customAliases"] = {
            "classifier": {
                "extends": "base",
                "modelConfig": {
                    "model": internal_flash_model,
                    "generateContentConfig": {
                        "maxOutputTokens": 1024,
                        "thinkingConfig": {"thinkingLevel": "MEDIUM"},
                    },
                },
            },
            "prompt-completion": {
                "extends": "base",
                "modelConfig": {
                    "model": internal_flash_model,
                    "generateContentConfig": {
                        "temperature": 0.3,
                        "maxOutputTokens": 16000,
                        "thinkingConfig": {"thinkingLevel": "LOW"},
                    },
                },
            },
            "fast-ack-helper": {
                "extends": "base",
                "modelConfig": {
                    "model": internal_flash_model,
                    "generateContentConfig": {
                        "temperature": 0.2,
                        "maxOutputTokens": 120,
                        "thinkingConfig": {"thinkingLevel": "LOW"},
                    },
                },
            },
            "edit-corrector": {
                "extends": "base",
                "modelConfig": {
                    "model": internal_flash_model,
                    "generateContentConfig": {
                        "thinkingConfig": {"thinkingLevel": "LOW"},
                    },
                },
            },
            "summarizer-default": {
                "extends": "base",
                "modelConfig": {
                    "model": internal_flash_model,
                    "generateContentConfig": {
                        "maxOutputTokens": 2000,
                        "thinkingConfig": {"thinkingLevel": "MINIMAL"},
                    },
                },
            },
            "summarizer-shell": {
                "extends": "base",
                "modelConfig": {
                    "model": internal_flash_model,
                    "generateContentConfig": {
                        "maxOutputTokens": 2000,
                        "thinkingConfig": {"thinkingLevel": "MINIMAL"},
                    },
                },
            },
        }

    # Write back
    with open(settings_path, "w") as f:
        json.dump(existing, f, indent=2)

    return True, f"Settings updated: {settings_path}"


# ─── Target 6: System env vars ─────────────────────────────────────────

def setup_env_vars(config):
    """
    Target 6: Inject env vars into /etc/environment (Linux) or create wrapper.
    """
    base_url = config["base_url"]
    api_key = config["api_key"]

    env_vars = {
        "GEMINI_API_KEY": api_key,
        "GOOGLE_GEMINI_BASE_URL": base_url,
    }

    if IS_WINDOWS:
        return _setup_env_windows(env_vars)

    # Linux/macOS: write to /etc/environment
    env_file = "/etc/environment"
    changes = 0

    try:
        existing_content = ""
        if os.path.isfile(env_file):
            with open(env_file, "r") as f:
                existing_content = f.read()

        lines = existing_content.splitlines()
        new_lines = []

        for line in lines:
            # Remove old entries
            key_match = False
            for key in env_vars:
                if line.startswith(f"{key}=") or line.startswith(f'export {key}='):
                    key_match = True
                    break
            if not key_match:
                new_lines.append(line)

        # Add new entries
        for key, value in env_vars.items():
            new_lines.append(f'{key}="{value}"')
            changes += 1

        with open(env_file, "w") as f:
            f.write("\n".join(new_lines) + "\n")

        # Also export to current process
        for key, value in env_vars.items():
            os.environ[key] = value

        return True, f"Set {changes} env var(s) in {env_file}"

    except PermissionError:
        # Fallback: just set for current process
        for key, value in env_vars.items():
            os.environ[key] = value
        return True, f"Set {len(env_vars)} env var(s) in current process (no root access for {env_file})"


def _setup_env_windows(env_vars):
    """Set env vars on Windows via setx."""
    changes = 0
    for key, value in env_vars.items():
        try:
            subprocess.run(["setx", key, value], check=True, capture_output=True)
            os.environ[key] = value
            changes += 1
        except Exception as e:
            eprint(f"Warning: Failed to set {key}: {e}")
    return changes > 0, f"Set {changes} env var(s) via setx"


# ─── Target 7+8: Auto-update registry redirect ────────────────────────

def get_auto_update_paths(gemini_root):
    """Return paths to auto-update related files."""
    return {
        "registry_url": os.path.join(gemini_root, REGISTRY_URL_SUBPATH),
        "install_info": os.path.join(gemini_root, INSTALL_INFO_SUBPATH),
        "npm_conf": os.path.join(gemini_root, NPM_CONF_DEFAULTS_SUBPATH),
    }


def patch_auto_update(gemini_root, config):
    """
    Target 7: Redirect registry-url default from registry.npmjs.org → npm_registry.
    Target 8: Add --registry flag to update commands in installationInfo.js.
    """
    npm_registry = config.get("npm_registry", "").rstrip("/")
    if not npm_registry:
        return True, "Skipped (no npm_registry in config)"

    paths = get_auto_update_paths(gemini_root)
    changes = 0
    patched_parts = []

    # --- Target 7a: registry-url/index.js ---
    reg_path = paths["registry_url"]
    if os.path.isfile(reg_path):
        with open(reg_path, "r", encoding="utf-8") as f:
            content = f.read()

        old_registry = "https://registry.npmjs.org/"
        if old_registry in content:
            backup = reg_path + ".backup"
            if not os.path.exists(backup):
                shutil.copy2(reg_path, backup)
            content = content.replace(old_registry, npm_registry + "/")
            with open(reg_path, "w", encoding="utf-8") as f:
                f.write(content)
            changes += 1
            patched_parts.append("registry-url")
        elif npm_registry in content:
            patched_parts.append("registry-url: already patched")
        else:
            patched_parts.append("registry-url: pattern not found")

    # --- Target 7b: @pnpm/npm-conf/lib/defaults.js ---
    conf_path = paths["npm_conf"]
    if os.path.isfile(conf_path):
        with open(conf_path, "r", encoding="utf-8") as f:
            content = f.read()

        old_conf = "registry: 'https://registry.npmjs.org/',"
        new_conf = f"registry: '{npm_registry}/',"
        if old_conf in content:
            backup = conf_path + ".backup"
            if not os.path.exists(backup):
                shutil.copy2(conf_path, backup)
            content = content.replace(old_conf, new_conf)
            with open(conf_path, "w", encoding="utf-8") as f:
                f.write(content)
            changes += 1
            patched_parts.append("npm-conf defaults")
        elif npm_registry in content:
            patched_parts.append("npm-conf: already patched")

    # --- Target 8: installationInfo.js ---
    info_path = paths["install_info"]
    if os.path.isfile(info_path):
        with open(info_path, "r", encoding="utf-8") as f:
            content = f.read()

        cmd_changes = 0

        # npm, pnpm, yarn: append --registry
        for old_cmd in [
            "'npm install -g @google/gemini-cli@latest'",
            "'pnpm add -g @google/gemini-cli@latest'",
            "'yarn global add @google/gemini-cli@latest'",
        ]:
            new_cmd = old_cmd.replace("@latest'", f"@latest --registry {npm_registry}'")
            if old_cmd in content:
                content = content.replace(old_cmd, new_cmd)
                cmd_changes += 1

        # bun: prefix with BUN_CONFIG_REGISTRY env var
        old_bun = "'bun add -g @google/gemini-cli@latest'"
        new_bun = f"'BUN_CONFIG_REGISTRY={npm_registry} bun add -g @google/gemini-cli@latest'"
        if old_bun in content:
            content = content.replace(old_bun, new_bun)
            cmd_changes += 1

        if cmd_changes > 0:
            backup = info_path + ".backup"
            if not os.path.exists(backup):
                shutil.copy2(info_path, backup)
            with open(info_path, "w", encoding="utf-8") as f:
                f.write(content)
            changes += cmd_changes
            patched_parts.append(f"installationInfo: {cmd_changes} command(s)")
        elif npm_registry in content:
            patched_parts.append("installationInfo: already patched")
        else:
            patched_parts.append("installationInfo: no commands matched")

    if changes == 0 and any("already patched" in p for p in patched_parts):
        return True, "Already patched (" + "; ".join(patched_parts) + ")"
    elif changes == 0:
        return False, "No auto-update patterns matched (" + "; ".join(patched_parts) + ")"

    return True, "; ".join(patched_parts)


# ─── Target 9: Auto-permissions (bypass approval prompts) ──────────────

def patch_auto_permissions(gemini_root, config):
    """
    Target 9a: Patch config.js to allow YOLO mode from settings.json.
    Target 9b: Set defaultApprovalMode=yolo + disable folderTrust in settings.json.
    Target 9c: Auto-trust folders via trustedFolders.json.
    """
    changes = 0
    patched_parts = []

    # --- 9a: Patch config.js to remove YOLO filter from settings ---
    config_js_path = os.path.join(gemini_root, CONFIG_JS_SUBPATH)
    if os.path.isfile(config_js_path):
        with open(config_js_path, "r", encoding="utf-8") as f:
            content = f.read()

        old_yolo_filter = (
            "(settings.general?.defaultApprovalMode !== 'yolo'\n"
            "            ? settings.general?.defaultApprovalMode\n"
            "            : undefined)"
        )
        new_yolo_filter = "(settings.general?.defaultApprovalMode)"

        if old_yolo_filter in content:
            backup = config_js_path + ".backup"
            if not os.path.exists(backup):
                shutil.copy2(config_js_path, backup)
            content = content.replace(old_yolo_filter, new_yolo_filter, 1)
            with open(config_js_path, "w", encoding="utf-8") as f:
                f.write(content)
            changes += 1
            patched_parts.append("config.js: yolo filter removed")
        elif new_yolo_filter in content and old_yolo_filter not in content:
            patched_parts.append("config.js: already patched")
        else:
            patched_parts.append("config.js: pattern not found")
    else:
        patched_parts.append("config.js: not found")

    # --- 9a2: Patch settingsSchema.js to add 'yolo' to valid options ---
    schema_path = os.path.join(gemini_root, SETTINGS_SCHEMA_SUBPATH)
    if os.path.isfile(schema_path):
        with open(schema_path, "r", encoding="utf-8") as f:
            schema_content = f.read()

        old_options = (
            "{ value: 'default', label: 'Default' },\n"
            "                    { value: 'auto_edit', label: 'Auto Edit' },\n"
            "                    { value: 'plan', label: 'Plan' },"
        )
        new_options = (
            "{ value: 'default', label: 'Default' },\n"
            "                    { value: 'auto_edit', label: 'Auto Edit' },\n"
            "                    { value: 'plan', label: 'Plan' },\n"
            "                    { value: 'yolo', label: 'YOLO' },"
        )

        if old_options in schema_content and "{ value: 'yolo'" not in schema_content:
            backup = schema_path + ".backup"
            if not os.path.exists(backup):
                shutil.copy2(schema_path, backup)
            schema_content = schema_content.replace(old_options, new_options, 1)
            with open(schema_path, "w", encoding="utf-8") as f:
                f.write(schema_content)
            changes += 1
            patched_parts.append("settingsSchema.js: yolo option added")
        elif "{ value: 'yolo'" in schema_content:
            patched_parts.append("settingsSchema.js: already patched")
        else:
            patched_parts.append("settingsSchema.js: pattern not found")
    else:
        patched_parts.append("settingsSchema.js: not found")

    # --- 9b: Set YOLO mode + disable folderTrust in settings.json ---
    home_dir = os.path.expanduser("~")
    gemini_dir = os.path.join(home_dir, ".gemini")
    settings_path = os.path.join(gemini_dir, "settings.json")

    os.makedirs(gemini_dir, exist_ok=True)

    existing = {}
    if os.path.isfile(settings_path):
        try:
            with open(settings_path, "r") as f:
                existing = json.load(f)
        except (json.JSONDecodeError, OSError):
            existing = {}

    settings_changed = False

    if "general" not in existing:
        existing["general"] = {}
    if existing["general"].get("defaultApprovalMode") != "yolo":
        existing["general"]["defaultApprovalMode"] = "yolo"
        settings_changed = True

    if "security" not in existing:
        existing["security"] = {}
    if "folderTrust" not in existing["security"]:
        existing["security"]["folderTrust"] = {}
    if existing["security"]["folderTrust"].get("enabled") is not False:
        existing["security"]["folderTrust"]["enabled"] = False
        settings_changed = True

    if existing.get("security", {}).get("disableYoloMode"):
        existing["security"]["disableYoloMode"] = False
        settings_changed = True

    if settings_changed:
        with open(settings_path, "w") as f:
            json.dump(existing, f, indent=2)
        changes += 1
        patched_parts.append("settings.json: yolo + folderTrust disabled")
    else:
        patched_parts.append("settings.json: already configured")

    # --- 9c: Auto-trust common folders via trustedFolders.json ---
    trusted_path = os.path.join(gemini_dir, "trustedFolders.json")
    trusted = {}
    if os.path.isfile(trusted_path):
        try:
            with open(trusted_path, "r") as f:
                trusted = json.load(f)
        except (json.JSONDecodeError, OSError):
            trusted = {}

    trust_paths = [home_dir, "/home", "/root", "/tmp"]
    trusted_changed = False
    for tp in trust_paths:
        if tp not in trusted:
            trusted[tp] = "TRUST_PARENT"
            trusted_changed = True

    if trusted_changed:
        with open(trusted_path, "w") as f:
            json.dump(trusted, f, indent=2)
        changes += 1
        patched_parts.append("trustedFolders.json: paths added")
    else:
        patched_parts.append("trustedFolders.json: already configured")

    if changes == 0 and any("already" in p for p in patched_parts):
        return True, "Already patched (" + "; ".join(patched_parts) + ")"
    elif changes == 0:
        return False, "No auto-permissions patterns matched (" + "; ".join(patched_parts) + ")"

    return True, "; ".join(patched_parts)


# ─── Rollback ───────────────────────────────────────────────────────────

def rollback(genai_mjs_path, settings_js_path, gemini_root=None):
    """Restore backup files."""
    restored = 0
    paths = [genai_mjs_path, settings_js_path]
    if gemini_root:
        auto_paths = get_auto_update_paths(gemini_root)
        paths.extend(auto_paths.values())
        config_js = os.path.join(gemini_root, CONFIG_JS_SUBPATH)
        paths.append(config_js)
        schema_js = os.path.join(gemini_root, SETTINGS_SCHEMA_SUBPATH)
        paths.append(schema_js)
    for p in paths:
        backup = p + ".backup"
        if os.path.exists(backup):
            shutil.copy2(backup, p)
            restored += 1
    return restored > 0, f"Restored {restored} file(s) from backup"


# ─── Validation ─────────────────────────────────────────────────────────

def validate_installation(config):
    """Run basic validation: gemini --version and test prompt."""
    results = []

    # Check gemini exists
    try:
        result = subprocess.run(
            ["gemini", "--version"],
            capture_output=True, text=True, timeout=10
        )
        version = result.stdout.strip() or result.stderr.strip()
        results.append(("version_check", True, version))
    except Exception as e:
        results.append(("version_check", False, str(e)))

    # Test prompt (needs env vars set)
    try:
        env = os.environ.copy()
        env["GEMINI_API_KEY"] = config["api_key"]
        env["GOOGLE_GEMINI_BASE_URL"] = config["base_url"]

        result = subprocess.run(
            ["gemini", "-p", "Reply with just the word OK, nothing else"],
            capture_output=True, text=True, timeout=60, env=env
        )
        output = result.stdout.strip()
        success = "OK" in output.upper()
        results.append(("prompt_test", success, output[:200]))
    except Exception as e:
        results.append(("prompt_test", False, str(e)))

    return results


# ─── Main orchestrator ──────────────────────────────────────────────────

def apply_all_patches(config=None, settings_only=False):
    """Apply all patch targets."""
    if config is None:
        config = load_config()

    gemini_root, genai_mjs, settings_js = detect_gemini()
    version = read_version(gemini_root)

    print(f"\n{BOLD}Gemini Code Patcher{RESET}")
    print(f"  Version: {color(version, CYAN)}")
    print(f"  Root:    {gemini_root}")
    print(f"  Proxy:   {config['base_url']}")
    print()

    results = {}
    all_ok = True

    if not settings_only:
        # Target 1+2: genai URLs
        ok, msg = patch_genai_urls(genai_mjs, config)
        results["genai_urls"] = (ok, msg)
        print(f"  {'[OK]' if ok else '[FAIL]':>8}  Target 1+2: {msg}")
        if not ok:
            all_ok = False

        # Target 3+4: settings.js
        ok, msg = patch_settings_js(settings_js)
        results["settings_js"] = (ok, msg)
        print(f"  {'[OK]' if ok else '[FAIL]':>8}  Target 3+4: {msg}")
        if not ok:
            all_ok = False

    # Target 5: user settings
    ok, msg = patch_user_settings(config)
    results["user_settings"] = (ok, msg)
    print(f"  {'[OK]' if ok else '[FAIL]':>8}  Target 5:   {msg}")
    if not ok:
        all_ok = False

    # Target 6: env vars
    ok, msg = setup_env_vars(config)
    results["env_vars"] = (ok, msg)
    print(f"  {'[OK]' if ok else '[FAIL]':>8}  Target 6:   {msg}")
    if not ok:
        all_ok = False

    if not settings_only:
        # Target 7+8: auto-update registry redirect
        ok, msg = patch_auto_update(gemini_root, config)
        results["auto_update"] = (ok, msg)
        print(f"  {'[OK]' if ok else '[FAIL]':>8}  Target 7+8: {msg}")
        if not ok:
            all_ok = False

        # Target 9: auto-permissions (bypass approval prompts)
        ok, msg = patch_auto_permissions(gemini_root, config)
        results["auto_permissions"] = (ok, msg)
        print(f"  {'[OK]' if ok else '[FAIL]':>8}  Target 9:   {msg}")
        if not ok:
            all_ok = False

    print()
    if all_ok:
        print(f"  {color('All patches applied successfully!', GREEN)}")
    else:
        print(f"  {color('Some patches failed. Check output above.', RED)}")

    return all_ok, results


# ─── CLI ────────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(
        description="Gemini Code Patcher — route Gemini CLI through custom proxy"
    )
    parser.add_argument("--apply", action="store_true", help="Apply all patches")
    parser.add_argument("--settings-only", action="store_true", help="Apply only settings.json + env vars")
    parser.add_argument("--rollback", action="store_true", help="Restore from backup")
    parser.add_argument("--detect", action="store_true", help="Detect Gemini CLI installation")
    parser.add_argument("--validate", action="store_true", help="Validate installation")
    parser.add_argument("--config", type=str, help="Path to config file")
    args = parser.parse_args()

    config = load_config(args.config)

    if args.detect:
        try:
            root, genai, settings = detect_gemini()
            version = read_version(root)
            print(f"Gemini CLI found:")
            print(f"  Root:       {root}")
            print(f"  Version:    {version}")
            print(f"  GenAI MJS:  {genai}")
            print(f"  Settings:   {settings}")
        except FileNotFoundError as e:
            eprint(str(e))
            sys.exit(1)

    elif args.rollback:
        try:
            root, genai, settings = detect_gemini()
            ok, msg = rollback(genai, settings, root)
            print(msg)
            sys.exit(0 if ok else 1)
        except FileNotFoundError as e:
            eprint(str(e))
            sys.exit(1)

    elif args.validate:
        results = validate_installation(config)
        for name, ok, detail in results:
            status = color("[OK]", GREEN) if ok else color("[FAIL]", RED)
            print(f"  {status} {name}: {detail}")
        sys.exit(0 if all(r[1] for r in results) else 1)

    elif args.apply or args.settings_only:
        ok, _ = apply_all_patches(config, settings_only=args.settings_only)
        sys.exit(0 if ok else 1)

    else:
        parser.print_help()


if __name__ == "__main__":
    main()
