"""
光湖语言系统 · API 代理网关 v1.0
Guanghu Language System · API Proxy Gateway

人格体不需要密钥 → 人格体用语言说 → 代理取密钥 → 代理调 API → 返回结果
密钥不出保险库 · 人格体代码零密钥 · 审计日志全追溯

部署: SG-001 /opt/zhuyuan/api-proxy/server.py
端口: 127.0.0.1:8911（仅本地 · 不对外开放）
"""

import os, json, time, hmac, hashlib, secrets as _secrets
from datetime import datetime
from functools import wraps
from flask import Flask, request, jsonify, g

# ──────────────────────────────────────────
# 配置
# ──────────────────────────────────────────
APP = Flask(__name__)
PROXY_PORT = int(os.environ.get("API_PROXY_PORT", "8911"))
SESSION_TTL = 3600          # session 1小时过期
SESSION_SECRET = os.environ.get("API_PROXY_SECRET", _secrets.token_hex(32))

# GZ-006 保险库 · API 子库（通过 gatekeeper 调用）
KEYSTORE_GATEKEEPER = "http://43.139.217.141:3910/exec"
KEYSTORE_TOKEN = os.environ.get("GZ_006_GK_TOKEN", "")
KEYSTORE_API_KS = "/opt/zhuyuan/keystore/api/ks.py"

# 第三方 API 端点 · 密钥全在保险库 · 人格体只需知道 provider 名
# 模型 ID 经 2026-07-11 实时验证 ✅ = 已验证通过
ENDPOINTS = {
    # ── 火山方舟 · 即梦生图3.0（用户买的这个）──
    "volcano_jimeng": {
        "url": "https://ark.cn-beijing.volces.com/api/v3/images/generations",
        "key_name": "VOLCANO_JIMENG_API_KEY",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer ",
        "description": "火山即梦生图3.0 · model: doubao-seedream-4-0-250828 ✅ · size≥1920×1920",
    },
    # ── 小云雀 · 图片生成（短剧漫剧配图）──
    "xiaoyunque_image": {
        "url": "https://ark.cn-beijing.volces.com/api/v3/images/generations",
        "key_name": "VOLCANO_JIMENG_API_KEY",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer ",
        "description": "小云雀短剧漫剧图片生成 · model: doubao-seedream-4-0-250828 ✅ · size≥1920×1920",
    },
    # ── 小云雀 · 剧本解析（短剧漫剧文本理解）──
    "xiaoyunque_script": {
        "url": "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
        "key_name": "VOLCANO_JIMENG_API_KEY",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer ",
        "description": "小云雀短剧漫剧剧本解析 · model: doubao-seed-2-1-pro-260628 ✅ / doubao-seed-2-1-turbo-260628 / doubao-seed-2-0-pro-260215",
    },
    # ── 火山方舟 · LLM 对话/文本（通用）──
    "volcano_chat": {
        "url": "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
        "key_name": "VOLCANO_JIMENG_API_KEY",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer ",
        "description": "火山LLM对话 · doubao-seed-2-1-pro ✅ / doubao-seed-2-1-turbo / doubao-seed-2-0-pro / deepseek-v4-pro / qwen3-32b / glm-5-2",
    },
    # ── 火山方舟 · Seedream 4.5 ──
    "volcano_seedream": {
        "url": "https://ark.cn-beijing.volces.com/api/v3/images/generations",
        "key_name": "VOLCANO_JIMENG_API_KEY",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer ",
        "description": "火山Seedream图片生成 · models: doubao-seedream-4-5-251128 ✅ / doubao-seedream-5-0-260128 ✅ / TOKEN_STRING_REDACTED ✅ · size≥1920×1920",
    },
    # ── 火山方舟 · Seedance 视频生成 ──
    "volcano_seedance": {
        "url": "https://ark.cn-beijing.volces.com/api/v3/images/generations",
        "key_name": "VOLCANO_JIMENG_API_KEY",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer ",
        "description": "火山Seedance视频 · TOKEN_STRING_REDACTED / doubao-seedance-2-0-260128 / TOKEN_STRING_REDACTED / TOKEN_STRING_REDACTED",
    },
    # ── 火山方舟 · SeaWeed 视频生成 ──
    "volcano_seaweed": {
        "url": "https://ark.cn-beijing.volces.com/api/v3/images/generations",
        "key_name": "VOLCANO_JIMENG_API_KEY",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer ",
        "description": "火山SeaWeed视频生成（统一视频端点·含Wan2.1）",
    },
    # ── 火山语音复刻 ──
    "volcano_voice": {
        "url": "https://openspeech.bytedance.com/api/v1/tts",
        "key_name": "VOLCANO_VOICE_ACCESS_TOKEN",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer;",
        "description": "火山语音复刻TTS",
    },
    # ── 阿里千问VL · 视觉理解（看图·万相）──
    "aliyun_qwen_vl": {
        "url": "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
        "key_name": "ALIYUN_QWEN_VL_KEY",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer ",
        "description": "阿里千问VL视觉理解 · model: qwen-vl-max",
    },
    # ── 阿里百炼 · 图像生成 ──
    "aliyun_bailian": {
        "url": "https://dashscope.aliyuncs.com/api/v1/services/aigc/image-generation/generation",
        "key_name": "ALIYUN_API_KEY",
        "auth_header": "Authorization",
        "auth_prefix": "Bearer ",
        "description": "阿里百炼图像生成",
    },
}

# 审计日志
AUDIT_DIR = "/opt/zhuyuan/api-proxy/audit"
os.makedirs(AUDIT_DIR, exist_ok=True)

# 内存 session 存储（重启丢失 · 安全设计）
SESSIONS = {}

# ──────────────────────────────────────────
# 工具函数
# ──────────────────────────────────────────

def _audit(persona_id, action, detail=""):
    """写审计日志 · 只追加不可改"""
    ts = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
    entry = {"ts": ts, "persona": persona_id, "action": action, "detail": detail}
    log_file = f"{AUDIT_DIR}/api-proxy-{datetime.now().strftime('%Y%m%d')}.jsonl"
    with open(log_file, "a") as f:
        f.write(json.dumps(entry, ensure_ascii=False) + "\n")

def _call_keystore(api_name):
    """通过 GZ-006 gatekeeper 调 API 子库获取密钥"""
    import subprocess
    # Step 1: challenge
    cmd = f"python3 {KEYSTORE_API_KS} challenge {api_name}"
    payload = json.dumps({"cmd": cmd})
    for attempt in range(2):
        r = subprocess.run([
            "curl", "-s", "--connect-timeout", "10", "-X", "POST",
            KEYSTORE_GATEKEEPER,
            "-H", f"Authorization: Bearer {KEYSTORE_TOKEN}",
            "-H", "Content-Type: application/json",
            "-d", payload
        ], capture_output=True, text=True)
        if r.returncode != 0:
            continue
        try:
            data = json.loads(r.stdout)
            if not data.get("ok"):
                continue
            ch = json.loads(data["stdout"])
            cid, nonce, target = ch["challenge_id"], ch["nonce"], ch["target"]
            break
        except:
            continue
    else:
        raise Exception("keystore challenge failed")

    # Step 2: compute response (本地 · 密码从环境变量取)
    password = os.environ.get("KEYSTORE_PASSWORD", "")
    if not password:
        raise Exception("KEYSTORE_PASSWORD not set")

    salt = os.environ.get("KEYSTORE_API_SALT", "")
    if not salt:
        # 降级：从 GZ-006 远程取 salt
        import subprocess as _sp
        cmd = f"cat {os.path.dirname(KEYSTORE_API_KS)}/salt"
        payload = json.dumps({"cmd": cmd})
        sr = _sp.run([
            "curl", "-s", "--connect-timeout", "10", "-X", "POST",
            KEYSTORE_GATEKEEPER,
            "-H", f"Authorization: Bearer {KEYSTORE_TOKEN}",
            "-H", "Content-Type: application/json",
            "-d", payload
        ], capture_output=True, text=True)
        try:
            sd = json.loads(sr.stdout)
            salt = sd.get("stdout", "").strip()
        except:
            raise Exception("无法获取 keystore salt")

    pw_hash = hmac.new(password.encode(), salt.encode(), hashlib.sha256).hexdigest()
    response = hmac.new(pw_hash.encode(), f"{nonce}:{target}".encode(), hashlib.sha256).hexdigest()
    encrypt_key = hmac.new(password.encode(), b"hololake-keystore-encrypt", hashlib.sha256).hexdigest()

    # Step 3: unlock
    cmd = f"python3 {KEYSTORE_API_KS} unlock {cid} {response} {encrypt_key}"
    payload = json.dumps({"cmd": cmd})
    r = subprocess.run([
        "curl", "-s", "--connect-timeout", "10", "-X", "POST",
        KEYSTORE_GATEKEEPER,
        "-H", f"Authorization: Bearer {KEYSTORE_TOKEN}",
        "-H", "Content-Type: application/json",
        "-d", payload
    ], capture_output=True, text=True)
    data = json.loads(r.stdout)
    if not data.get("ok"):
        raise Exception(f"keystore unlock failed: {data.get('stdout', '')}")
    result = json.loads(data["stdout"])
    if not result.get("ok"):
        raise Exception(f"keystore: {result.get('error', 'unknown')}")
    return result["token"]


# ──────────────────────────────────────────
# Session 管理
# ──────────────────────────────────────────

def _make_session(persona_id):
    """生成临时 session token"""
    sid = _secrets.token_hex(32)
    ts = int(time.time())
    SESSIONS[sid] = {"persona": persona_id, "created": ts, "expires": ts + SESSION_TTL}
    # 清理过期 session
    for k in list(SESSIONS.keys()):
        if SESSIONS[k]["expires"] < ts:
            del SESSIONS[k]
    return sid

def _verify_session(sid):
    """验证 session · 返回 persona_id 或 None"""
    if not sid or sid not in SESSIONS:
        return None
    if SESSIONS[sid]["expires"] < time.time():
        del SESSIONS[sid]
        return None
    return SESSIONS[sid]["persona"]


def require_session(f):
    """装饰器：需要有效 session"""
    @wraps(f)
    def wrapper(*a, **kw):
        sid = request.headers.get("X-Session-Token", "")
        persona = _verify_session(sid)
        if not persona:
            return jsonify({"error": "无效或过期的 session · 请先 POST /auth/session"}), 401
        g.persona_id = persona
        return f(*a, **kw)
    return wrapper


# ──────────────────────────────────────────
# 路由
# ──────────────────────────────────────────

@APP.route("/health", methods=["GET"])
def health():
    return jsonify({"ok": True, "service": "api-proxy-gateway", "version": "1.0.0"})


@APP.route("/auth/session", methods=["POST"])
def auth_session():
    """人格体申请临时 session token"""
    body = request.get_json(silent=True) or {}
    persona_id = body.get("persona_id", "").strip()
    if not persona_id:
        return jsonify({"error": "需要 persona_id（如 ICE-GL-ZY001）"}), 400

    # 简单验证：persona_id 必须以 ICE-GL- 或 TCS- 开头
    if not (persona_id.startswith("ICE-GL-") or persona_id.startswith("TCS-")):
        return jsonify({"error": "无效的 persona_id 格式"}), 400

    sid = _make_session(persona_id)
    _audit(persona_id, "session_created", f"session={sid[:8]}...")
    return jsonify({
        "ok": True,
        "session_token": sid,
        "expires_in": SESSION_TTL,
        "persona_id": persona_id,
    })


@APP.route("/proxy/<provider>", methods=["POST"])
@require_session
def proxy_call(provider):
    """代理调用第三方 API"""
    if provider not in ENDPOINTS:
        return jsonify({"error": f"未知 provider: {provider}", "available": list(ENDPOINTS.keys())}), 404

    cfg = ENDPOINTS[provider]
    body = request.get_json(silent=True) or {}

    # ① 从保险库取密钥（不出现在返回中）
    try:
        api_key = _call_keystore(cfg["key_name"])
    except Exception as e:
        _audit(g.persona_id, "keystore_error", str(e))
        return jsonify({"error": f"密钥获取失败: {str(e)}"}), 502

    # ② 构造请求 + 转发
    import subprocess
    headers = {
        "Content-Type": "application/json",
        cfg["auth_header"]: cfg["auth_prefix"] + api_key,
    }
    # 合并 body 中的额外参数
    proxy_body = {k: v for k, v in body.items() if not k.startswith("_")}

    payload = json.dumps(proxy_body)
    r = subprocess.run([
        "curl", "-s", "--connect-timeout", "30", "--max-time", "120",
        "-X", "POST", cfg["url"],
        "-H", f"Content-Type: application/json",
        "-H", f"{cfg['auth_header']}: {cfg['auth_prefix']}{api_key}",
        "-d", payload,
    ], capture_output=True, text=True)

    # ③ 审计日志
    _audit(g.persona_id, f"proxy_{provider}", f"status={r.returncode} len={len(r.stdout)}")

    # ④ 返回结果（不含密钥）
    try:
        result = json.loads(r.stdout)
    except:
        result = {"raw": r.stdout[:1000], "stderr": r.stderr[:500]}

    return jsonify({"ok": r.returncode == 0, "provider": provider, "result": result})


@APP.route("/providers", methods=["GET"])
@require_session
def list_providers():
    """列出可用的 API 提供商（不含密钥）"""
    providers = {}
    for name, cfg in ENDPOINTS.items():
        providers[name] = {
            "key_name": cfg["key_name"],
            "description": cfg.get("description", ""),
        }
    return jsonify({"providers": providers})


@APP.route("/audit", methods=["GET"])
@require_session
def view_audit():
    """查看最近的审计日志"""
    today = datetime.now().strftime("%Y%m%d")
    log_file = f"{AUDIT_DIR}/api-proxy-{today}.jsonl"
    entries = []
    if os.path.exists(log_file):
        with open(log_file) as f:
            for line in f.readlines()[-50:]:
                try:
                    entries.append(json.loads(line.strip()))
                except:
                    pass
    return jsonify({"date": today, "entries": entries, "count": len(entries)})


# ──────────────────────────────────────────
# 启动
# ──────────────────────────────────────────

if __name__ == "__main__":
    print(f"光湖 API 代理网关 v1.0")
    print(f"监听: 127.0.0.1:{PROXY_PORT}")
    print(f"Providers: {list(ENDPOINTS.keys())}")
    print(f"审计日志: {AUDIT_DIR}")
    APP.run(host="127.0.0.1", port=PROXY_PORT, debug=False)
