"""AliyunSmsProvider —— 调阿里云短信服务（dysmsapi）。

复用 vault `aliyun.power-app` platform（2026-04-23 简化：AK/SK + 签名 + 模板都在一处）:
    access_key_id / access_key_secret  — 短信服务 AK
    sign_name                          — "杭州阿空智能科技"（已审核通过）
    template_code                      — "SMS_317151039"（已审核通过）

实现走阿里云 RPC 签名协议（GET + HMAC-SHA1），避开阿里云巨型 SDK 依赖。

参考：https://help.aliyun.com/document_detail/101343.html
"""

from __future__ import annotations

import base64
import datetime as _dt
import hashlib
import hmac
import json
import logging
import urllib.parse
import uuid

import httpx

from .. import credentials as creds
from .base import SmsProvider

log = logging.getLogger("xiangqin.sms.aliyun")

ENDPOINT = "https://dysmsapi.aliyuncs.com/"
SMS_API_VERSION = "2017-05-25"

# 阿里云侧公开元数据（非秘密，审核通过后固定不变）
SIGN_NAME = "杭州阿空智能科技"
TEMPLATE_CODE = "SMS_317151039"


class AliyunSmsProvider(SmsProvider):
    name = "aliyun"

    def __init__(self) -> None:
        c = creds.get("aliyun_sms")
        self._ak = c["access_key_id"]
        self._sk = c["access_key_secret"]
        self._sign_name = SIGN_NAME
        self._template_code = TEMPLATE_CODE

    def send_code(self, phone: str, code: str) -> None:
        params = {
            # 公共参数
            "Format": "JSON",
            "Version": SMS_API_VERSION,
            "AccessKeyId": self._ak,
            "SignatureMethod": "HMAC-SHA1",
            "SignatureVersion": "1.0",
            "SignatureNonce": str(uuid.uuid4()),
            "Timestamp": _dt.datetime.now(_dt.UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
            "Action": "SendSms",
            # 业务参数
            "PhoneNumbers": phone,
            "SignName": self._sign_name,
            "TemplateCode": self._template_code,
            "TemplateParam": json.dumps({"code": code}),
        }
        params["Signature"] = _rpc_sign(params, self._sk)

        masked = f"{phone[:3]}****{phone[-4:]}"
        try:
            r = httpx.get(ENDPOINT, params=params, timeout=10.0)
            r.raise_for_status()
            resp = r.json()
        except httpx.HTTPError as e:
            raise RuntimeError(f"aliyun-sms 请求失败（{masked}）：{e}") from e

        if resp.get("Code") != "OK":
            raise RuntimeError(
                f"aliyun-sms 业务错误（{masked}）：Code={resp.get('Code')} "
                f"Message={resp.get('Message')}"
            )
        log.info("[aliyun] 已发验证码到 %s (BizId=%s)", masked, resp.get("BizId"))


def _rpc_sign(params: dict[str, str], access_key_secret: str) -> str:
    """按阿里云 RPC 签名规范生成 Signature 字段。

    步骤:
    1. params 按 key 字典序排序
    2. 对 key 和 value 都做 RFC 3986 URL encode
    3. 拼 canonical query string
    4. stringToSign = "GET" + "&" + urlencode("/") + "&" + urlencode(canonical)
    5. Signature = Base64(HMAC-SHA1(AK_Secret + "&", stringToSign))
    """
    # 排除 Signature 本身（未签时不存在，但防御）
    sorted_items = sorted((k, v) for k, v in params.items() if k != "Signature")
    canonical = "&".join(f"{_rfc3986_quote(k)}={_rfc3986_quote(v)}" for k, v in sorted_items)
    string_to_sign = f"GET&{_rfc3986_quote('/')}&{_rfc3986_quote(canonical)}"
    key = (access_key_secret + "&").encode()
    digest = hmac.new(key, string_to_sign.encode(), hashlib.sha1).digest()
    return base64.b64encode(digest).decode()


def _rfc3986_quote(s: str) -> str:
    return urllib.parse.quote(s, safe="~")
