#!/usr/bin/env bash
# Wegoo adaptation; source URLs and hashes are recorded in sources.json.
set -euo pipefail
set +x
if ! command -v python3 >/dev/null 2>&1; then
  printf '%s\n' '需要 Python 3，请安装后重试。' >&2
  printf '%s\n' '如仍遇到问题，请登录 Wegoo 后台提交工单：https://ai.wegoo.site/tickets ，附上报错信息和操作系统，请勿提供 API Key。' >&2
  exit 1
fi
python3 - codex <<'WEGOO_PYTHON'
"""Wegoo installers: adapted from the archived APIKEY installers (sources.json)."""
import datetime
import getpass
import json
import os
from pathlib import Path
import re
import shutil
import socket
import ssl
import subprocess
import sys
import tempfile
import urllib.error
import urllib.request

BASE_URL = 'https://ai.wegoo.site'
MODELS_URL = BASE_URL + '/v1/models'


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def available_models(api_key):
    # No redirects: never forward the credential to a different endpoint.
    opener = urllib.request.build_opener(NoRedirect(), urllib.request.HTTPSHandler(context=ssl.create_default_context()))
    request = urllib.request.Request(MODELS_URL, headers={
        'Authorization': 'Bearer ' + api_key,
        'User-Agent': 'Wegoo-Installer/1.0',
        'Accept': 'application/json',
    })
    try:
        with opener.open(request, timeout=20) as response:
            payload = json.load(response)
    except urllib.error.HTTPError as exc:
        raise ValueError('读取模型列表失败（HTTP %s），请检查密钥及分组。' % exc.code) from None
    except json.JSONDecodeError:
        raise ValueError('模型接口未返回有效 JSON，可能被代理或网络验证页拦截；未修改配置。') from None
    except (OSError, ValueError) as exc:
        reason = exc.reason if isinstance(exc, urllib.error.URLError) else exc
        if isinstance(reason, ssl.SSLCertVerificationError):
            message = 'Python 无法验证 HTTPS 证书，请检查 Python CA 证书及代理证书配置。'
        elif isinstance(reason, socket.gaierror):
            message = '无法解析 ai.wegoo.site，请检查 DNS 和代理设置。'
        elif isinstance(reason, TimeoutError):
            message = '连接 Wegoo 模型接口超时，请检查网络和代理设置。'
        else:
            message = '无法连接 Wegoo 模型接口，请检查网络和代理设置。'
        raise ValueError(message + '未修改配置。') from None
    if not isinstance(payload, dict) or not isinstance(payload.get('data'), list):
        raise ValueError('模型目录格式异常，已停止；未修改配置。')
    models = []
    for item in payload['data']:
        name = item.get('id') if isinstance(item, dict) else None
        if isinstance(name, str) and re.fullmatch(r'[A-Za-z0-9._:/@+-]+', name) and name not in models:
            models.append(name)
    if not models:
        raise ValueError('此密钥没有可用模型，请检查分组和模型权限。')
    return models


def mapping(parent, key):
    if key not in parent:
        parent[key] = {}
    if not isinstance(parent[key], dict):
        raise ValueError('现有配置中的 %s 不是对象，已停止写入。' % key)
    return parent[key]


def read_object(path, yaml=False):
    if not path.exists():
        return {}
    if path.is_symlink():
        raise ValueError('配置文件是符号链接，请手动配置：' + str(path))
    try:
        if yaml:
            import yaml as yaml_module
            data = yaml_module.safe_load(path.read_text(encoding='utf-8-sig'))
        else:
            data = json.loads(path.read_text(encoding='utf-8-sig'))
    except Exception:
        raise ValueError('无法解析现有配置，原文件已保留：' + str(path)) from None
    if not isinstance(data, dict):
        raise ValueError('现有配置不是对象，原文件已保留：' + str(path))
    return data


def private_write(path, content):
    # Stage and fsync before replacing; backups and new files are owner-only.
    if path.is_symlink():
        raise ValueError('配置文件是符号链接，请手动配置：' + str(path))
    path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
    if path.exists():
        stamp = datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')
        backup = path.with_name(path.name + '.bak.' + stamp)
        with backup.open('xb') as target:
            os.chmod(backup, 0o600)
            target.write(path.read_bytes())
    fd, staged = tempfile.mkstemp(prefix='.' + path.name + '.', dir=path.parent)
    try:
        with os.fdopen(fd, 'w', encoding='utf-8') as target:
            os.chmod(staged, 0o600)
            target.write(content)
            target.flush()
            os.fsync(target.fileno())
        os.replace(staged, path)
    finally:
        if os.path.exists(staged):
            os.unlink(staged)


def write_config(tool, root, key, model, protocol):
    if tool == 'codex':
        # Explicit overwrite confirmation happens before this call. auth.json is
        # left untouched; a provider-scoped bearer cannot sign out another account.
        path = root / 'config.toml'
        text = 'model_provider = "wegoo"\nmodel = ' + json.dumps(model) + '\n\n'
        text += '[model_providers.wegoo]\nname = "Wegoo"\nbase_url = "https://ai.wegoo.site/v1"\nwire_api = "responses"\n'
        text += 'experimental_bearer_token = ' + json.dumps(key) + '\n'
        private_write(path, text)
        return path
    if tool == 'claude-code':
        path = root / 'settings.json'
        data = read_object(path)
        env = mapping(data, 'env')
        # Avoid ambiguous API-key vs bearer-token precedence inside settings.
        env.pop('ANTHROPIC_API_KEY', None)
        env.update(ANTHROPIC_BASE_URL=BASE_URL, ANTHROPIC_AUTH_TOKEN=key, ANTHROPIC_MODEL=model)
    elif tool == 'openclaw':
        path = root / 'openclaw.json'
        data = read_object(path)
        models = mapping(data, 'models')
        models.setdefault('mode', 'merge')
        provider_id = 'wegoo-claude' if protocol == 'anthropic' else 'wegoo-codex'
        provider = mapping(mapping(models, 'providers'), provider_id)
        provider.update(baseUrl=BASE_URL if protocol == 'anthropic' else BASE_URL + '/v1', apiKey=key,
                        api='anthropic-messages' if protocol == 'anthropic' else 'openai-responses',
                        models=[{'id': model, 'name': model}])
        defaults = mapping(mapping(data, 'agents'), 'defaults')
        selected = provider_id + '/' + model
        if isinstance(defaults.get('model'), str):
            defaults['model'] = {'primary': selected}
        else:
            mapping(defaults, 'model')['primary'] = selected
        if 'models' in defaults:
            mapping(defaults, 'models').setdefault(selected, {})
    elif tool == 'hermes':
        import yaml
        path = root / 'config.yaml'
        data = read_object(path, yaml=True)
        provider_id = 'wegoo-claude' if protocol == 'anthropic' else 'wegoo-codex'
        provider = mapping(mapping(data, 'providers'), provider_id)
        for field in ('key_env', 'api_key_env', 'key_cmd', 'base_url', 'url', 'api_mode'):
            provider.pop(field, None)
        provider.update(api=BASE_URL if protocol == 'anthropic' else BASE_URL + '/v1', api_key=key,
                        transport='anthropic_messages' if protocol == 'anthropic' else 'codex_responses',
                        default_model=model)
        selected = mapping(data, 'model')
        for field in ('base_url', 'api_key', 'key_env'):
            selected.pop(field, None)
        selected.update(provider=provider_id, default=model)
        private_write(path, yaml.safe_dump(data, allow_unicode=True, sort_keys=False))
        return path
    else:
        raise ValueError('不支持的工具。')
    private_write(path, json.dumps(data, ensure_ascii=False, indent=2) + '\n')
    return path


def main(tool):
    # curl | bash leaves stdin attached to source code. All input uses the TTY.
    try:
        tty_in = open('/dev/tty', 'r')
        tty_out = open('/dev/tty', 'w')
    except OSError:
        raise ValueError('需要交互终端。请下载脚本后，在终端中运行 bash ' + tool + '.sh。') from None
    if os.environ.get('SUDO_USER'):
        raise ValueError('请使用自己的用户直接运行脚本，不要加 sudo。')
    os.umask(0o077)

    def ask(prompt):
        tty_out.write(prompt)
        tty_out.flush()
        value = tty_in.readline()
        if not value:
            raise ValueError('输入已取消，未继续配置。')
        return value.strip()

    print('Wegoo ' + ('ChatGPT / Codex CLI' if tool == 'codex' else tool) + ' 配置')
    print('密钥请从 Wegoo 控制台的 API 密钥页面获取；请选择支持所需接口的分组。')
    cli_target = True
    if tool == 'codex':
        choice = ask('1) ChatGPT 桌面端  2) Codex CLI [1]: ') or '1'
        if choice not in ('1', '2'):
            raise ValueError('无效选择。')
        cli_target = choice == '2'
        if not cli_target:
            print('Windows 官方下载：https://get.microsoft.com/installer/download/9PLM9XGG6VKS?cid=website_cta_psi')
    protocol = 'anthropic' if tool == 'claude-code' else 'openai'
    if tool in ('openclaw', 'hermes'):
        choice = ask('1) Claude / Anthropic  2) Codex / Responses [2]: ') or '2'
        if choice not in ('1', '2'):
            raise ValueError('无效选择。')
        protocol = 'anthropic' if choice == '1' else 'openai'
    if tool == 'hermes':
        try:
            import yaml
        except ImportError:
            raise ValueError('需要 Python 的 PyYAML 模块；请安装后重试，或使用文档中的 hermes model 向导。') from None
    key = getpass.getpass('请输入 Wegoo API Key（不回显）: ', stream=tty_out).strip()
    if not re.fullmatch(r'[A-Za-z0-9._-]+', key):
        raise ValueError('API Key 为空或含有空格、引号等非法字符，请重新复制。')
    models = available_models(key)
    print('该密钥可用的模型（请选支持 ' + ('Anthropic Messages' if protocol == 'anthropic' else 'Responses') + ' 的模型）：')
    for i, name in enumerate(models, 1):
        print('  %d) %s' % (i, name))
    choice = ask('模型序号: ')
    if not choice.isdigit() or not 1 <= int(choice) <= len(models):
        raise ValueError('无效模型序号。')
    model = models[int(choice) - 1]
    if tool == 'codex':
        root = Path(os.environ.get('CODEX_HOME') or Path.home() / '.codex').expanduser()
    elif tool == 'hermes':
        root = Path(os.environ.get('HERMES_HOME') or Path.home() / '.hermes').expanduser()
    elif tool == 'claude-code':
        root = Path(os.environ.get('CLAUDE_CONFIG_DIR') or Path.home() / '.claude').expanduser()
    else:
        config_override = os.environ.get('OPENCLAW_CONFIG_PATH')
        if config_override:
            raise ValueError('检测到 OPENCLAW_CONFIG_PATH 自定义路径，请按文档手动合并配置，未写入默认目录。')
        root = Path(os.environ.get('OPENCLAW_STATE_DIR') or Path.home() / '.openclaw').expanduser()
    if tool == 'codex' and (root / 'config.toml').exists():
        print('现有 config.toml 将先备份再替换，原 MCP、profile 和权限设置仅保留在备份中。')
        if ask('继续替换？[y/N]: ').lower() not in ('y', 'yes'):
            print('已取消，原配置保持不变。')
            return
    command = 'claude' if tool == 'claude-code' else tool
    if cli_target and tool in ('codex', 'claude-code') and not shutil.which(command):
        if ask('未找到 ' + command + '，使用 npm 安装？[y/N]: ').lower() in ('y', 'yes'):
            if not shutil.which('npm') or not shutil.which('node'):
                raise ValueError('请先从 https://nodejs.org 安装 Node.js LTS，然后重新运行脚本。')
            package = '@openai/codex' if tool == 'codex' else '@anthropic-ai/claude-code'
            subprocess.run(['npm', 'install', '-g', package], check=True)
    path = write_config(tool, root, key, model, protocol)
    print('配置已保存：' + str(path))
    print('已有文件的备份位于同目录 .bak.*；含密钥的配置和备份仅当前用户可读写。')
    print('重新打开 ChatGPT。' if tool == 'codex' and not cli_target else '重新启动 ' + command + ' 即可读取配置。')
    if tool == 'openclaw':
        print('运行 openclaw config validate 验证后，重启正在运行的网关。')
    if tool in ('openclaw', 'hermes') and not shutil.which(command):
        print('尚未检测到 ' + command + '；本脚本配置连接，不安装该客户端。')


if __name__ == '__main__':
    try:
        main(sys.argv[1])
    except (ValueError, OSError, subprocess.CalledProcessError) as exc:
        # Do not print arbitrary exceptions that may contain a credential or body.
        print(str(exc) if isinstance(exc, ValueError) else '配置或安装失败；请检查文件权限、依赖和网络。', file=sys.stderr)
        print('如仍遇到问题，请登录 Wegoo 后台提交工单：https://ai.wegoo.site/tickets ，附上报错信息和操作系统，请勿提供 API Key。', file=sys.stderr)
        sys.exit(1)
    except KeyboardInterrupt:
        print('\n已取消。', file=sys.stderr)
        sys.exit(130)

WEGOO_PYTHON
