# -*- coding: utf-8 -*-
# 账号模式 AppEn 客户端：公告、登录、注册、解绑、充值、找回/修改密码、意见反馈、控制台相关接口。

from __future__ import annotations

import os
import sys
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Optional
from urllib.parse import quote, unquote

import requests

from bsphp_crypto import (
    aes128_cbc_encrypt_base64,
    decrypt_response_body,
    md5_hex,
    rsa_encrypt_pkcs1_base64,
)
from bsphp_encode import encode_parameter


def _coerce_code(v: Any) -> Optional[int]:
    if v is None or isinstance(v, bool):
        return None
    if isinstance(v, int):
        return v
    if isinstance(v, float):
        return int(v)
    try:
        return int(v)
    except (TypeError, ValueError):
        return None


@dataclass
class ClientConfig:
    url: str
    mutual_key: str
    server_private_key: str
    client_public_key: str
    code_url_prefix: str = ""


@dataclass
class APIResult:
    data: Any
    code: Optional[int]

    def message(self) -> str:
        if isinstance(self.data, str):
            return self.data
        if isinstance(self.data, list):
            return "\n".join(str(x) for x in self.data)
        return ""


class InitFailedError(Exception):
    """初始化失败；debug_log 与 Mac 控制台 [BSPHP] 输出对齐，便于排查密钥/网络。"""

    def __init__(self, message: str, *, debug_log: str = "") -> None:
        super().__init__(message)
        self.debug_log = debug_log

    def __str__(self) -> str:
        if self.debug_log:
            return f"{self.args[0]}\n\n--- [BSPHP] 调试详情 ---\n{self.debug_log}"
        return str(self.args[0])


CODE_TYPE_LOGIN = "INGES_LOGIN"
CODE_TYPE_REG = "INGES_RE"
CODE_TYPE_BACK_PWD = "INGES_MACK"
CODE_TYPE_SAY = "INGES_SAY"

USER_INFO_FIELDS = [
    ("UserName", "用户名称"),
    ("UserUID", "用户UID"),
    ("UserReDate", "激活时间"),
    ("UserReIp", "激活时Ip"),
    ("UserIsLock", "用户状态"),
    ("UserLogInDate", "登录时间"),
    ("UserLogInIp", "登录Ip"),
    ("UserVipDate", "到期时"),
    ("UserKey", "绑定特征"),
    ("Class_Nane", "用户分组名称"),
    ("Class_Mark", "用户分组别名"),
    ("UserQQ", "用户QQ"),
    ("UserMAIL", "用户邮箱"),
    ("UserPayZhe", "购卡折扣"),
    ("UserTreasury", "是否代理"),
    ("UserMobile", "电话"),
    ("UserRMB", "帐号金额"),
    ("UserPoint", "帐号积分"),
    ("Usermibao_wenti", "密保问题"),
    ("UserVipWhether", "vip是否到期"),
    ("UserVipDateSurplus_DAY", "到期倒计时-天"),
    ("UserVipDateSurplus_H", "到期倒计时-时"),
    ("UserVipDateSurplus_I", "到期倒计时-分"),
    ("UserVipDateSurplus_S", "到期倒计时-秒"),
]


def _bsphp_truncate(s: str, max_len: int = 300) -> str:
    s = s.replace("\r", " ").replace("\n", " ")
    if len(s) <= max_len:
        return s
    return s[:max_len] + "..."


class BSPHPClient:
    def __init__(self, cfg: ClientConfig, machine_code_fn):
        self._cfg = cfg
        self._machine_code_fn = machine_code_fn
        self.bs_php_sessl = ""
        self.last_send_debug = ""
        self._session = requests.Session()
        self._session.headers.update(
            {"Content-Type": "application/x-www-form-urlencoded"}
        )

    def _bsphp_debug_enabled(self) -> bool:
        return os.environ.get("BSPHP_DEBUG", "1") != "0"

    def _bsphp_emit(self, lines: list[str], msg: str) -> None:
        lines.append(msg)
        if self._bsphp_debug_enabled():
            print(f"[BSPHP] {msg}", file=sys.stderr)

    @property
    def machine_code(self) -> str:
        return self._machine_code_fn()

    @property
    def code_image_url(self) -> str:
        if not self._cfg.code_url_prefix:
            return ""
        return self._cfg.code_url_prefix + self.bs_php_sessl

    def _now_parts(self) -> tuple[str, str, str]:
        now = datetime.now()
        appsafecode = md5_hex(now.strftime("%Y-%m-%d %H:%M:%S"))
        date_hash = now.strftime("%Y-%m-%d#%H:%M:%S")
        return appsafecode, date_hash, appsafecode

    def send(self, api: str, params: Optional[dict[str, str]] = None) -> Optional[dict]:
        params = params or {}
        lines: list[str] = []
        appsafecode, date_hash, _ = self._now_parts()
        param: dict[str, str] = {
            "api": api,
            "BSphpSeSsL": self.bs_php_sessl,
            "date": date_hash,
            "md5": "",
            "mutualkey": self._cfg.mutual_key,
            "appsafecode": appsafecode,
        }
        param.update(params)
        pairs = [f"{k}={encode_parameter(v)}" for k, v in sorted(param.items())]
        data_str = "&".join(pairs)
        self._bsphp_emit(lines, "========== 加密前 ==========")
        self._bsphp_emit(lines, f"api: {api}")
        self._bsphp_emit(lines, f"request_data: {_bsphp_truncate(data_str, 2000)}")
        aes_key_full = md5_hex(self._cfg.server_private_key + appsafecode)
        aes_key = aes_key_full[:16]
        try:
            enc_b64 = aes128_cbc_encrypt_base64(data_str, aes_key)
            sig_md5 = md5_hex(enc_b64)
            signature_content = f"0|AES-128-CBC|{aes_key}|{sig_md5}|json"
            rsa_b64 = rsa_encrypt_pkcs1_base64(
                signature_content, self._cfg.client_public_key
            )
        except Exception as e:
            self._bsphp_emit(lines, "========== 请求加密失败（故无「加密后」「解密前」）==========")
            self._bsphp_emit(lines, f"api: {api} 错误: {e!r}")
            self._bsphp_emit(
                lines,
                "常见原因：clientPublicKey 须为后台「客户端公钥」；勿与 serverPrivateKey 对调；须与 mutualkey 对应应用一致",
            )
            self.last_send_debug = "\n".join(lines)
            return None
        payload = f"{enc_b64}|{rsa_b64}"
        encoded = quote(payload, safe="-_.~")
        self._bsphp_emit(lines, "========== 加密后 ==========")
        self._bsphp_emit(lines, f"encrypted_b64: {_bsphp_truncate(enc_b64, 120)}")
        self._bsphp_emit(lines, f"rsa_b64: {_bsphp_truncate(rsa_b64, 120)}")
        self._bsphp_emit(lines, f"payload 总长度: {len(payload)}, encoded 总长度: {len(encoded)}")
        try:
            r = self._session.post(
                self._cfg.url,
                data=f"parameter={encoded}",
                timeout=60,
            )
        except OSError as e:
            self._bsphp_emit(lines, f"HTTP 请求异常: {e!r}")
            self.last_send_debug = "\n".join(lines)
            return None
        self._bsphp_emit(lines, "========== 解密前(服务器原始响应) ==========")
        self._bsphp_emit(lines, f"http_status: {r.status_code}")
        if r.status_code != 200 or not r.content:
            self._bsphp_emit(lines, "响应非 200 或 body 为空，无法解密")
            self.last_send_debug = "\n".join(lines)
            return None
        text = r.text.strip()
        if not text:
            self._bsphp_emit(lines, "响应文本为空")
            self.last_send_debug = "\n".join(lines)
            return None
        raw = unquote(text) if "%" in text else text
        self._bsphp_emit(lines, f"raw 长度: {len(raw)}")
        self._bsphp_emit(lines, f"raw: {_bsphp_truncate(raw, 300)}")
        resp = decrypt_response_body(
            raw, self._cfg.server_private_key, appsafecode
        )
        if resp is None:
            self._bsphp_emit(lines, "========== 解密失败 ==========")
            self._bsphp_emit(
                lines,
                "无法解析响应（段数不足、RSA/AES 失败、JSON 非法或 appsafecode 不匹配）。请核对 serverPrivateKey / clientPublicKey / mutualkey / AppEn URL。",
            )
            self.last_send_debug = "\n".join(lines)
            return None
        self._bsphp_emit(lines, "========== 解密后 ==========")
        self._bsphp_emit(lines, f"response 摘要: code={resp.get('code')!r} data={_bsphp_truncate(str(resp.get('data')), 500)}")
        self.last_send_debug = "\n".join(lines)
        return resp

    def connect(self) -> bool:
        r = self.send("internet.in")
        return bool(r and str(r.get("data")) == "1")

    def get_sessl(self) -> bool:
        r = self.send("BSphpSeSsL.in")
        if not r:
            return False
        data = r.get("data")
        if isinstance(data, str) and data:
            self.bs_php_sessl = data
            return True
        return False

    def bootstrap(self) -> None:
        r = self.send("internet.in")
        log1 = self.last_send_debug
        if not r:
            raise InitFailedError("连接失败", debug_log=log1)
        if str(r.get("data")) != "1":
            raise InitFailedError(
                f"连接失败（internet.in 返回 data={r.get('data')!r}，期望 '1'）",
                debug_log=log1,
            )
        r = self.send("BSphpSeSsL.in")
        log2 = self.last_send_debug
        if not r:
            raise InitFailedError(
                "获取 BSphpSeSsL 失败",
                debug_log=f"--- internet.in ---\n{log1}\n\n--- BSphpSeSsL.in ---\n{log2}",
            )
        data = r.get("data")
        if isinstance(data, str) and data:
            self.bs_php_sessl = data
            return
        raise InitFailedError(
            f"获取 BSphpSeSsL 失败（data={data!r}，期望非空字符串）",
            debug_log=f"--- internet.in ---\n{log1}\n\n--- BSphpSeSsL.in ---\n{log2}",
        )

    def _result(self, api: str, params: Optional[dict[str, str]] = None) -> APIResult:
        r = self.send(api, params)
        if not r:
            return APIResult(None, None)
        return APIResult(r.get("data"), _coerce_code(r.get("code")))

    def logout(self) -> APIResult:
        r = self._result("cancellation.lg")
        self.bs_php_sessl = ""
        self.get_sessl()
        return r

    def get_notice(self) -> APIResult:
        return self._result("gg.in")

    def get_version(self) -> APIResult:
        return self._result("v.in")

    def get_soft_info(self) -> APIResult:
        return self._result("miao.in")

    def get_server_date(self) -> APIResult:
        return self._result("date.in")

    def get_preset_url(self) -> APIResult:
        return self._result("url.in")

    def get_web_url(self) -> APIResult:
        return self._result("weburl.in")

    def get_global_info(self) -> APIResult:
        return self._result("globalinfo.in")

    def get_app_custom(self, info: str) -> APIResult:
        return self._result("appcustom.in", {"info": info})

    def get_code_enabled(
        self, types: Optional[list[str]] = None, single: Optional[str] = None
    ) -> APIResult:
        if single:
            return self._result("getsetimag.in", {"type": single})
        if types:
            return self._result(
                "getsetimag.in", {"type": "|".join(types)}
            )
        return self._result("getsetimag.in")

    def get_logic_a(self) -> APIResult:
        return self._result("logica.in")

    def get_logic_b(self) -> APIResult:
        return self._result("logicb.in")

    def get_logic_info_a(self) -> APIResult:
        return self._result("logicinfoa.in")

    def get_logic_info_b(self) -> APIResult:
        return self._result("logicinfob.in")

    def get_end_time(self) -> APIResult:
        return self._result("vipdate.lg")

    def get_user_info(self, info: Optional[str] = None) -> APIResult:
        p = {"info": info} if info else {}
        return self._result("getuserinfo.lg", p)

    def get_user_key(self) -> APIResult:
        return self._result("userkey.lg")

    def heartbeat(self) -> APIResult:
        return self._result("timeout.lg")

    def login(
        self,
        user: str,
        password: str,
        code: str,
        key: str = "",
        maxoror: str = "",
    ) -> APIResult:
        mc = key or self.machine_code
        mx = maxoror or mc
        r = self.send(
            "login.lg",
            {
                "user": user,
                "pwd": password,
                "coode": code,
                "key": mc,
                "maxoror": mx,
            },
        )
        if not r:
            return APIResult("系统错误，登录失败！", None)
        ci = _coerce_code(r.get("code"))
        if ci in (1011, 9908) and isinstance(r.get("SeSsL"), str):
            self.bs_php_sessl = r["SeSsL"]
        return APIResult(r.get("data"), ci)

    def send_email_code(self, scene: str, email: str, coode: str) -> APIResult:
        return self._result(
            "send_email.lg",
            {"scene": scene, "email": email, "coode": coode},
        )

    def register_email(
        self,
        user: str,
        email: str,
        email_code: str,
        pwd: str,
        pwdb: str,
        key: str,
        coode: str,
    ) -> APIResult:
        return self._result(
            "register_email.lg",
            {
                "user": user,
                "email": email,
                "email_code": email_code,
                "pwd": pwd,
                "pwdb": pwdb,
                "key": key,
                "coode": coode,
            },
        )

    def login_email(
        self,
        email: str,
        email_code: str,
        key: str,
        maxoror: str,
        coode: str,
    ) -> APIResult:
        r = self.send(
            "login_email.lg",
            {
                "email": email,
                "email_code": email_code,
                "key": key,
                "maxoror": maxoror,
                "coode": coode,
            },
        )
        if not r:
            return APIResult("系统错误，邮箱验证码登录失败！", None)
        return APIResult(r.get("data"), _coerce_code(r.get("code")))

    def reset_email_pwd(
        self,
        email: str,
        email_code: str,
        pwd: str,
        pwdb: str,
        coode: str,
    ) -> APIResult:
        return self._result(
            "resetpwd_email.lg",
            {
                "email": email,
                "email_code": email_code,
                "pwd": pwd,
                "pwdb": pwdb,
                "coode": coode,
            },
        )

    def send_sms_code(
        self, scene: str, mobile: str, area: str, coode: str
    ) -> APIResult:
        return self._result(
            "send_sms.lg",
            {
                "scene": scene,
                "mobile": mobile,
                "area": area,
                "coode": coode,
            },
        )

    def register_sms(
        self,
        user: str,
        mobile: str,
        area: str,
        sms_code: str,
        pwd: str,
        pwdb: str,
        key: str,
        coode: str,
    ) -> APIResult:
        return self._result(
            "register_sms.lg",
            {
                "user": user,
                "mobile": mobile,
                "area": area,
                "sms_code": sms_code,
                "pwd": pwd,
                "pwdb": pwdb,
                "key": key,
                "coode": coode,
            },
        )

    def login_sms(
        self,
        mobile: str,
        area: str,
        sms_code: str,
        key: str,
        maxoror: str,
        coode: str,
    ) -> APIResult:
        r = self.send(
            "login_sms.lg",
            {
                "mobile": mobile,
                "area": area,
                "sms_code": sms_code,
                "key": key,
                "maxoror": maxoror,
                "coode": coode,
            },
        )
        if not r:
            return APIResult("系统错误，短信验证码登录失败！", None)
        ci = _coerce_code(r.get("code"))
        if ci in (1011, 9908) and isinstance(r.get("SeSsL"), str):
            self.bs_php_sessl = r["SeSsL"]
        return APIResult(r.get("data"), ci)

    def reset_sms_pwd(
        self,
        mobile: str,
        area: str,
        sms_code: str,
        pwd: str,
        pwdb: str,
        coode: str,
    ) -> APIResult:
        return self._result(
            "resetpwd_sms.lg",
            {
                "mobile": mobile,
                "area": area,
                "sms_code": sms_code,
                "pwd": pwd,
                "pwdb": pwdb,
                "coode": coode,
            },
        )

    def reg(
        self,
        user: str,
        pwd: str,
        pwdb: str,
        coode: str,
        mobile: str,
        mibao_wenti: str,
        mibao_daan: str,
        qq: str = "",
        mail: str = "",
        extension: str = "",
    ) -> APIResult:
        return self._result(
            "registration.lg",
            {
                "user": user,
                "pwd": pwd,
                "pwdb": pwdb,
                "coode": coode,
                "mobile": mobile,
                "mibao_wenti": mibao_wenti,
                "mibao_daan": mibao_daan,
                "qq": qq,
                "mail": mail,
                "extension": extension,
            },
        )

    def unbind(self, user: str, pwd: str) -> APIResult:
        return self._result("jiekey.lg", {"user": user, "pwd": pwd})

    def pay(
        self,
        user: str,
        userpwd: str,
        userset: bool,
        ka: str,
        pwd: str,
    ) -> APIResult:
        return self._result(
            "chong.lg",
            {
                "user": user,
                "userpwd": userpwd,
                "userset": "1" if userset else "0",
                "ka": ka,
                "pwd": pwd,
            },
        )

    def back_pass(
        self,
        user: str,
        pwd: str,
        pwdb: str,
        wenti: str,
        daan: str,
        coode: str = "",
    ) -> APIResult:
        return self._result(
            "backto.lg",
            {
                "user": user,
                "pwd": pwd,
                "pwdb": pwdb,
                "wenti": wenti,
                "daan": daan,
                "coode": coode,
            },
        )

    def edit_pass(
        self,
        user: str,
        pwd: str,
        pwda: str,
        pwdb: str,
        img: str,
    ) -> APIResult:
        return self._result(
            "password.lg",
            {
                "user": user,
                "pwd": pwd,
                "pwda": pwda,
                "pwdb": pwdb,
                "img": img,
            },
        )

    def feedback(
        self,
        user: str,
        pwd: str,
        table: str,
        qq: str,
        leix: str,
        text: str,
        coode: str,
    ) -> APIResult:
        return self._result(
            "liuyan.in",
            {
                "user": user,
                "pwd": pwd,
                "table": table,
                "qq": qq,
                "leix": leix,
                "txt": text,
                "coode": coode,
            },
        )
