# -*- coding: utf-8 -*-
from __future__ import annotations

import os
import subprocess
import sys
import urllib.error
import urllib.request
import webbrowser
from pathlib import Path
from typing import Any, Callable
from urllib.parse import quote

from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QComboBox,
    QDialog,
    QFormLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMainWindow,
    QMessageBox,
    QPushButton,
    QTabWidget,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

import config as cfg
from bsphp_client import (
    BSPHPClient,
    ClientConfig,
    CODE_TYPE_BACK_PWD,
    CODE_TYPE_LOGIN,
    CODE_TYPE_REG,
    CODE_TYPE_SAY,
    InitFailedError,
    USER_INFO_FIELDS,
)
from machine_code import get_machine_code
from ui_common import INPUT_FIELD_QSS, run_async

REG_QUESTIONS = [
    "你最喜欢的颜色？",
    "你母亲的名字？",
    "你父亲的名字？",
    "你的出生地？",
    "你最喜欢的食物？",
    "你的小学名称？",
    "自定义问题",
]
FEEDBACK_TYPES = ["建议反馈", "BUG", "使用问题"]


class ConsoleWindow(QDialog):
    def __init__(self, owner: "UserDemoQt") -> None:
        super().__init__(owner)
        self.owner = owner
        self.client = owner.client
        self.setWindowTitle("控制台")
        self.resize(980, 640)
        root = QVBoxLayout(self)
        self.detail = QTextEdit()
        self.detail.setReadOnly(True)
        root.addWidget(self._section("公用接口", [
            ("服务器时间", self.client.get_server_date),
            ("预设URL", self.client.get_preset_url),
            ("Web地址", self.client.get_web_url),
            ("全局配置", self.client.get_global_info),
            ("逻辑值A", self.client.get_logic_a),
            ("逻辑值B", self.client.get_logic_b),
            ("逻辑值A内容", self.client.get_logic_info_a),
            ("逻辑值B内容", self.client.get_logic_info_b),
        ]))
        root.addWidget(self._section("自定义配置模型", [
            ("软件配置", lambda: self.client.get_app_custom("myapp")),
            ("VIP配置", lambda: self.client.get_app_custom("myvip")),
            ("登录配置", lambda: self.client.get_app_custom("mylogin")),
        ]))
        root.addWidget(self._section("通用接口", [
            ("获取版本", self.client.get_version),
            ("获取软件描述", self.client.get_soft_info),
        ]))
        root.addWidget(self._section("登录模式接口", [
            ("注销登录", self._logout),
            ("检测到期", self.client.get_end_time),
            ("取用户信息(默认)", self.client.get_user_info),
            ("心跳包更新", self.client.heartbeat),
            ("用户特征Key", self.client.get_user_key),
        ]))
        user_info_box = QGroupBox("取用户信息 info 字段")
        ui_lay = QHBoxLayout(user_info_box)
        for key, label in USER_INFO_FIELDS:
            btn = QPushButton(label)
            btn.clicked.connect(lambda _=False, k=key, t=label: self._run(t, lambda: self.client.get_user_info(info=k)))
            ui_lay.addWidget(btn)
        root.addWidget(user_info_box)
        renew = QGroupBox("续费订阅推广")
        rl = QHBoxLayout(renew)
        b1 = QPushButton("续费订阅(直接)")
        b1.clicked.connect(self._open_renew)
        b2 = QPushButton("购买充值卡")
        b2.clicked.connect(lambda: webbrowser.open(cfg.BSPHP_RENEW_CARD_URL))
        b3 = QPushButton("购买库存卡")
        b3.clicked.connect(lambda: webbrowser.open(cfg.BSPHP_RENEW_STOCK_CARD_URL))
        rl.addWidget(b1)
        rl.addWidget(b2)
        rl.addWidget(b3)
        root.addWidget(renew)
        root.addWidget(self.detail, 1)

    def _section(self, title: str, items: list[tuple[str, Callable[[], Any]]]) -> QWidget:
        box = QGroupBox(title)
        lay = QHBoxLayout(box)
        for text, fn in items:
            btn = QPushButton(text)
            btn.clicked.connect(lambda _=False, t=text, f=fn: self._run(t, f))
            lay.addWidget(btn)
        lay.addStretch(1)
        return box

    def _run(self, title: str, fn: Callable[[], Any]) -> None:
        def done(res: Any) -> None:
            if isinstance(res, Exception):
                QMessageBox.critical(self, "错误", repr(res))
                return
            body = res.message() if hasattr(res, "message") else str(res)
            code = getattr(res, "code", None)
            self.detail.setPlainText(f"[{title}] code={code}\n{body}")

        run_async(fn, done)

    def _logout(self) -> Any:
        r = self.client.logout()
        self.owner.logged_in = False
        self.owner.refresh_status()
        return r

    def _open_renew(self) -> None:
        r = self.client.get_user_info(info="UserName")
        msg = r.message() if hasattr(r, "message") else ""
        user = msg.split("=", 1)[-1].strip() if "=" in msg else msg.strip()
        url = cfg.BSPHP_RENEW_URL if not user else f"{cfg.BSPHP_RENEW_URL}&user={quote(user)}"
        webbrowser.open(url)


class UserDemoQt(QMainWindow):
    def __init__(self) -> None:
        super().__init__()
        self.setWindowTitle("BSPHP 账号模式演示 (PySide6)")
        self.resize(1080, 860)
        self.logged_in = False
        self.ready = False
        self._busy = False
        self._bootstrap_running = False
        self.marker_path = Path.home() / ".bsphp_python_demo" / "web_login_marker.txt"
        self.client = BSPHPClient(
            ClientConfig(
                url=cfg.BSPHP_URL,
                mutual_key=cfg.BSPHP_MUTUAL_KEY,
                server_private_key=cfg.BSPHP_SERVER_PRIVATE_KEY,
                client_public_key=cfg.BSPHP_CLIENT_PUBLIC_KEY,
                code_url_prefix=cfg.BSPHP_CODE_URL_PREFIX,
            ),
            get_machine_code,
        )
        self.code_enabled = {
            CODE_TYPE_LOGIN: True,
            CODE_TYPE_REG: True,
            CODE_TYPE_BACK_PWD: True,
            CODE_TYPE_SAY: True,
        }
        self.captcha_preview_by_tab: dict[int, tuple[str, QLabel, bool]] = {}
        self._init_ui()
        self.bootstrap()

    def _init_ui(self) -> None:
        root = QWidget(self)
        self.setCentralWidget(root)
        layout = QVBoxLayout(root)
        self.notice = QTextEdit()
        self.notice.setReadOnly(True)
        self.notice.setFixedHeight(120)
        layout.addWidget(self.notice)
        st = QHBoxLayout()
        self.status_dot = QLabel("●")
        self.status_text = QLabel("服务未连接")
        self.busy_text = QLabel("")
        self.login_badge = QLabel("")
        retry = QPushButton("重新连接")
        retry.clicked.connect(self.bootstrap)
        st.addWidget(self.status_dot)
        st.addWidget(self.status_text)
        st.addWidget(self.busy_text)
        st.addStretch(1)
        st.addWidget(self.login_badge)
        st.addWidget(retry)
        layout.addLayout(st)
        self.tabs = QTabWidget()
        layout.addWidget(self.tabs, 1)
        self._build_password_login_tab()
        self._build_sms_login_tab()
        self._build_email_login_tab()
        self._build_register_tab()
        self._build_sms_register_tab()
        self._build_email_register_tab()
        self._build_unbind_tab()
        self._build_recharge_tab()
        self._build_sms_recover_tab()
        self._build_email_recover_tab()
        self._build_recover_tab()
        self._build_change_pwd_tab()
        self._build_feedback_tab()
        self.tabs.currentChanged.connect(self._on_tab_changed)

    def _tab(self, title: str) -> tuple[QWidget, QFormLayout]:
        page = QWidget()
        frm = QFormLayout(page)
        frm.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
        self.tabs.addTab(page, title)
        return page, frm

    def _add_actions(self, form: QFormLayout, actions: list[tuple[str, Callable[[], None]]]) -> None:
        row = QHBoxLayout()
        for t, fn in actions:
            b = QPushButton(t)
            b.clicked.connect(fn)
            row.addWidget(b)
        row.addStretch(1)
        wrap = QWidget()
        wrap.setLayout(row)
        form.addRow("", wrap)

    def _add_captcha_preview(
        self,
        page: QWidget,
        form: QFormLayout,
        code_type: str,
        force_enabled: bool = False,
    ) -> None:
        tab_index = self.tabs.indexOf(page)
        row = QHBoxLayout()
        label = QLabel("验证码预览")
        img = QLabel("未加载")
        img.setFixedSize(120, 40)
        img.setAlignment(Qt.AlignCenter)
        img.setStyleSheet("border:1px solid #A0A0A0; background:#FFFFFF;")
        btn_refresh = QPushButton("刷新")
        btn_refresh.clicked.connect(lambda: self.refresh_captcha_image(tab_index, force_new=True))
        btn_open = QPushButton("浏览器打开")
        btn_open.clicked.connect(lambda: self.open_captcha_in_browser(tab_index))
        row.addWidget(label)
        row.addWidget(img)
        row.addWidget(btn_refresh)
        row.addWidget(btn_open)
        row.addStretch(1)
        wrap = QWidget()
        wrap.setLayout(row)
        form.addRow("", wrap)
        self.captcha_preview_by_tab[tab_index] = (code_type, img, force_enabled)

    def _build_password_login_tab(self) -> None:
        page, f = self._tab("密码登录")
        self.login_user = QLineEdit()
        self.login_pwd = QLineEdit(); self.login_pwd.setEchoMode(QLineEdit.Password)
        self.login_code = QLineEdit()
        f.addRow("登录账号", self.login_user)
        f.addRow("登录密码", self.login_pwd)
        f.addRow("验证码", self.login_code)
        self._add_captcha_preview(page, f, CODE_TYPE_LOGIN)
        self._add_actions(f, [
            ("测试网络", self.test_net),
            ("检测到期", self.check_end),
            ("获取版本", self.check_version),
            ("Web方式登录", self.web_login),
            ("检测 Web 登录", self.check_web_login),
            ("登录", self.do_login),
            ("控制台", self.open_console),
        ])

    def _build_sms_login_tab(self) -> None:
        page, f = self._tab("短信登录")
        self.sms_mobile = QLineEdit()
        self.sms_area = QLineEdit("86")
        self.sms_code = QLineEdit()
        self.sms_key = QLineEdit(get_machine_code())
        self.sms_max = QLineEdit(get_machine_code())
        self.sms_coode = QLineEdit()
        f.addRow("手机号", self.sms_mobile)
        f.addRow("区号", self.sms_area)
        f.addRow("图形验证码", self.sms_coode)
        self._add_captcha_preview(page, f, CODE_TYPE_LOGIN)
        f.addRow("短信验证码", self.sms_code)
        f.addRow("绑定特征key", self.sms_key)
        f.addRow("maxoror", self.sms_max)
        self._add_actions(f, [("发送短信验证码", self.send_sms_login), ("短信登录", self.do_sms_login)])

    def _build_email_login_tab(self) -> None:
        page, f = self._tab("邮箱登录")
        self.em_login_email = QLineEdit()
        self.em_login_code = QLineEdit()
        self.em_login_key = QLineEdit(get_machine_code())
        self.em_login_max = QLineEdit(get_machine_code())
        self.em_login_coode = QLineEdit()
        f.addRow("邮箱", self.em_login_email)
        f.addRow("图形验证码", self.em_login_coode)
        self._add_captcha_preview(page, f, CODE_TYPE_LOGIN)
        f.addRow("邮箱验证码", self.em_login_code)
        f.addRow("绑定特征key", self.em_login_key)
        f.addRow("maxoror", self.em_login_max)
        self._add_actions(f, [("发送邮箱验证码", self.send_email_login), ("邮箱登录", self.do_email_login)])

    def _build_register_tab(self) -> None:
        page, f = self._tab("账号注册")
        self.reg_user = QLineEdit(); self.reg_pwd = QLineEdit(); self.reg_pwd.setEchoMode(QLineEdit.Password)
        self.reg_pwdb = QLineEdit(); self.reg_pwdb.setEchoMode(QLineEdit.Password)
        self.reg_mobile = QLineEdit(); self.reg_qq = QLineEdit(); self.reg_mail = QLineEdit()
        self.reg_q = QComboBox(); self.reg_q.addItems(REG_QUESTIONS)
        self.reg_a = QLineEdit(); self.reg_code = QLineEdit(); self.reg_ext = QLineEdit()
        f.addRow("注册账号", self.reg_user); f.addRow("注册密码", self.reg_pwd); f.addRow("确认密码", self.reg_pwdb)
        f.addRow("手机号", self.reg_mobile); f.addRow("QQ", self.reg_qq); f.addRow("邮箱", self.reg_mail)
        f.addRow("密保问题", self.reg_q); f.addRow("密保答案", self.reg_a); f.addRow("验证码", self.reg_code)
        self._add_captcha_preview(page, f, CODE_TYPE_REG, force_enabled=True)
        f.addRow("推广码", self.reg_ext)
        self._add_actions(f, [("注册", self.do_register)])

    def _build_sms_register_tab(self) -> None:
        page, f = self._tab("短信注册")
        self.sr_mobile = QLineEdit(); self.sr_area = QLineEdit("86"); self.sr_user = QLineEdit()
        self.sr_sms = QLineEdit(); self.sr_pwd = QLineEdit(); self.sr_pwd.setEchoMode(QLineEdit.Password)
        self.sr_pwdb = QLineEdit(); self.sr_pwdb.setEchoMode(QLineEdit.Password)
        self.sr_key = QLineEdit(get_machine_code()); self.sr_coode = QLineEdit()
        f.addRow("手机号", self.sr_mobile); f.addRow("区号", self.sr_area); f.addRow("图形验证码", self.sr_coode)
        self._add_captcha_preview(page, f, CODE_TYPE_REG, force_enabled=True)
        f.addRow("短信验证码", self.sr_sms); f.addRow("账号", self.sr_user); f.addRow("密码", self.sr_pwd); f.addRow("确认密码", self.sr_pwdb); f.addRow("绑定特征key", self.sr_key)
        self._add_actions(f, [("发送短信验证码", self.send_sms_register), ("短信注册", self.do_sms_register)])

    def _build_email_register_tab(self) -> None:
        page, f = self._tab("邮箱注册")
        self.er_email = QLineEdit(); self.er_user = QLineEdit(); self.er_code = QLineEdit()
        self.er_pwd = QLineEdit(); self.er_pwd.setEchoMode(QLineEdit.Password)
        self.er_pwdb = QLineEdit(); self.er_pwdb.setEchoMode(QLineEdit.Password)
        self.er_key = QLineEdit(get_machine_code()); self.er_coode = QLineEdit()
        f.addRow("邮箱", self.er_email); f.addRow("图形验证码", self.er_coode)
        self._add_captcha_preview(page, f, CODE_TYPE_REG)
        f.addRow("邮箱验证码", self.er_code)
        f.addRow("账号", self.er_user); f.addRow("密码", self.er_pwd); f.addRow("确认密码", self.er_pwdb); f.addRow("绑定特征key", self.er_key)
        self._add_actions(f, [("发送邮箱验证码", self.send_email_register), ("邮箱注册", self.do_email_register)])

    def _build_unbind_tab(self) -> None:
        _, f = self._tab("解绑")
        self.ub_user = QLineEdit(); self.ub_pwd = QLineEdit(); self.ub_pwd.setEchoMode(QLineEdit.Password)
        f.addRow("登录账号", self.ub_user); f.addRow("登录密码", self.ub_pwd)
        self._add_actions(f, [("解绑", self.do_unbind)])

    def _build_recharge_tab(self) -> None:
        _, f = self._tab("充值")
        self.rc_user = QLineEdit(); self.rc_pwd = QLineEdit(); self.rc_pwd.setEchoMode(QLineEdit.Password)
        self.rc_ka = QLineEdit(); self.rc_kp = QLineEdit(); self.rc_kp.setEchoMode(QLineEdit.Password)
        self.rc_verify = QCheckBox("需要验证登录密码")
        self.rc_verify.setChecked(True)
        f.addRow("充值账号", self.rc_user); f.addRow("登录密码", self.rc_pwd); f.addRow("充值卡号", self.rc_ka); f.addRow("充值密码", self.rc_kp); f.addRow("验证密码", self.rc_verify)
        self._add_actions(f, [("充值", self.do_recharge)])

    def _build_sms_recover_tab(self) -> None:
        page, f = self._tab("短信找回")
        self.sre_mobile = QLineEdit(); self.sre_area = QLineEdit("86"); self.sre_coode = QLineEdit(); self.sre_sms = QLineEdit()
        self.sre_pwd = QLineEdit(); self.sre_pwd.setEchoMode(QLineEdit.Password); self.sre_pwdb = QLineEdit(); self.sre_pwdb.setEchoMode(QLineEdit.Password)
        f.addRow("手机号", self.sre_mobile); f.addRow("区号", self.sre_area); f.addRow("图形验证码", self.sre_coode)
        self._add_captcha_preview(page, f, CODE_TYPE_BACK_PWD, force_enabled=True)
        f.addRow("短信验证码", self.sre_sms); f.addRow("新密码", self.sre_pwd); f.addRow("确认密码", self.sre_pwdb)
        self._add_actions(f, [("发送短信验证码", self.send_sms_reset), ("短信找回", self.do_sms_reset)])

    def _build_email_recover_tab(self) -> None:
        page, f = self._tab("邮箱找回")
        self.ere_email = QLineEdit(); self.ere_coode = QLineEdit(); self.ere_code = QLineEdit()
        self.ere_pwd = QLineEdit(); self.ere_pwd.setEchoMode(QLineEdit.Password); self.ere_pwdb = QLineEdit(); self.ere_pwdb.setEchoMode(QLineEdit.Password)
        f.addRow("邮箱", self.ere_email); f.addRow("图形验证码", self.ere_coode)
        self._add_captcha_preview(page, f, CODE_TYPE_BACK_PWD, force_enabled=True)
        f.addRow("邮箱验证码", self.ere_code); f.addRow("新密码", self.ere_pwd); f.addRow("确认密码", self.ere_pwdb)
        self._add_actions(f, [("发送邮箱验证码", self.send_email_reset), ("邮箱找回", self.do_email_reset)])

    def _build_recover_tab(self) -> None:
        page, f = self._tab("找回密码")
        self.bp_user = QLineEdit(); self.bp_q = QComboBox(); self.bp_q.addItems(REG_QUESTIONS); self.bp_a = QLineEdit()
        self.bp_n1 = QLineEdit(); self.bp_n1.setEchoMode(QLineEdit.Password); self.bp_n2 = QLineEdit(); self.bp_n2.setEchoMode(QLineEdit.Password); self.bp_code = QLineEdit()
        f.addRow("登录账号", self.bp_user); f.addRow("密保问题", self.bp_q); f.addRow("密保答案", self.bp_a); f.addRow("新密码", self.bp_n1); f.addRow("确认密码", self.bp_n2); f.addRow("验证码", self.bp_code)
        self._add_captcha_preview(page, f, CODE_TYPE_BACK_PWD)
        self._add_actions(f, [("找回密码", self.do_back_pass)])

    def _build_change_pwd_tab(self) -> None:
        page, f = self._tab("修改密码")
        self.cp_user = QLineEdit(); self.cp_old = QLineEdit(); self.cp_old.setEchoMode(QLineEdit.Password)
        self.cp_n1 = QLineEdit(); self.cp_n1.setEchoMode(QLineEdit.Password); self.cp_n2 = QLineEdit(); self.cp_n2.setEchoMode(QLineEdit.Password); self.cp_img = QLineEdit()
        f.addRow("登录账号", self.cp_user); f.addRow("旧密码", self.cp_old); f.addRow("新密码", self.cp_n1); f.addRow("确认密码", self.cp_n2); f.addRow("验证码", self.cp_img)
        self._add_captcha_preview(page, f, CODE_TYPE_BACK_PWD)
        self._add_actions(f, [("修改密码", self.do_change_pwd)])

    def _build_feedback_tab(self) -> None:
        page, f = self._tab("意见反馈")
        self.fb_user = QLineEdit(); self.fb_pwd = QLineEdit(); self.fb_pwd.setEchoMode(QLineEdit.Password)
        self.fb_title = QLineEdit(); self.fb_contact = QLineEdit(); self.fb_type = QComboBox(); self.fb_type.addItems(FEEDBACK_TYPES)
        self.fb_text = QTextEdit(); self.fb_code = QLineEdit()
        f.addRow("账号", self.fb_user); f.addRow("密码", self.fb_pwd); f.addRow("标题", self.fb_title); f.addRow("联系", self.fb_contact); f.addRow("类型", self.fb_type); f.addRow("内容", self.fb_text); f.addRow("验证码", self.fb_code)
        self._add_captcha_preview(page, f, CODE_TYPE_SAY)
        self._add_actions(f, [("提交", self.do_feedback)])

    def _set_busy(self, on: bool) -> None:
        self._busy = on
        self.busy_text.setText("处理中..." if on else "")

    def refresh_status(self) -> None:
        if self.ready:
            self.status_text.setText("服务已连接")
            self.status_dot.setStyleSheet("color: green;")
        else:
            self.status_text.setText("服务未连接")
            self.status_dot.setStyleSheet("color: orange;")
        self.login_badge.setText("已登录" if self.logged_in else "")

    def _coode_for(self, code_type: str, text: str, *, force_enabled: bool = False) -> str:
        return text.strip() if (force_enabled or self.code_enabled.get(code_type, True)) else ""

    @staticmethod
    def _is_code_enabled_flag(raw: str) -> bool:
        v = raw.strip().lower()
        return v in {"checked", "check", "true", "1", "yes", "on"}

    def _apply_code_enabled_ui(self) -> None:
        login_on = self.code_enabled.get(CODE_TYPE_LOGIN, True)
        reg_on = self.code_enabled.get(CODE_TYPE_REG, True)
        back_on = self.code_enabled.get(CODE_TYPE_BACK_PWD, True)
        say_on = self.code_enabled.get(CODE_TYPE_SAY, True)
        login_fields = [self.login_code, self.sms_coode, self.em_login_coode]
        reg_fields = [self.reg_code]
        back_fields = [self.bp_code, self.cp_img]
        say_fields = [self.fb_code]
        for fields, on in (
            (login_fields, login_on),
            (reg_fields, reg_on),
            (back_fields, back_on),
            (say_fields, say_on),
        ):
            for w in fields:
                w.setEnabled(on)
                if on:
                    w.setPlaceholderText("")
                else:
                    w.clear()
                    w.setPlaceholderText("当前场景无需图形验证码")
        for tab_index, (code_type, img, force_enabled) in self.captcha_preview_by_tab.items():
            if force_enabled or self.code_enabled.get(code_type, True):
                continue
            img.setPixmap(QPixmap())
            img.setText("当前场景无需图形验证码")

    def _on_tab_changed(self, _index: int) -> None:
        if self.ready:
            self.refresh_captcha_image(self.tabs.currentIndex(), force_new=False)

    def alert(self, title: str, text: str, code: int | None = None) -> None:
        t = f"{title} (code={code})" if code is not None else title
        QMessageBox.information(self, t, text)

    def require_ready(self) -> bool:
        if self.ready:
            return True
        if self._bootstrap_running:
            QMessageBox.information(self, "提示", "正在初始化服务，请稍候再试。")
            return False
        QMessageBox.warning(self, "提示", "服务尚未连接，请先初始化。")
        return False

    def bootstrap(self) -> None:
        if self._bootstrap_running:
            return
        self._bootstrap_running = True
        self.ready = False
        self.refresh_status()
        self.notice.setPlainText("正在连接服务器...")

        def work() -> tuple[str, dict[str, bool]]:
            self.client.bootstrap()
            ce = self.client.get_code_enabled(types=[CODE_TYPE_LOGIN, CODE_TYPE_REG, CODE_TYPE_BACK_PWD, CODE_TYPE_SAY])
            m = {CODE_TYPE_LOGIN: True, CODE_TYPE_REG: True, CODE_TYPE_BACK_PWD: True, CODE_TYPE_SAY: True}
            if isinstance(ce.data, str):
                ps = ce.data.split("|")
                for i, k in enumerate([CODE_TYPE_LOGIN, CODE_TYPE_REG, CODE_TYPE_BACK_PWD, CODE_TYPE_SAY]):
                    if i < len(ps):
                        m[k] = self._is_code_enabled_flag(ps[i])
            notice = self.client.get_notice().message() or "暂无公告"
            return notice, m

        def done(res: Any) -> None:
            self._bootstrap_running = False
            if isinstance(res, InitFailedError):
                self.ready = False
                self.refresh_status()
                short = res.args[0] if res.args else "初始化失败"
                self.notice.setPlainText(
                    f"初始化失败: {short}\n\n"
                    "终端已输出 [BSPHP] 调试行（与 mac Xcode 控制台一致）。\n"
                    "点击下方弹窗「显示详细信息」可查看完整加密/解密日志。"
                )
                box = QMessageBox(self)
                box.setIcon(QMessageBox.Icon.Critical)
                box.setWindowTitle("初始化失败")
                box.setText(short)
                if res.debug_log:
                    box.setDetailedText(res.debug_log)
                box.exec()
                return
            if isinstance(res, Exception):
                self.ready = False
                self.refresh_status()
                self.notice.setPlainText(f"初始化失败: {res}")
                QMessageBox.critical(self, "初始化失败", str(res))
                return
            notice, m = res
            self.ready = True
            self.code_enabled = m
            self.notice.setPlainText(notice)
            self._apply_code_enabled_ui()
            self.refresh_captcha_image(self.tabs.currentIndex())
            self.refresh_status()

        run_async(work, done)

    def refresh_captcha_image(self, tab_index: int, force_new: bool = False) -> None:
        if not self.require_ready():
            return
        item = self.captcha_preview_by_tab.get(tab_index)
        if item is None:
            return
        code_type, img, force_enabled = item
        if (not force_enabled) and (not self.code_enabled.get(code_type, True)):
            img.setPixmap(QPixmap())
            img.setText("当前场景无需图形验证码")
            return
        if force_new:
            self.client.get_sessl()
        url = (self.client.code_image_url or "").strip()
        if not url:
            img.setText("验证码 URL 为空")
            img.setPixmap(QPixmap())
            return
        try:
            req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
            with urllib.request.urlopen(req, timeout=8) as resp:
                data = resp.read()
            pix = QPixmap()
            if not pix.loadFromData(data):
                raise ValueError("图片数据解析失败")
            img.setPixmap(pix.scaled(img.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
            img.setText("")
        except (OSError, urllib.error.URLError, ValueError) as e:
            img.setPixmap(QPixmap())
            img.setText("验证码加载失败")
            self.notice.append(f"验证码图片加载失败: {e}")

    def open_captcha_in_browser(self, tab_index: int) -> None:
        if not self.require_ready():
            return
        item = self.captcha_preview_by_tab.get(tab_index)
        if item is None:
            return
        code_type, _img, force_enabled = item
        if (not force_enabled) and (not self.code_enabled.get(code_type, True)):
            QMessageBox.information(self, "验证码", "当前场景无需图形验证码。")
            return
        url = (self.client.code_image_url or "").strip()
        if not url:
            QMessageBox.information(self, "验证码", "当前无可用验证码 URL。")
            return
        webbrowser.open(url)

    def _run_api(self, fn: Callable[[], Any], title: str, on_ok: Callable[[Any], None] | None = None) -> None:
        self._set_busy(True)

        def done(res: Any) -> None:
            self._set_busy(False)
            if isinstance(res, Exception):
                QMessageBox.critical(self, title, repr(res))
                return
            if on_ok:
                on_ok(res)

        run_async(fn, done)

    def test_net(self) -> None:
        if not self.require_ready():
            return
        self._run_api(lambda: self.client.connect(), "测试网络", lambda ok: self.alert("网络测试", "测试连接成功!" if ok else "测试连接失败!"))

    def check_end(self) -> None:
        if not self.require_ready():
            return
        self._run_api(self.client.get_end_time, "检测到期", lambda r: self.alert("检测到期", r.message() or "系统错误，取到期时间失败！", r.code))

    def check_version(self) -> None:
        if not self.require_ready():
            return
        self._run_api(self.client.get_version, "版本", lambda r: self.alert("版本", str(r.data) if r.data is not None else "获取版本失败", r.code))

    def do_login(self) -> None:
        if not self.require_ready():
            return
        def work() -> Any:
            coode = self._coode_for(CODE_TYPE_LOGIN, self.login_code.text())
            return self.client.login(self.login_user.text().strip(), self.login_pwd.text().strip(), coode)
        def done(r: Any) -> None:
            if r.code in (1011, 9908):
                self.logged_in = True
                self.refresh_status()
                self.alert("登录", "登录成功！" if r.code == 1011 else "使用已经到期！", r.code)
                self.open_console()
            else:
                self.alert("登录失败", r.message() or "登录失败", r.code)
        self._run_api(work, "登录", done)

    def web_login(self) -> None:
        if not self.require_ready():
            return
        url = cfg.BSPHP_WEB_LOGIN_URL + self.client.bs_php_sessl
        helper = Path(__file__).resolve().parent / "web_login_helper.py"
        self.marker_path.parent.mkdir(parents=True, exist_ok=True)
        if self.marker_path.exists():
            self.marker_path.unlink()
        try:
            subprocess.Popen([sys.executable, str(helper), url, str(self.marker_path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except OSError:
            webbrowser.open(url)

    def check_web_login(self) -> None:
        if not self.require_ready():
            return
        def done(r: Any) -> None:
            if r.code == 5031:
                self.logged_in = True
                self.refresh_status()
                self.alert("网页登录", "登录成功！")
                self.open_console()
            else:
                self.alert("网页登录", r.message() or "尚未检测到网页登录完成", r.code)
        self._run_api(self.client.heartbeat, "检测 Web 登录", done)

    def open_console(self) -> None:
        if not self.require_ready():
            return
        dlg = ConsoleWindow(self)
        dlg.show()
        dlg.exec()

    def send_sms_login(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.send_sms_code(
                "login",
                self.sms_mobile.text().strip(),
                self.sms_area.text().strip() or "86",
                self._coode_for(CODE_TYPE_LOGIN, self.sms_coode.text()),
            ),
            "发送短信",
            lambda r: self.alert("发送短信", r.message() or "发送失败", r.code),
        )

    def do_sms_login(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.login_sms(
                self.sms_mobile.text().strip(),
                self.sms_area.text().strip() or "86",
                self.sms_code.text().strip(),
                self.sms_key.text().strip(),
                self.sms_max.text().strip(),
                self._coode_for(CODE_TYPE_LOGIN, self.sms_coode.text()),
            ),
            "短信登录",
            lambda r: self._after_login(r, "短信验证码登录失败！"),
        )

    def send_email_login(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.send_email_code(
                "login",
                self.em_login_email.text().strip(),
                self._coode_for(CODE_TYPE_LOGIN, self.em_login_coode.text()),
            ),
            "发送邮箱",
            lambda r: self.alert("发送邮箱", r.message() or "发送失败", r.code),
        )

    def do_email_login(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.login_email(
                self.em_login_email.text().strip(),
                self.em_login_code.text().strip(),
                self.em_login_key.text().strip(),
                self.em_login_max.text().strip(),
                self._coode_for(CODE_TYPE_LOGIN, self.em_login_coode.text()),
            ),
            "邮箱登录",
            lambda r: self._after_login(r, "邮箱验证码登录失败！"),
        )

    def do_register(self) -> None:
        if not self.require_ready():
            return
        c = self.reg_code.text().strip() if self.code_enabled.get(CODE_TYPE_REG, True) else ""
        self._run_api(lambda: self.client.reg(self.reg_user.text().strip(), self.reg_pwd.text().strip(), self.reg_pwdb.text().strip(), c, self.reg_mobile.text().strip(), self.reg_q.currentText(), self.reg_a.text().strip(), self.reg_qq.text().strip(), self.reg_mail.text().strip(), self.reg_ext.text().strip()), "注册", lambda r: self.alert("注册", r.message() or "系统错误，注册失败！", r.code))

    def send_sms_register(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.send_sms_code(
                "register",
                self.sr_mobile.text().strip(),
                self.sr_area.text().strip() or "86",
                self._coode_for(CODE_TYPE_REG, self.sr_coode.text(), force_enabled=True),
            ),
            "发送短信",
            lambda r: self.alert("发送短信", r.message() or "系统错误，发送短信验证码失败！", r.code),
        )

    def do_sms_register(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.register_sms(
                self.sr_user.text().strip(),
                self.sr_mobile.text().strip(),
                self.sr_area.text().strip() or "86",
                self.sr_sms.text().strip(),
                self.sr_pwd.text().strip(),
                self.sr_pwdb.text().strip(),
                self.sr_key.text().strip(),
                self._coode_for(CODE_TYPE_REG, self.sr_coode.text(), force_enabled=True),
            ),
            "短信注册",
            lambda r: self.alert("短信注册", r.message() or "系统错误，短信验证码注册失败！", r.code),
        )

    def send_email_register(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.send_email_code(
                "register",
                self.er_email.text().strip(),
                self._coode_for(CODE_TYPE_REG, self.er_coode.text(), force_enabled=True),
            ),
            "发送邮箱",
            lambda r: self.alert("发送邮箱", r.message() or "系统错误，发送邮箱验证码失败！", r.code),
        )

    def do_email_register(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.register_email(
                self.er_user.text().strip(),
                self.er_email.text().strip(),
                self.er_code.text().strip(),
                self.er_pwd.text().strip(),
                self.er_pwdb.text().strip(),
                self.er_key.text().strip(),
                self._coode_for(CODE_TYPE_REG, self.er_coode.text(), force_enabled=True),
            ),
            "邮箱注册",
            lambda r: self.alert("邮箱注册", r.message() or "系统错误，邮箱验证码注册失败！", r.code),
        )

    def do_unbind(self) -> None:
        if not self.require_ready():
            return
        self._run_api(lambda: self.client.unbind(self.ub_user.text().strip(), self.ub_pwd.text().strip()), "解绑", lambda r: self.alert("解绑", r.message() or "系统错误，解绑失败！", r.code))

    def do_recharge(self) -> None:
        if not self.require_ready():
            return
        self._run_api(lambda: self.client.pay(self.rc_user.text().strip(), self.rc_pwd.text().strip(), self.rc_verify.isChecked(), self.rc_ka.text().strip(), self.rc_kp.text().strip()), "充值", lambda r: self.alert("充值", r.message() or "系统错误，充值失败！", r.code))

    def send_sms_reset(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.send_sms_code(
                "reset",
                self.sre_mobile.text().strip(),
                self.sre_area.text().strip() or "86",
                self._coode_for(CODE_TYPE_BACK_PWD, self.sre_coode.text(), force_enabled=True),
            ),
            "发送短信",
            lambda r: self.alert("发送短信", r.message() or "系统错误，发送短信验证码失败！", r.code),
        )

    def do_sms_reset(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.reset_sms_pwd(
                self.sre_mobile.text().strip(),
                self.sre_area.text().strip() or "86",
                self.sre_sms.text().strip(),
                self.sre_pwd.text().strip(),
                self.sre_pwdb.text().strip(),
                self._coode_for(CODE_TYPE_BACK_PWD, self.sre_coode.text(), force_enabled=True),
            ),
            "短信找回",
            lambda r: self.alert("短信找回", r.message() or "系统错误，短信验证码找回失败！", r.code),
        )

    def send_email_reset(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.send_email_code(
                "reset",
                self.ere_email.text().strip(),
                self._coode_for(CODE_TYPE_BACK_PWD, self.ere_coode.text(), force_enabled=True),
            ),
            "发送邮箱",
            lambda r: self.alert("发送邮箱", r.message() or "系统错误，发送邮箱验证码失败！", r.code),
        )

    def do_email_reset(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.reset_email_pwd(
                self.ere_email.text().strip(),
                self.ere_code.text().strip(),
                self.ere_pwd.text().strip(),
                self.ere_pwdb.text().strip(),
                self._coode_for(CODE_TYPE_BACK_PWD, self.ere_coode.text(), force_enabled=True),
            ),
            "邮箱找回",
            lambda r: self.alert("邮箱找回", r.message() or "系统错误，邮箱验证码找回失败！", r.code),
        )

    def do_back_pass(self) -> None:
        if not self.require_ready():
            return
        c = self.bp_code.text().strip() if self.code_enabled.get(CODE_TYPE_BACK_PWD, True) else ""
        self._run_api(lambda: self.client.back_pass(self.bp_user.text().strip(), self.bp_n1.text().strip(), self.bp_n2.text().strip(), self.bp_q.currentText(), self.bp_a.text().strip(), c), "找回密码", lambda r: self.alert("找回密码", r.message() or "系统错误，找回密码失败！", r.code))

    def do_change_pwd(self) -> None:
        if not self.require_ready():
            return
        self._run_api(
            lambda: self.client.edit_pass(
                self.cp_user.text().strip(),
                self.cp_old.text().strip(),
                self.cp_n1.text().strip(),
                self.cp_n2.text().strip(),
                self._coode_for(CODE_TYPE_BACK_PWD, self.cp_img.text()),
            ),
            "修改密码",
            lambda r: self.alert("修改密码", r.message() or "系统错误，修改密码失败！", r.code),
        )

    def do_feedback(self) -> None:
        if not self.require_ready():
            return
        c = self._coode_for(CODE_TYPE_SAY, self.fb_code.text())
        if self.code_enabled.get(CODE_TYPE_SAY, True) and not c:
            self.alert("意见反馈", "当前留言反馈已开启图形验证码，请先填写验证码。")
            return
        self._run_api(
            lambda: self.client.feedback(
                self.fb_user.text().strip(),
                self.fb_pwd.text().strip(),
                self.fb_title.text().strip(),
                self.fb_contact.text().strip(),
                self.fb_type.currentText(),
                self.fb_text.toPlainText().strip(),
                c,
            ),
            "意见反馈",
            lambda r: self.alert("意见反馈", r.message() or "系统错误，意见反馈失败！", r.code),
        )

    def _after_login(self, r: Any, fallback: str) -> None:
        if r.code in (1011, 9908):
            self.logged_in = True
            self.refresh_status()
            self.alert("登录", "登录成功！" if r.code == 1011 else "使用已经到期！", r.code)
            self.open_console()
        else:
            self.alert("登录失败", r.message() or fallback, r.code)


def main() -> None:
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
    app = QApplication(sys.argv)
    app.setStyleSheet(INPUT_FIELD_QSS)
    w = UserDemoQt()
    w.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
