# -*- coding: utf-8 -*-
"""NetcCheck 官方签名模块 · Python（3.8+，零依赖）

用法：
    from sign_module import sign, verify_rsign

    body = {"appkey": APPKEY, "time": str(int(time.time())), "nonce": uuid.uuid4().hex,
            "username": "tom", "password": "123456"}
    body["sign"] = sign(body, SECRET)
    resp = http_post_json(API + "/user/login", body)
    if not verify_rsign(resp, body["nonce"], SECRET):
        raise SystemExit("响应来源不可信（疑似假服务器），已中止")

设备会话模式（推荐，见接入文档「设备会话」章节）：
    本模块的 sign() / verify_rsign() 对会话签名完全适用——签名流程不变，只换密钥：
      ① 设备注册   body["sign"] = sign(body, APPSECRET)          # /device/enroll，AppSecret 仅此一步
      ② 换取会话   body["sign"] = sign(body, deviceSecret)       # /device/session，含 deviceId 参数
      ③ 会话内业务 body["sign"] = sign(body, sessionKey)；body["sid"] = sessionId
      响应验签的密钥跟随当前签名密钥：会话请求用 verify_rsign(resp, nonce, sessionKey)，
      未携带 sid 的响应用 AppSecret。开启 strict_mode 的软件，AppSecret 泄露也无法伪造业务请求。

约定：
    * sign() 参与 signing 的是除 "sign" 本身外的全部参数——业务参数与公共参数一起；
      开启传输加密后 enc / iv / data 三个字段同样参与；会话请求的 sid 也参与。
    * 参数值请一律以字符串传入（时间戳、enc 等与示例代码保持一致）。
    * 算法细节随平台升级可能调整，请勿依据旧版资料自行实现——以本模块为准。
    * 免费提供，商用授权随套餐附送。获取/更新：控制台「总览 → 接入信息」或联系客服。
"""

import hashlib
import hmac as _hmac
import json as _json


def _canonical(params, secret):
    """签名基准串：除 sign 外的全部参数按参数名升序 k=v 用 & 连接，末尾拼 '&key=secret'。"""
    keys = sorted(k for k in params if k != "sign")
    base = "&".join("{}={}".format(k, params[k]) for k in keys)
    return base + "&key=" + str(secret)


def sign(params, secret):
    """计算请求签名（HMAC-SHA256，返回 hex 小写）。params 为字典，sign 键会被忽略。"""
    message = _canonical(params, secret).encode("utf-8")
    key = str(secret).encode("utf-8")
    return _hmac.new(key, message, hashlib.sha256).hexdigest()


def verify_rsign(resp, nonce, key):
    """验证响应签名（防「改 hosts 假服务器恒返 200」攻击）。

    resp 可为已解析的 dict 或原始 JSON 字符串/字节；nonce 为本次请求使用的 nonce。
    key = 本次请求使用的签名密钥：常规请求传 AppSecret；设备会话请求（带 sid）传 sessionKey。
    返回 True = 响应来自持有对应密钥的真服务器；
    返回 False = 响应不可信，立即中止，不要使用其中的任何数据。
    仅 200 响应需要且必须验签（鉴权未通过的失败响应不带 rsign）。
    """
    if isinstance(resp, (bytes, bytearray)):
        resp = resp.decode("utf-8")
    if isinstance(resp, str):
        resp = _json.loads(resp)
    rsign = resp.get("rsign") or ""
    if not rsign:
        return False
    message = "ncrs|{}|{}|{}".format(nonce, resp.get("code"), resp.get("msg", ""))
    k = str(key).encode("utf-8")
    expect = _hmac.new(k, message.encode("utf-8"), hashlib.sha256).hexdigest()
    return _hmac.compare_digest(expect, str(rsign).lower())


def sign_v2(raw_body, secret):
    """签名 v2（对接简化版，推荐新接入使用）：sign = HMAC-SHA256(secret, 原始请求体字符串)。

    用法：先把业务参数与公共参数（appkey/time/nonce，另加 sver=2）序列化为最终发送的 JSON 字符串，
    对该字符串做本函数签名后一并发送——服务端按收到的原文验签，无需排序拼接：
        raw = json.dumps(body_with_sver2, separators=(',', ':'), ensure_ascii=False)
        body_with_sign = raw[:-1] + ',"sign":"' + sign_v2(raw, SECRET) + '"}'
        # 或更稳妥：先把 sign 占位再整体替换（见 docs 接入文档「签名 v2」）
    约束：请求体必须与签名时的字符串逐字节一致；仅 HMAC-SHA256（无 MD5 路径）。
    密钥选择与 sign() 相同：常规请求 AppSecret；会话请求 deviceSecret / sessionKey。
    """
    key = str(secret).encode("utf-8")
    return _hmac.new(key, str(raw_body).encode("utf-8"), hashlib.sha256).hexdigest()


def derive_enc_key(appsecret_hex, appkey):
    """由 AppSecret 派生传输加密密钥（enc_key 留空的软件，AES 密钥即此值；HKDF-SHA256，与服务器一致）。

    appsecret_hex 为 48 位 hex 的 AppSecret；appkey 形如 'nkxxxxxxxx'。返回 64 位 hex 密钥。
    """
    prk = _hmac.new(b"netccheck-enc-v1", bytes.fromhex(str(appsecret_hex)), hashlib.sha256).digest()
    return _hmac.new(prk, b"nk:" + str(appkey).encode() + b"\x01", hashlib.sha256).hexdigest()
