#!/usr/bin/env python3
"""
NIO Power 充电站接口调用工具 - 原始数据版本

与 nio_power_api.py 功能相同，但返回未经处理的原始 API 数据。

使用方法:
    api = NIOPowerAPIRaw(authorization="Bearer YOUR_TOKEN")
    result = api.get_chargers("YOUR_STATION_ID")
    # result 是原始 JSON 响应，未经任何转换
"""

import hashlib
import time
import json
import uuid
import os
import urllib.parse
import requests
from typing import Dict, Any, Optional


class NIOPowerAPIRaw:
    """NIO Power API 调用类 - 返回原始数据"""

    # 环境配置
    # secret 为 NIO Power 签名密钥，通过环境变量注入（NIO_API_SECRET_<ENV>），不硬编码在代码中
    ENVIRONMENTS = {
        "production": {
            "host": "https://app.niopower.com",
            "secret": os.environ.get("NIO_API_SECRET_PRODUCTION", ""),
        },
        "staging": {
            "host": "https://app-stg.niopower.com",
            "secret": os.environ.get("NIO_API_SECRET_STAGING", ""),
        },
        # 注意：测试/开发环境为 NIO 内部域名，不随开源公开；如需使用请自行配置
        "test": {
            "host": os.environ.get("NIO_API_HOST_TEST", ""),
            "secret": os.environ.get("NIO_API_SECRET_TEST", ""),
        },
        "dev": {
            "host": os.environ.get("NIO_API_HOST_DEV", ""),
            "secret": os.environ.get("NIO_API_SECRET_DEV", ""),
        },
    }

    APP_ID = "100443"
    APP_VER = "4.16.0"
    LANG = "zh-cn"
    REGION = "cn"

    def __init__(self, env="production", authorization=None, device_id=None):
        """
        初始化

        Args:
            env: 环境名称 (production/staging/test/dev)
            authorization: Bearer token (需要通过登录获取)
            device_id: 设备ID (可选，不传则自动生成)
        """
        self.env = env
        self.config = self.ENVIRONMENTS.get(env, self.ENVIRONMENTS["production"])
        self.authorization = authorization
        self.device_id = device_id or self._generate_device_id()

    def _generate_device_id(self):
        """生成设备ID (32位MD5格式)"""
        return uuid.uuid4().hex

    def _generate_sign(self, path, params, method="GET"):
        """
        生成签名

        签名算法 (已验证):
        1. 拼接: method + path + "?"
        2. 参数按字母排序，用 & 连接
        3. 去掉最后的 &，加上 secret
        4. 如果有 Bearer token，追加到末尾
        5. MD5 加密，转小写十六进制

        Args:
            path: API 路径 (不含 query string)
            params: 参数列表 (格式: ["key=value", ...])
            method: HTTP 方法

        Returns:
            32位小写十六进制签名
        """
        # 1. 拼接前缀
        a = method + path + "?"

        # 2. 参数排序并拼接
        sorted_params = sorted(params)
        for p in sorted_params:
            a += p + "&"

        # 3. 去掉最后的 &，加上 secret
        a = a[:-1] + self.config["secret"]

        # 4. 如果有 Bearer token，追加
        if self.authorization and "Bearer" in self.authorization:
            a += self.authorization

        # 5. MD5
        return hashlib.md5(a.encode()).hexdigest()

    def _build_url(self, path, extra_params=None, method="GET", body=None, headers=None):
        """
        构建完整的请求 URL 和参数

        Args:
            path: API 路径
            extra_params: 额外参数 (dict)
            method: HTTP 方法
            body: POST 请求体
            headers: 请求头

        Returns:
            (完整URL, 签名, 请求参数dict)
        """
        # 基础参数 - 用于签名 (使用原始值，不编码)
        terminal_json = json.dumps({'name': 'Samsung', 'model': 'Samsung'})
        sign_params = [
            f"app_id={self.APP_ID}",
            f"app_ver={self.APP_VER}",
            f"lang={self.LANG}",
            f"region={self.REGION}",
            f"device_id={self.device_id}",
            f"terminal={terminal_json}",  # 签名用原始 JSON
        ]

        # URL 参数 (编码后)
        url_params = [
            f"app_id={self.APP_ID}",
            f"app_ver={self.APP_VER}",
            f"lang={self.LANG}",
            f"region={self.REGION}",
            f"device_id={self.device_id}",
            f"terminal={urllib.parse.quote(terminal_json)}",  # URL 用编码后的
        ]

        # 额外参数
        if extra_params:
            for key, value in extra_params.items():
                if isinstance(value, (dict, list)):
                    value_str = json.dumps(value, ensure_ascii=False)
                else:
                    value_str = str(value)
                sign_params.append(f"{key}={value_str}")
                url_params.append(f"{key}={urllib.parse.quote(value_str)}")

        # 时间戳
        timestamp = str(int(time.time()))
        sign_params.append(f"timestamp={timestamp}")
        url_params.append(f"timestamp={timestamp}")

        # POST 请求体处理
        if method == "POST" and body:
            if headers and headers.get("Content-Type") == "application/json":
                sign_params.append(f"jsonBody={json.dumps(body, ensure_ascii=False)}")
                url_params.append(f"jsonBody={urllib.parse.quote(json.dumps(body, ensure_ascii=False))}")
            else:
                for key, value in body.items():
                    sign_params.append(f"{key}={value}")
                    url_params.append(f"{key}={urllib.parse.quote(str(value))}")

        # 生成签名 (用未编码的参数)
        sign = self._generate_sign(path, sign_params, method)

        # 构建完整 URL (用编码后的参数)
        url_params.append(f"sign={sign}")
        full_url = self.config["host"] + path + "?" + "&".join(url_params)

        return full_url, sign, sign_params

    def request(self, path, method="GET", params=None, body=None, headers=None) -> Dict[str, Any]:
        """
        发送请求

        Args:
            path: API 路径
            method: HTTP 方法
            params: URL 参数
            body: 请求体
            headers: 额外请求头

        Returns:
            原始响应 JSON
        """
        url, sign, _ = self._build_url(path, params, method, body, headers)

        # 请求头
        req_headers = {
            "User-Agent": "Mozilla/5.0 (Linux; Android 12; SM-F926U) AppleWebKit/537.36 Chrome/142.0.0.0 Safari/537.36",
            "Content-Type": "application/x-www-form-urlencoded",
            "charset": "utf-8",
        }

        if self.authorization:
            req_headers["authorization"] = self.authorization

        if headers:
            req_headers.update(headers)

        # 发送请求（添加超时控制）
        timeout = 30
        try:
            if method == "GET":
                response = requests.get(url, headers=req_headers, timeout=timeout)
            else:
                response = requests.post(url, headers=req_headers, json=body, timeout=timeout)

            # 解析 JSON 响应
            try:
                return response.json()
            except json.JSONDecodeError:
                return {"result_code": "error", "message": "响应解析失败", "status_code": response.status_code}

        except requests.exceptions.Timeout:
            return {"result_code": "error", "message": f"请求超时（{timeout}秒）"}
        except requests.exceptions.ConnectionError:
            return {"result_code": "error", "message": "网络连接失败"}
        except requests.exceptions.RequestException as e:
            return {"result_code": "error", "message": f"请求异常: {str(e)}"}

    # ==================== 登录相关接口 ====================

    def _get_nonce(self):
        """生成随机 nonce (15位随机字符串)"""
        import random
        import string
        chars = string.ascii_lowercase + string.digits
        return ''.join(random.choice(chars) for _ in range(15))

    def _login_request(self, path, body, headers=None, need_auth=False):
        """
        构造登录/刷新类请求（复刻微信小程序请求格式）

        与普通业务请求的差异（从 NIO Power 小程序逆向得到，登录接口传 ignoreDeviceParams=true）:
        - URL 只放固定参数 app_id/app_ver/lang/region + timestamp + sign，不含任何 body 参数
        - sign 放在 URL query 中，而非 body
        - 签名参数 = 固定参数 + timestamp + body 参数（body 参数参与签名，但只出现在请求体中）
        """
        timestamp = str(int(time.time()))

        # 签名参数（登录接口 URL 不含 device_id/terminal，但它们作为 body 参数参与签名）
        sign_params = [
            f"app_id={self.APP_ID}",
            f"app_ver={self.APP_VER}",
            f"lang={self.LANG}",
            f"region={self.REGION}",
            f"timestamp={timestamp}",
        ]
        for k, v in body.items():
            sign_params.append(f"{k}={v}")

        # 生成签名（排序在 _generate_sign 内部完成）
        sign = self._generate_sign(path, sign_params, "POST")

        # 完整 URL: 固定参数 + sign（body 参数不进 URL，避免未编码 JSON 破坏签名）
        url_params = sign_params[:5] + [f"sign={sign}"]
        full_url = self.config["host"] + path + "?" + "&".join(url_params)

        req_headers = {
            "Content-Type": "application/x-www-form-urlencoded",
            "User-Agent": "Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36",
            "charset": "utf-8",
        }
        if need_auth and self.authorization:
            req_headers["authorization"] = self.authorization
        if headers:
            req_headers.update(headers)

        response = requests.post(full_url, data=body, headers=req_headers, timeout=30)
        return response.json()

    def send_verification_code(self, mobile, country_code="86"):
        """
        发送验证码

        Args:
            mobile: 手机号
            country_code: 国家代码，默认 86

        Returns:
            原始 API 响应
        """
        nonce = self._get_nonce()
        terminal = json.dumps({'name': 'Samsung', 'model': 'Samsung'})

        body = {
            "classifier": "login",
            "country_code": country_code,
            "device_id": self.device_id,
            "mobile": mobile,
            "nonce": nonce,
            "terminal": terminal
        }

        return self._login_request(
            "/pe/app/account/v1/send-verification-code",
            body
        )

    def login(self, mobile, verification_code, country_code="86", channel="default"):
        """
        手机号验证码登录

        Args:
            mobile: 手机号
            verification_code: 验证码
            country_code: 国家代码
            channel: 渠道

        Returns:
            原始登录响应，包含 access_token 和 refresh_token
        """
        nonce = self._get_nonce()
        terminal = json.dumps({'name': 'Samsung', 'model': 'Samsung'})

        body = {
            "classifier": "login",
            "client": "mini_program",
            "channel": channel,
            "country_code": country_code,
            "device_id": self.device_id,
            "mobile": mobile,
            "nonce": nonce,
            "terminal": terminal,
            "verification_code": verification_code
        }

        result = self._login_request("/pe/app/account/v1/auto-login", body)

        # 登录成功后自动更新 token
        if result.get("result_code") == "success" and result.get("data"):
            self.authorization = f"Bearer {result['data'].get('access_token', '')}"

        return result

    def refresh_token(self, refresh_token):
        """
        刷新 access_token

        Args:
            refresh_token: 刷新令牌

        Returns:
            新的 access_token 和 refresh_token
        """
        body = {
            "refresh_token": refresh_token,
            "device_id": self.device_id,
            "terminal": json.dumps({'name': 'Samsung', 'model': 'Samsung'})
        }

        result = self._login_request(
            "/pe/app/account/v1/refresh-access",
            body,
            need_auth=True
        )

        # 刷新成功后自动更新 token
        if result.get("result_code") == "success" and result.get("data"):
            self.authorization = f"Bearer {result['data'].get('access_token', '')}"

        return result

    # ==================== 业务接口 ====================

    def get_chargers(self, group_id) -> Dict[str, Any]:
        """
        获取充电站详情 (原始 JSON)

        Args:
            group_id: 充电站ID

        Returns:
            原始 API 响应 JSON，未经任何处理
        """
        return self.request(
            "/pe/app/charge-station/v1/resource/connectors",
            params={"group_id": group_id}
        )


# ==================== 使用示例 ====================

if __name__ == "__main__":
    print("NIO Power API 调用工具 - 原始数据版本")
    print("=" * 50)

    # 使用现有 token (从抓包获取)
    API = NIOPowerAPIRaw(
        env="production",
        authorization="Bearer YOUR_TOKEN_HERE",
        device_id="YOUR_DEVICE_ID_HERE"
    )

    # 获取充电站信息 (原始数据)
    result = API.get_chargers(os.environ.get("STATION_ID", ""))

    # 打印原始 JSON
    print(json.dumps(result, ensure_ascii=False, indent=2))
