#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
方传智付 开放 API —— Python 接入示例（零依赖，仅用标准库）
---------------------------------------------------------------------------
用法：
    export FC_APP_ID=fcxxxxxxxx FC_APP_SECRET=sk_xxxx FC_HOST=http://localhost:3000
    python3 sdk-python.py

签名规则与服务端 utils/sign.js 完全一致：
  1. 业务参数（query + body 顶层标量）+ 公共参数 app_id / timestamp / nonce；
  2. 追加 method（大写）、path（完整路径，含 /openapi/v1）；
  3. 剔除 sign、空值、对象/数组；
  4. 按参数名 ASCII 升序拼接 k=v（值不做 URL 编码）；
  5. sign = HMAC-SHA256(canonical, app_secret).hexdigest() 小写。
"""

import hashlib
import hmac
import json
import os
import time
import urllib.request
import urllib.parse
import urllib.error

CONFIG = {
    "host": os.environ.get("FC_HOST", "http://localhost:3000"),
    "app_id": os.environ.get("FC_APP_ID", "YOUR_APP_ID"),
    "app_secret": os.environ.get("FC_APP_SECRET", "YOUR_APP_SECRET"),
}


def build_canonical(params, method, path):
    """构造待签名串（与服务端一致）"""
    merged = {}
    for k, v in (params or {}).items():
        if k == "sign":
            continue
        if v is None or v == "":
            continue
        if not isinstance(v, (str, int, float, bool)):
            continue
        merged[k] = str(v)
    merged["method"] = str(method or "GET").upper()
    merged["path"] = str(path or "")
    return "&".join("{}={}".format(k, merged[k]) for k in sorted(merged))


def sign(params, method, path, secret):
    canonical = build_canonical(params, method, path)
    return hmac.new(secret.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256).hexdigest()


def request(method, path, query=None, body=None):
    """
    发起一次 API 调用
    :param method: HTTP 方法
    :param path:   完整路径，例如 /openapi/v1/balance
    :param query:  查询参数字典
    :param body:   JSON 请求体（POST 用）
    """
    q = query or {}
    b = body or {}
    timestamp = str(int(time.time()))
    import os as _os
    nonce = _os.urandom(12).hex()

    # 服务端会把 query+body+公共参数一起参与签名，因此这里也要并入
    params = {**q, **b, "app_id": CONFIG["app_id"], "timestamp": timestamp, "nonce": nonce}
    sig = sign(params, method, path, CONFIG["app_secret"])

    url = CONFIG["host"].rstrip("/") + path
    if q:
        url += "?" + urllib.parse.urlencode(q)
    data = json.dumps(b).encode("utf-8") if b else None

    req = urllib.request.Request(url, data=data, method=method)
    req.add_header("X-App-Id", CONFIG["app_id"])
    req.add_header("X-Timestamp", timestamp)
    req.add_header("X-Nonce", nonce)
    req.add_header("X-Sign", sig)
    req.add_header("Content-Type", "application/json")

    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            raw = resp.read().decode("utf-8")
            return {"status": resp.status, "body": json.loads(raw) if raw else None}
    except urllib.error.HTTPError as e:
        raw = e.read().decode("utf-8")
        return {"status": e.code, "body": json.loads(raw) if raw else None}


def main():
    print("== ping ==")
    print(request("GET", "/openapi/v1/ping"))

    print("== 查询余额（按手机号）==")
    print(request("GET", "/openapi/v1/balance", {"phone": "13800000001"}))

    print("== 代付（扣款）==")
    print(request("POST", "/openapi/v1/pay", None, {
        "phone": "13800000001",
        "amount": "10.00",
        "out_trade_no": "ORDER_" + str(int(time.time())),
        "remark": "SDK 示例代付",
    }))

    print("== 入账（充值）==")
    print(request("POST", "/openapi/v1/recharge", None, {
        "phone": "13800000001",
        "amount_fen": 5000,
        "out_trade_no": "RECH_" + str(int(time.time())),
        "remark": "SDK 示例入账",
    }))


if __name__ == "__main__":
    main()
