#!/usr/bin/env python3
"""
llama-index 探针安装脚本：pip install whl + 生成 config.json。

与 opencode 探针不同：本脚本**自带 whl 路径参数**，执行时先 pip install
whl，再把上报参数写入 config.json——whl 与 install.py 单独提供，用户
只需执行一条命令即可完成部署。

用法（10 个参数）：
    python install.py \
        --whl path/to/huaweicloud_opentelemetry_instrumentation_llama_index-*.whl \
        --agent-id {{agent_id}} \
        --domain-id {{domain_id}} \
        --project-id {{project_id}} \
        --endpoint {{trace_endpoint}} \
        --token {{trace_token}} \
        --aom-endpoint {{metric_endpoint}} \
        --aom-access-code {{metric_token}} \
        --aom-prometheus-id {{prom_id}} \
        --resource-type agent

作用：
  1. 校验 whl 文件存在
  2. pip install <whl>（安装 llama 探针 + 注册 entry point）
  3. 用携带参数生成 config.json 到 <用户主目录>/.llama-index-metrics/config.json
  4. 提示启动方式（opentelemetry-instrument / sitecustomize / demo）

跨平台：自动适配 Windows / Linux：
  - 路径用 pathlib.Path + os.path.join，home 目录用 Path.home()：
      Windows: %USERPROFILE%\\.llama-index-metrics\\config.json
      Linux:   $HOME/.llama-index-metrics/config.json
  - 探针 (config.py) 用 os.path.expanduser("~/.llama-index-metrics") 读取——
    与本脚本写入位置完全对齐。
  - pip 用 [sys.executable, '-m', 'pip', 'install', whl]，避免 Linux 下 pip
    命令不在 PATH。
  - Linux 下给本脚本加 +x 权限（首次运行后即可 ./install.py 直接执行）。
"""

import argparse
import json
import os
import subprocess
import sys
from pathlib import Path

CONFIG_VERSION = '0.1.0'


def home_dir() -> Path:
    return Path.home()


def ensure_port(endpoint: str, default_port: int = 4317) -> str:
    endpoint = endpoint.strip()
    if ':' in endpoint:
        return endpoint
    return f'{endpoint}:{default_port}'


def json_escape_winpath(p: Path) -> str:
    s = str(p)
    return s.replace('\\', '\\\\') if os.name == 'nt' else s


def json_escape(value: str) -> str:
    if value is None:
        return ''
    return json.dumps(str(value), ensure_ascii=False)[1:-1]


def pip_install(whl: Path) -> int:
    """执行 pip install whl：探针强制重装 + 缺失依赖补装。

    两步安装，满足"只对探针 whl 强制 --force-reinstall，其他已装组件不动"：

    第 1 步 `--force-reinstall --no-deps`：只强制重装探针本体（即使版本相同，
      覆盖 site-packages 旧文件，保证连续执行 install.py 时探针总是最新的）。
      `--no-deps` 确保不连带重装任何依赖组件。
    第 2 步（不带 force）再装一次 whl：pip 发现探针已装（版本满足），只补装
      **缺失** 的依赖（opentelemetry-api/sdk/exporter-otlp/instrumentation/
      semantic-conventions-ai/wrapt）；已存在的依赖显示 "already satisfied"
      跳过，不强制重装、不覆盖用户已有的版本。

    跨平台：用 [sys.executable, '-m', 'pip', ...]，避免 Linux 下 pip 不在 PATH。
    """
    # 第 1 步：探针本体强制重装（不碰依赖）
    cmd1 = [sys.executable, '-m', 'pip', 'install',
            '--force-reinstall', '--no-deps', '--no-cache-dir', str(whl)]
    print(f'[install] Running: {" ".join(cmd1)}')
    result = subprocess.run(cmd1, capture_output=True, text=True)
    if result.returncode != 0:
        print('[install] pip install (probe, no-deps) FAILED:', file=sys.stderr)
        print(result.stderr, file=sys.stderr)
        return result.returncode
    print('[install] 探针 whl 强制重装 OK (force-reinstall, no-deps)')

    # 第 2 步：补装缺失依赖（已存在的自动跳过，不强制重装）
    cmd2 = [sys.executable, '-m', 'pip', 'install',
            '--no-cache-dir', str(whl)]
    print(f'[install] Running: {" ".join(cmd2)}')
    result2 = subprocess.run(cmd2, capture_output=True, text=True)
    if result2.returncode != 0:
        print('[install] pip install (deps) FAILED:', file=sys.stderr)
        print(result2.stderr, file=sys.stderr)
        return result2.returncode
    if 'already satisfied' in result2.stdout.lower():
        print('[install] 依赖已存在，未强制重装（pip already satisfied）')
    else:
        print('[install] 缺失依赖补装 OK')
    return result2.returncode


def is_package_installed(package_name: str) -> bool:
    """通用检查：某个 Python 包是否已安装（基于 importlib.metadata）。"""
    try:
        import importlib.metadata
        dist = importlib.metadata.distribution(package_name)
        return dist is not None
    except Exception:
        return False


def ensure_package_installed(package_name: str, pip_install_name: str = None) -> bool:
    """确保某个 Python 包已安装，未安装则自动 pip install。

    Args:
        package_name: 用于 importlib.metadata 检查的发行版名称（如 'llama-index', 'httpx'）。
        pip_install_name: 用于 pip install 的名称（默认与 package_name 相同）。
                          当 PyPI 包名与 metadata 查询名不一致时使用。

    Returns:
        True 表示成功（已存在或刚安装成功），False 表示安装失败。
    """
    if pip_install_name is None:
        pip_install_name = package_name

    if is_package_installed(package_name):
        print(f'[install] {package_name} 已安装，跳过')
        return True

    print(f'[install] {package_name} 未安装，执行 pip install {pip_install_name} ...')
    cmd = [sys.executable, '-m', 'pip', 'install', '--no-cache-dir', pip_install_name]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(f'[install] ERROR: pip install {pip_install_name} 失败:', file=sys.stderr)
        print(result.stderr, file=sys.stderr)
        return False
    print(f'[install] {package_name} 安装 OK')
    return True


# ---------- 业务代码强依赖清单（examples 目录下的 demo 所必需的第三方库） ----------
# 探针本身的依赖在 whl 的 pyproject.toml 中声明，pip install whl 时自动解决。
# 但 examples 中使用的非标准库（不在探针依赖链中）需要在此处显式兜底检查。
BUSINESS_RUNTIME_DEPS = [
    # app_workflow.py / app_agent_interactive.py 显式 import httpx 构建自定义客户端
    ("httpx", "httpx"),
]


def build_config(args: argparse.Namespace) -> str:
    """生成 config.json（JSONC 注释模板 + 携带值注入），与 llama 探针读取格式一致。"""
    output_dir = home_dir() / '.llama-index-metrics' / 'data'
    config = f'''{{
  "version": "{CONFIG_VERSION}",
  // 插件总开关
  "enabled": true,
  // 日志输出目录（日志存放位置：outputDir/logs/plugin-YYYY-MM-DD.log）。
  "outputDir": "{json_escape_winpath(output_dir)}",
  // 用户 ID（注入 user.id）：为空/不设置则兜底主机名上报
  "user.id": "",
  "features": {{
    "debug": true,                 // debug 日志开关：false=关闭（默认），true=开启（打印 AOM 上报属性明细）
    "captureMessageContent": true   // 是否采集 trace 输入/输出消息内容：true=展示（默认），false=不展示
  }},
  // ============ OTel 上报配置（AgentArts） ============
  "otel": {{
    // APM（Trace 上报）开关：true 开启，false 关闭
    "agentarts_agentops_otel_agent_apm_enable": true,
    // AOM（Metrics 上报）开关：true 开启，false 关闭
    "agentarts_agentops_otel_agent_aom_enable": true,
    // 账号/租户 ID（Wiki 必填维度 domain.id）
    "agent_domain_id": "{json_escape(args.domain_id)}",
    // AgentArts 项目 ID
    "agentarts_project_id": "{json_escape(args.project_id)}",
    // 智能体 ID（AgentArts 创建接入时分配，页面图表必须绑定此值才能看到数据）
    "agent_resource_id": "{json_escape(args.agent_id)}",
    // 资源类型：单智能体填 "agent"，多智能体 "multiagents"，工作流 "workflow"
    "resource_type": "{json_escape(args.resource_type)}",
    // APM 上报认证 token（敏感信息，勿外泄）
    "agentarts_agentops_apm_token": "{json_escape(args.token)}",
    // APM 上报端点（gRPC，端口 4317）
    "agentarts_agentops_apm_exporter_endpoint": "{json_escape(ensure_port(args.endpoint))}",
    // AOM 上报认证 access code（敏感信息，勿外泄）
    "agentarts_agentops_aom_access_code": "{json_escape(args.aom_access_code)}",
    // AOM 上报端点（gRPC，端口 4317）
    "agentarts_agentops_aom_exporter_endpoint": "{json_escape(ensure_port(args.aom_endpoint))}",
    // AOM Prometheus 实例 ID
    "agentarts_agentops_aom_prometheus_id": "{json_escape(args.aom_prometheus_id)}"
  }}
}}
'''
    return config


def main() -> int:
    parser = argparse.ArgumentParser(
        description='Install llama-index probe: pip install whl + write config.json',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=(
            '示例：\n'
            '  python install.py \\\n'
            '    --whl huaweicloud_opentelemetry_instrumentation_llama_index-0.1.0-py3-none-any.whl \\\n'
            '    --agent-id xxx --domain-id xxx --project-id xxx \\\n'
            '    --endpoint 10.83.90.64:4317 --token xxx \\\n'
            '    --aom-endpoint 215.255.142.169:4317 --aom-access-code xxx \\\n'
            '    --aom-prometheus-id xxx --resource-type agent'
        ),
    )
    parser.add_argument('--whl', required=True, help='llama 探针 wheel 文件路径（pip install 用）')
    parser.add_argument('--agent-id', required=True, help='智能体 ID（agent_resource_id，AgentArts 接入分配）')
    parser.add_argument('--domain-id', required=True, help='账号/租户 ID（agent_domain_id，Wiki 必填维度）')
    parser.add_argument('--project-id', required=True, help='AgentArts 项目 ID（agentarts_project_id）')
    parser.add_argument('--endpoint', required=True, help='APM（Trace）上报端点，如 10.83.90.64:4317（缺端口自动补 4317）')
    parser.add_argument('--token', required=True, help='APM 上报认证 token（敏感信息）')
    parser.add_argument('--aom-endpoint', required=True, help='AOM（Metrics）上报端点，如 215.255.142.169:4317（缺端口自动补 4317）')
    parser.add_argument('--aom-access-code', required=True, help='AOM 上报认证 access code（敏感信息）')
    parser.add_argument('--aom-prometheus-id', required=True, help='AOM Prometheus 实例 ID')
    parser.add_argument('--resource-type', required=True, choices=['agent', 'multiagents', 'workflow'],
                        help='资源类型：单智能体 agent，多智能体 multiagents，工作流 workflow')
    args = parser.parse_args()

    # ---------- 1. 校验 whl 存在 ----------
    whl = Path(args.whl).expanduser().resolve()
    if not whl.is_file():
        print(f'[install] ERROR: whl not found: {whl}', file=sys.stderr)
        return 1
    print(f'[install] whl: {whl}')

    # ---------- 2. pip install whl ----------
    rc = pip_install(whl)
    if rc != 0:
        return rc

    # ---------- 2b. 检查并安装业务运行时依赖 ----------
    # 探针本身的依赖在 whl 的 pyproject.toml 中声明，pip install whl 时自动解决。
    # 但 examples 中使用的非标准库（不在探针依赖链中）需要在此处显式兜底检查。

    # 核心框架：llama-index（探针运行期强依赖）
    if not ensure_package_installed("llama-index"):
        return 1

    # 业务代码依赖：examples 目录下的 demo 所需的第三方库
    for package_name, pip_install_name in BUSINESS_RUNTIME_DEPS:
        if not ensure_package_installed(package_name, pip_install_name):
            return 1

    # ---------- 3. 写 config.json 到 ~/.llama-index-metrics ----------
    config_dir = home_dir() / '.llama-index-metrics'
    config_file = config_dir / 'config.json'
    config_dir.mkdir(parents=True, exist_ok=True)
    config_file.write_text(build_config(args), encoding='utf-8')
    print(f'[install] Config file written with provided values: {config_file}')

    # Linux 下给自己加可执行权限（便于用户后续 ./install.py 直接跑）
    if os.name == 'posix':
        try:
            script_path = Path(__file__).resolve()
            script_path.chmod(script_path.stat().st_mode | 0o755)
        except OSError:
            pass

    # ---------- 4. 提示启动方式 ----------
    print()
    print('[install] Done. 启动方式（三选一）：')
    print('  [方式1] opentelemetry-instrument python app.py   # 零代码侵入（推荐，entry point 自动装配）')
    print('  [方式2] sitecustomize.py 放 PYTHONPATH（调 auto_instrument()）')
    print('  [方式3] 业务代码里 from opentelemetry.instrumentation.huawei_llama_index import auto_instrument; auto_instrument()')
    logs_dir = home_dir() / '.llama-index-metrics' / 'data' / 'logs'
    print(f'[install] 日志目录: {logs_dir}')
    return 0


if __name__ == '__main__':
    sys.exit(main())
