"""阿里云适配器（含国际版，OpenAPI RPC 风格 + HMAC-SHA1 签名）

API 文档：https://help.aliyun.com/document_detail/25484.html
所需配置：{"access_key_id": "...", "access_key_secret": "...", "region": "cn-hangzhou"}
国际版（alibabacloud）只需将 region 设为海外区域（如 ap-southeast-1），端点自动按区域构造。
说明：此处实现 RPC V1 签名（HMAC-SHA1），可直接调用 ECS OpenAPI；
      生产环境也可替换为官方 alibabacloud-ecs SDK，接口契约保持不变。
"""

import base64
import hashlib
import hmac
import time
import uuid
from urllib.parse import quote

import httpx

from app.adapters.base import BaseAdapter, NormalizedVPS
from app.adapters.registry import register


@register("aliyun-sdk", "alibabacloud-sdk")
class AliyunAdapter(BaseAdapter):
    required_config = ["access_key_id", "access_key_secret"]
    API_VERSION = "2014-05-26"

    def _region(self) -> str:
        return self.config.get("region") or "cn-hangzhou"

    def _endpoint(self) -> str:
        return f"https://ecs.{self._region()}.aliyuncs.com"

    @staticmethod
    def _percent_encode(value) -> str:
        return quote(str(value), safe="~")

    def _sign(self, params: dict, method: str = "GET") -> str:
        canonical = "&".join(
            f"{self._percent_encode(k)}={self._percent_encode(v)}"
            for k, v in sorted(params.items())
        )
        string_to_sign = f"{method}&{self._percent_encode('/')}&{self._percent_encode(canonical)}"
        key = (self.config.get("access_key_secret", "") + "&").encode()
        digest = hmac.new(key, string_to_sign.encode(), hashlib.sha1).digest()
        return base64.b64encode(digest).decode()

    def _call(self, action: str, biz_params: dict = None) -> dict:
        params = {
            "Format": "JSON",
            "Version": self.API_VERSION,
            "AccessKeyId": self.config.get("access_key_id", ""),
            "SignatureMethod": "HMAC-SHA1",
            "Timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
            "SignatureVersion": "1.0",
            "SignatureNonce": str(uuid.uuid4()),
            "Action": action,
            "RegionId": self._region(),
        }
        if biz_params:
            params.update(biz_params)
        params["Signature"] = self._sign(params)
        with httpx.Client(timeout=30) as client:
            resp = client.get(self._endpoint(), params=params)
            resp.raise_for_status()
            data = resp.json()
        if data.get("Code"):
            raise RuntimeError(f"{data.get('Code')}: {data.get('Message')}")
        return data

    def test_connection(self) -> dict:
        try:
            data = self._call("DescribeRegions")
            count = len(data.get("Regions", {}).get("Region", []))
            return {"ok": True, "message": f"连接成功，可用区域 {count} 个"}
        except httpx.HTTPStatusError as e:
            return {"ok": False, "message": f"HTTP {e.response.status_code}：凭证无效"}
        except Exception as e:  # noqa: BLE001
            return {"ok": False, "message": str(e)}

    def list_vps(self) -> list:
        # 分页拉取：单次最多 100 条，按 TotalCount 翻页，避免实例超 100 台时漏同步
        result = []
        page = 1
        while True:
            data = self._call("DescribeInstances", {"PageSize": "100", "PageNumber": str(page)})
            instances = data.get("Instances", {}).get("Instance", [])
            for inst in instances:
                public_ips = inst.get("PublicIpAddress", {}).get("IpAddress", [])
                eip = inst.get("EipAddress", {}).get("IpAddress")
                mem_mb = inst.get("Memory") or 0
                status_map = {"Running": "active", "Stopped": "stopped"}
                result.append(
                    NormalizedVPS(
                        external_id=inst.get("InstanceId"),
                        name=inst.get("InstanceName") or inst.get("InstanceId"),
                        ip_address=eip or (public_ips[0] if public_ips else None),
                        region=inst.get("RegionId"),
                        os=inst.get("OSName"),
                        cpu_cores=inst.get("Cpu"),
                        memory_gb=round(mem_mb / 1024, 1) if mem_mb else None,
                        status=status_map.get(inst.get("Status"), inst.get("Status") or "unknown"),
                        currency="CNY",
                        raw=inst,
                    )
                )
            total = int(data.get("TotalCount") or 0)
            if not instances or page * 100 >= total:
                break
            page += 1
        return result
