#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
creds.py —— palm-kyc-community 凭证管理

存储策略（优先级从高到低）：
  1. 系统 Keychain（macOS Keychain / Linux Secret Service / Windows Credential Manager）
     需要安装：pip install keyring
  2. 降级：~/.palm-kyc-community/credentials.json（明文 JSON，keyring 不可用时使用）

用法：
  python3 creds.py set <key>                 # 交互输入（getpass，不回显，不入 shell history）
  python3 creds.py set <key> --stdin         # 从 stdin 读取（推荐脚本化场景，可配合 here-doc）
  python3 creds.py set <key> --value <val>   # 通过参数传入（⚠️ 会写入 shell history）
  PALM_KYC_COMMUNITY_<KEY>=xxx python3 creds.py set <key>   # 通过环境变量传入（⚠️ 会出现在进程环境）
  python3 creds.py get <key>                 # 读取凭证（stdout 输出，无换行）
  python3 creds.py list                      # 列出已保存的 key
  python3 creds.py clear [key]               # 删除某个 key；省略则清空
  python3 creds.py backend                   # 显示当前使用的存储后端

凭证来源优先级（set 命令）：
  --value 参数  >  --stdin  >  环境变量 PALM_KYC_COMMUNITY_<KEY_UPPER>  >  getpass 交互输入
"""
import argparse
import getpass
import json
import os
import sys

# ── 常量 ──────────────────────────────────────────────────────────────────────
SERVICE_NAME = "palm-kyc-community"
CREDS_FILE = os.path.expanduser("~/.palm-kyc-community/credentials.json")

# ── Keyring 可用性检测 ─────────────────────────────────────────────────────────
def _keyring_available() -> bool:
    """检测 keyring 是否可用（已安装且后端正常）。"""
    try:
        import keyring
        import keyring.errors
        # 尝试一次读操作，确认后端真正可用（部分 Linux 环境安装了库但无守护进程）
        keyring.get_password(SERVICE_NAME, "__probe__")
        return True
    except Exception:
        return False

_USE_KEYRING: bool = _keyring_available()

# ── Keyring 后端 ───────────────────────────────────────────────────────────────
def _kr_get(key: str):
    import keyring
    return keyring.get_password(SERVICE_NAME, key)

def _kr_set(key: str, value: str):
    import keyring
    keyring.set_password(SERVICE_NAME, key, value)

def _kr_delete(key: str):
    import keyring
    import keyring.errors
    try:
        keyring.delete_password(SERVICE_NAME, key)
    except keyring.errors.PasswordDeleteError:
        pass

# ── 文件后端 ───────────────────────────────────────────────────────────────────
def _file_load() -> dict:
    if not os.path.exists(CREDS_FILE):
        return {}
    try:
        with open(CREDS_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}

def _file_save(data: dict):
    os.makedirs(os.path.dirname(CREDS_FILE), exist_ok=True)
    with open(CREDS_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

# ── 统一接口 ───────────────────────────────────────────────────────────────────
def _get(key: str):
    if _USE_KEYRING:
        return _kr_get(key)
    return _file_load().get(key)

def _set(key: str, value: str):
    if _USE_KEYRING:
        _kr_set(key, value)
    else:
        data = _file_load()
        data[key] = value
        _file_save(data)

def _delete(key: str):
    if _USE_KEYRING:
        _kr_delete(key)
    else:
        data = _file_load()
        data.pop(key, None)
        _file_save(data)

def _list_keys() -> list:
    """列出所有已保存的 key（keyring 不支持枚举，降级到文件索引）。"""
    # keyring 无法枚举所有 key，统一用文件维护 key 列表
    return sorted(_file_load().keys()) if not _USE_KEYRING else sorted(_file_load().keys())

def _set_with_index(key: str, value: str):
    """存储凭证，同时维护文件中的 key 索引（keyring 模式下文件只存 key 不存 value）。"""
    _set(key, value)
    if _USE_KEYRING:
        # 文件仅记录 key 名，值为空占位，不存实际凭证
        data = _file_load()
        data[key] = ""
        _file_save(data)

def _delete_with_index(key: str):
    """删除凭证，同时从文件索引中移除。"""
    _delete(key)
    if _USE_KEYRING:
        data = _file_load()
        data.pop(key, None)
        _file_save(data)

def _clear_all():
    """清空所有凭证。"""
    keys = list(_file_load().keys())
    if _USE_KEYRING:
        for k in keys:
            _kr_delete(k)
    _file_save({})

# ── 命令实现 ───────────────────────────────────────────────────────────────────
def cmd_set(key: str, value_arg: str = None, from_stdin: bool = False):
    """保存凭证。输入来源优先级：--value > --stdin > 环境变量 > getpass 交互。"""
    source = None
    if value_arg is not None:
        value = value_arg
        source = "argv"
    elif from_stdin:
        # 读取 stdin 全部内容，strip 末尾换行；允许值本身包含空白
        value = sys.stdin.read()
        # 仅去掉尾部换行，避免误伤值内的前后空格（但两端空白仍 strip，保持与交互一致）
        value = value.strip()
        source = "stdin"
    else:
        env_key = f"PALM_KYC_COMMUNITY_{key.upper()}"
        env_val = os.environ.get(env_key)
        if env_val is not None:
            value = env_val.strip()
            source = f"env:{env_key}"
        else:
            value = getpass.getpass(f"Enter value for '{key}': ").strip()
            source = "tty"

    if not value:
        print("[creds] empty value, abort", file=sys.stderr)
        sys.exit(2)
    _set_with_index(key, value)
    backend = "Keychain" if _USE_KEYRING else "~/.palm-kyc-community/credentials.json"
    print(f"[creds] saved '{key}' → {backend} (source={source})", file=sys.stderr)
    if source == "argv":
        print("[creds] ⚠️  值通过 --value 参数传入，可能已记录到 shell history，"
              "建议执行 `history -d <n>` 清理或改用 --stdin / 环境变量。", file=sys.stderr)

def cmd_get(key: str):
    value = _get(key) if _USE_KEYRING else _file_load().get(key)
    if not value:
        print(f"[creds] '{key}' not found. Run: python3 creds.py set {key}", file=sys.stderr)
        sys.exit(3)
    sys.stdout.write(value)

def cmd_list():
    keys = sorted(_file_load().keys())
    if not keys:
        print("[creds] no credentials configured", file=sys.stderr)
        return
    for k in keys:
        print(k)

def cmd_clear(key):
    if key is None:
        _clear_all()
        print("[creds] cleared all", file=sys.stderr)
    else:
        if not _file_load().get(key) and not (_USE_KEYRING and _kr_get(key)):
            print(f"[creds] '{key}' not found", file=sys.stderr)
            return
        _delete_with_index(key)
        print(f"[creds] cleared '{key}'", file=sys.stderr)

def cmd_backend():
    if _USE_KEYRING:
        try:
            import keyring
            backend = type(keyring.get_keyring()).__name__
        except Exception:
            backend = "keyring（未知后端）"
        print(f"[creds] 存储后端：系统 Keychain（{backend}）")
    else:
        print(f"[creds] 存储后端：本地文件（{CREDS_FILE}）")
        print("[creds] 提示：安装 keyring 可升级到系统 Keychain：pip install keyring")

# ── 入口 ───────────────────────────────────────────────────────────────────────
def main():
    ap = argparse.ArgumentParser(description="palm-kyc-community 凭证管理")
    sub = ap.add_subparsers(dest="cmd", required=True)

    p_set = sub.add_parser("set", help="保存凭证（交互 / --value / --stdin / 环境变量）")
    p_set.add_argument("key")
    g_set = p_set.add_mutually_exclusive_group()
    g_set.add_argument("--value", dest="value", default=None,
                       help="直接传入凭证值（⚠️ 会被 shell history 记录，仅用于临时调试）")
    g_set.add_argument("--stdin", dest="from_stdin", action="store_true",
                       help="从标准输入读取凭证值（推荐，可配合 here-doc 避免泄露）")

    p_get = sub.add_parser("get", help="读取凭证值（stdout）")
    p_get.add_argument("key")

    sub.add_parser("list", help="列出已配置的 key")

    p_clr = sub.add_parser("clear", help="删除凭证；省略 key 则清空全部")
    p_clr.add_argument("key", nargs="?", default=None)

    sub.add_parser("backend", help="显示当前存储后端")

    a = ap.parse_args()
    if a.cmd == "set":
        cmd_set(a.key, value_arg=a.value, from_stdin=a.from_stdin)
    elif a.cmd == "get":
        cmd_get(a.key)
    elif a.cmd == "list":
        cmd_list()
    elif a.cmd == "clear":
        cmd_clear(a.key)
    elif a.cmd == "backend":
        cmd_backend()


if __name__ == "__main__":
    main()
