"""
platform.ciyuanfun.cn Python SDK
=================================

双鉴权模式：Bearer Token（MAM API）+ HMAC-SHA256（开放平台 API）

基础用法:
    from ciyuan_open import Client

    # Bearer Token 模式（MAM /api/v1/org/*）
    client = Client(token="your-bearer-token")

    # HMAC-SHA256 模式（开放平台 /open/v1/*）
    client = Client(app_id="app_xxx", app_secret="xxx")

    # 健康检查
    print(client.health())
"""

import hashlib
import hmac
import json
import time
import uuid
from typing import Optional, Dict, Any
from urllib.request import Request, urlopen
from urllib.error import HTTPError


class Client:
    """Ciyuan 平台 Python SDK，支持 Bearer Token 和 HMAC-SHA256 双鉴权"""

    BASE_URL = "https://platform.ciyuanfun.cn"

    def __init__(
        self,
        token: Optional[str] = None,
        app_id: Optional[str] = None,
        app_secret: Optional[str] = None,
        base_url: Optional[str] = None,
    ):
        self.token = token
        self.app_id = app_id
        self.app_secret = app_secret
        if base_url:
            self.BASE_URL = base_url

    # ── 内部方法 ──────────────────────────────────────────

    def _hmac_headers(self, method: str, path: str, body: str = "") -> Dict[str, str]:
        """生成 HMAC-SHA256 签名头（开放平台）"""
        timestamp = str(int(time.time()))
        nonce = uuid.uuid4().hex[:16]
        sign_str = f"{method}\n{path}\n{body}\n{timestamp}\n{nonce}\n{self.app_secret}"
        sign = hmac.new(
            self.app_secret.encode(), sign_str.encode(), hashlib.sha256
        ).hexdigest()
        return {
            "X-App-ID": self.app_id,
            "X-Timestamp": timestamp,
            "X-Nonce": nonce,
            "X-Sign": sign,
        }

    def _request(self, method: str, path: str, body: Optional[dict] = None) -> dict:
        body_str = json.dumps(body, ensure_ascii=False) if body else ""

        # 自动选择鉴权方式
        if self.token:
            # Bearer Token（MAM API）
            headers = {
                "Authorization": f"Bearer {self.token}",
                "Content-Type": "application/json",
            }
        elif self.app_id and self.app_secret:
            # HMAC-SHA256（开放平台 API）
            headers = self._hmac_headers(method, path, body_str)
            headers["Content-Type"] = "application/json"
        else:
            raise ValueError("需要提供 token(Bearer) 或 app_id+app_secret(HMAC)")

        url = f"{self.BASE_URL}/{path.lstrip('/')}"
        data = body_str.encode("utf-8") if body_str else None
        req = Request(url, data=data, headers=headers, method=method)

        try:
            with urlopen(req, timeout=30) as resp:
                return json.loads(resp.read().decode("utf-8"))
        except HTTPError as e:
            return json.loads(e.read().decode("utf-8"))

    def _get(self, path: str) -> dict:
        return self._request("GET", path)

    def _post(self, path: str, body: Optional[dict] = None) -> dict:
        return self._request("POST", path, body)

    def _put(self, path: str, body: Optional[dict] = None) -> dict:
        return self._request("PUT", path, body)

    def _delete(self, path: str) -> dict:
        return self._request("DELETE", path)

    # ── 通用 ─────────────────────────────────────────────

    def health(self) -> dict:
        return self._get("/health")

    # ── 认证 (MAM / Bearer) ──────────────────────────────

    def register(self, username: str, password: str, display_name: str = "") -> dict:
        return self._post("/api/v1/auth/register", {
            "username": username, "password": password, "display_name": display_name,
        })

    def login(self, username: str, password: str) -> dict:
        return self._post("/api/v1/auth/login", {
            "username": username, "password": password,
        })

    def profile(self) -> dict:
        return self._get("/api/v1/users/profile")

    # ── 故事 & 分镜 (MAM / Bearer) ─────────────────────

    def list_stories(self) -> dict:
        return self._get("/api/v1/org/stories")

    def get_story(self, story_id: int) -> dict:
        return self._get(f"/api/v1/org/stories/{story_id}")

    def create_story(self, title: str, script: str) -> dict:
        return self._post("/api/v1/org/stories", {
            "title": title, "script": script,
        })

    def generate_story_video(self, story_id: int, duration: str = "auto",
                             ratio: str = "16:9", resolution: str = "720p") -> dict:
        return self._get(
            f"/api/v1/org/stories/{story_id}/video"
            f"?duration={duration}&ratio={ratio}&resolution={resolution}"
        )

    def video_status(self, story_id: int) -> dict:
        return self._get(f"/api/v1/org/stories/{story_id}/video/status")

    def concat_story_video(self, story_id: int, panel_nos: Optional[list] = None) -> dict:
        """分镜拼接：本地 ffmpeg 合并各分镜视频为成片（不消耗 Seedance token）。
        panel_nos: 可选，指定拼接顺序，如 [1,2,3,4]；缺省按分镜序号升序。
        """
        path = f"/api/v1/org/stories/{story_id}/video/concat"
        if panel_nos:
            path += "?panel_nos=" + ",".join(str(p) for p in panel_nos)
        return self._get(path)

    # ── 角色 (MAM / Bearer) ────────────────────────────

    def list_characters(self) -> dict:
        return self._get("/api/v1/org/characters")

    def create_character(self, name: str, **kwargs) -> dict:
        body = {"name": name, **kwargs}
        return self._post("/api/v1/org/characters", body)

    # ── 音色 & TTS (MAM / Bearer) ─────────────────────

    def list_voices(self) -> dict:
        return self._get("/api/v1/org/voices")

    def tts_synthesize(self, text: str, voice_id: str) -> dict:
        return self._post("/api/v1/org/tts/synthesize", {
            "text": text, "voice_id": voice_id,
        })

    def clone_voice(self, name: str, audio_path: str) -> dict:
        """上传文件克隆声音（使用 multipart/form-data，需自行处理）"""
        # 此接口需要 multipart 上传，本 SDK 暂不内置
        # 请使用 curl: curl -F "audio=@file.wav" -F "name=xxx"
        #   https://platform.ciyuanfun.cn/api/v1/org/voices/clone
        raise NotImplementedError(
            "请使用 curl 上传音频克隆：curl -F 'audio=@file.wav' -F 'name=xxx' "
            "-H 'Authorization: Bearer <token>' "
            "https://platform.ciyuanfun.cn/api/v1/org/voices/clone"
        )

    # ── 视频 (MAM / Bearer) ────────────────────────────

    def list_videos(self) -> dict:
        return self._get("/api/v1/org/videos")

    def create_video(self, prompt: str, duration: int = 5,
                     ratio: str = "16:9", resolution: str = "720p") -> dict:
        return self._post("/api/v1/org/videos", {
            "prompt": prompt, "duration": duration,
            "ratio": ratio, "resolution": resolution,
        })

    # ── 套餐 (MAM / Bearer) ────────────────────────────

    def list_plans(self) -> dict:
        return self._get("/api/v1/org/packages/plans")

    def my_packages(self) -> dict:
        return self._get("/api/v1/org/packages")

    # ── 钱包 & 积分充值 (MAM / Bearer) ──────────────────

    def recharge_plans(self) -> dict:
        """充值套餐列表与人民币:积分汇率"""
        return self._get("/api/v1/wallet/recharge/plans")

    def recharge_create(self, plan_id: Optional[int] = None,
                        amount_fen: Optional[int] = None) -> dict:
        """创建微信支付充值订单（plan_id 或 amount_fen 二选一）"""
        body = {}
        if plan_id is not None:
            body["plan_id"] = plan_id
        if amount_fen is not None:
            body["amount_fen"] = amount_fen
        return self._post("/api/v1/wallet/recharge", body)

    def recharge_status(self, out_trade_no: str) -> dict:
        """查询充值订单支付状态"""
        return self._get(f"/api/v1/wallet/recharge/status?out_trade_no={out_trade_no}")

    def wallet_balance(self) -> dict:
        """查询当前用户积分余额"""
        return self._get("/api/v1/wallet/balance")

    def account_income(self) -> dict:
        """积分收入明细（充值/平台转入/其他转入）"""
        return self._get("/api/v1/wallet/account/income")

    def px_balance(self) -> dict:
        """查询当前用户 PX 积分余额（账本口径，与 PX 平台一致）"""
        return self._get("/api/v1/wallet/px/balance")

    def px_exchange(self, direction: str, amount: int,
                    target_user_id: int = 0, note: str = "") -> dict:
        """PX 双向兑换：direction=mam_to_px|px_to_mam，amount=源数量；
        跨用户传 target_user_id（缺省 0=自己）；px_to_mam 消费无手续费，
        mam_to_px 退款 1% 手续费进资金池"""
        body = {"direction": direction, "amount": amount}
        if target_user_id:
            body["target_user_id"] = target_user_id
        if note:
            body["note"] = note
        return self._post("/api/v1/wallet/px/exchange", body)

    def admin_login(self, username: str, password: str, base: str = "https://admin.ciyuanfun.cn") -> dict:
        """管理端登录（超管/渠道商）→ 返回 role/token；后续 admin_* 调用需在 headers 带 Authorization: Bearer <token> 或 X-Mam-Admin-Key"""
        return self._post("/admin/login", {"username": username, "password": password})

    def admin_reseller_stats(self) -> dict:
        """渠道商自助-统计（需 admin token）"""
        return self._get("/admin/reseller/me/stats")

    def admin_reseller_credits(self, user_id: int, amount: int, description: str = "") -> dict:
        """渠道商自助-给名下用户补贴积分（校验归属+余额）"""
        body = {"user_id": user_id, "amount": amount}
        if description:
            body["description"] = description
        return self._post("/admin/reseller/me/credits", body)

    def _raw_get(self, url: str, headers: Optional[dict] = None) -> dict:
        """低层 GET：支持自定义域名与自定义头（跨平台调用，如 MaM/PX）"""
        req = Request(url, headers=headers or {})
        try:
            with urlopen(req, timeout=30) as resp:
                return json.loads(resp.read().decode("utf-8"))
        except HTTPError as e:
            return json.loads(e.read().decode("utf-8"))

    def _raw_post(self, url: str, body: dict, headers: Optional[dict] = None) -> dict:
        """低层 POST：支持自定义域名与自定义头（跨平台调用，如 PX 兑换）"""
        data = json.dumps(body, ensure_ascii=False).encode("utf-8")
        req = Request(url, data=data,
                     headers={"Content-Type": "application/json", **(headers or {})},
                     method="POST")
        try:
            with urlopen(req, timeout=30) as resp:
                return json.loads(resp.read().decode("utf-8"))
        except HTTPError as e:
            try:
                return json.loads(e.read().decode("utf-8"))
            except Exception:
                return {"success": False, "error": str(e.reason)}

    def px_exchange_total(self, user_id: int, mam_key: str = "",
                          base: str = "https://mam.ciyuanfun.cn") -> dict:
        """MaM 端统计用户 PX 兑换获得积分累计（2026-08-25 新增）

        只统计 PX 兑换入账（ref_type=px_exchange 或描述含"PX兑换"），排除现金充值。
        鉴权头 X-Mam-Admin-Key。返回 {"success":true,"user_id":5,"px_exchange_total":2}
        """
        return self._raw_get(
            f"{base}/api/v1/admin/users/{user_id}/px-exchange-total",
            {"X-Mam-Admin-Key": mam_key},
        )

    def px_consume_progress(self, username: str,
                            base: str = "https://px-sh.tokenroute.net") -> dict:
        """PX 消费进度（2026-08-26 改造）：综合 MaM 兑换 + gateway 托管积分

        返回 {"success":true,"username":"王五","partners":[{"partnerName","partnerId","creditUnit","rate","usedCredits"|"totalCredits","usedCredits"}]}
        """
        return self._raw_get(f"{base}/api/gateway/credits/{username}")

    def px_redeem(self, partner_id: int, amount: int,
                  direction: str = "consume", scenario_id: str = "",
                  target_username: str = "", username: str = "",
                  base: str = "https://px-sh.tokenroute.net") -> dict:
        """PX 统一兑换（2026-08-26 三模式）

        三模式按 partner 能力路由：api=伙伴端真实入账；gateway=购买积分包（需 scenario_id）；
        none=暂未接入。direction=consume(兑换/购买) | refund(退款, 仅 api, 退自己 99% 到账)。
        示例买 DeepSeek 包：px_redeem(1, 1, scenario_id="ds_free", username="王五")
        """
        body = {"username": username, "partnerId": partner_id,
                "amount": amount, "direction": direction}
        if scenario_id:
            body["scenarioId"] = scenario_id
        if target_username:
            body["targetUsername"] = target_username
        return self._raw_post(f"{base}/api/exchange/redeem", body)

    # ── 开放平台 (HMAC-SHA256) ─────────────────────────

    def user_credits(self) -> dict:
        return self._get("/open/v1/user/credits")

    def image_generate(self, **params) -> dict:
        return self._post("/open/v1/image/generate", params)

    def image_task(self, task_id: str) -> dict:
        return self._get(f"/open/v1/image/task/{task_id}")

    def video_generate_open(self, **params) -> dict:
        return self._post("/open/v1/video/generate", params)

    def list_works(self) -> dict:
        return self._get("/open/v1/works")

    def get_work(self, work_id: str) -> dict:
        return self._get(f"/open/v1/works/{work_id}")

    def asset_certify(self, content: str, creator: str = "",
                      content_type: str = "digital_work") -> dict:
        return self._post("/open/v1/asset/certify", {
            "content": content, "creator": creator, "type": content_type,
        })

    def asset_verify(self, cert_id: str) -> dict:
        return self._get(f"/open/v1/asset/verify/{cert_id}")


# ── 快速测试 ──────────────────────────────────────────────
if __name__ == "__main__":
    import os

    # Bearer Token 模式（MAM）
    token = os.environ.get("CY_TOKEN")
    if token:
        c = Client(token=token)
        print("=== MAM API ===")
        print("stories:", c.list_stories())
        print("characters:", c.list_characters())
        print("voices:", c.list_voices())

    # HMAC 模式（开放平台）
    app_id = os.environ.get("CY_APP_ID", "app_test001")
    secret = os.environ.get("CY_APP_SECRET", "your-secret")
    c2 = Client(app_id=app_id, app_secret=secret)
    print("=== Open API ===")
    print("health:", c2.health())
    print("credits:", c2.user_credits())
