import json
import requests
from PySide6.QtWidgets import (
    QDialog, QVBoxLayout, QHBoxLayout, QFrame, QLabel, QLineEdit,
    QCheckBox, QPushButton, QMessageBox, QComboBox, QGraphicsDropShadowEffect,
    QTextEdit, QApplication
)
from PySide6.QtGui import QColor

TK_SDK_PYTHON_VERSION = "v0.3.0"


class LicenseAuthClient:
    """Python 客户端可复用授权组件（配置/公告/校验）。"""

    def __init__(self, config_file, machine_code_provider):
        self.config_file = config_file
        self.machine_code_provider = machine_code_provider
        self._verify_reason_map = {
            "SOFTWARE_ID_REQUIRED": "软件ID缺失，请检查配置",
            "SOFTWARE_ID_INVALID": "软件ID格式非法，请填写数值ID",
            "SOFTWARE_NOT_FOUND": "软件ID不存在，请联系管理员确认",
            "SOFTWARE_MISMATCH": "卡密与软件不匹配，请核对软件ID",
            "SOFTWARE_SCOPE_MISMATCH": "卡密与当前软件不匹配，请切换到正确的软件后重试。",
            "INVALID_LICENSE": "卡密无效，请检查后重试",
            "NOT_STARTED": "卡密尚未生效",
            "EXPIRED": "卡密已过期，请续费后再试",
            "MACHINE_CODE_REQUIRED": "新用户专享卡密需要设备码校验，请在客户端绑定设备后重试。",
            "NEW_USER_ALREADY_USED": "该设备已使用过新用户专享卡密，不可再次使用。",
            "NEW_USER_DEVICE_MISMATCH": "新用户专享卡密已绑定其他设备，当前设备不可使用。",
            "DEVICE_MISMATCH": "设备不匹配，该卡密已绑定其他设备",
            "REDEEM_SUBJECT_REQUIRED": "积分卡兑换需要兑换主体或设备码",
            "POINTS_ALREADY_REDEEMED": "积分卡已被其他账号或设备兑换",
        }
        self._trial_reason_map = {
            "TRIAL_MACHINE_CODE_REQUIRED": "缺少机器码",
            "TRIAL_DISABLED": "当前软件未开启免费试用",
            "TRIAL_ALREADY_USED": "该设备已注册过试用卡密",
            "TRIAL_KEY_CONFLICT": "卡密生成冲突，请重试",
            "TRIAL_SOFTWARE_NOT_FOUND": "软件不存在或已删除",
        }

    def load_config(self, require_license_key=False):
        if not self.config_file:
            return None, "未设置配置文件路径"
        try:
            with open(self.config_file, "r", encoding="utf-8") as f:
                cfg = json.load(f)
            app_settings = cfg.get("app_settings", {})
            server_url = (app_settings.get("server_url") or "").strip().rstrip("/")
            license_key = (app_settings.get("license_key") or "").strip()
            software_id_raw = app_settings.get("software_id")
            if software_id_raw is None:
                return None, "缺少 software_id，请在 app_settings 中配置"
            software_id = int(software_id_raw)
        except (ValueError, TypeError):
            return None, "software_id 必须是数值 ID"
        except Exception as e:
            return None, f"读取授权配置失败: {e}"

        if not server_url:
            return None, "缺少 server_url，请在 app_settings 中配置"
        if require_license_key and not license_key:
            return None, "缺少 license_key，请在 app_settings 中配置"
        if software_id <= 0:
            return None, "software_id 必须大于 0"

        return {
            "server_url": server_url,
            "license_key": license_key,
            "software_id": software_id
        }, None

    def save_license_key(self, license_key):
        try:
            with open(self.config_file, "r", encoding="utf-8") as f:
                cfg = json.load(f)
            app_settings = cfg.setdefault("app_settings", {})
            app_settings["license_key"] = (license_key or "").strip()
            with open(self.config_file, "w", encoding="utf-8") as f:
                json.dump(cfg, f, ensure_ascii=False, indent=4)
            return True
        except Exception:
            return False

    def fetch_notice_policy(self, conf):
        url = f"{(conf.get('server_url') or '').rstrip('/')}/api/software-notice"
        params = {"software_id": conf.get("software_id")}
        resp = requests.get(url, params=params, timeout=8)
        if resp.status_code != 200:
            return {"success": False}
        data = resp.json()
        return data if isinstance(data, dict) else {"success": False}

    def _map_verify_fail_message(self, data, machine_code):
        reason = (data.get("reason_code") or "").strip()
        base = data.get("message") or self._verify_reason_map.get(reason) or "授权校验未通过"
        if reason == "EXPIRED":
            return f"{base}\n\n请购买普通卡密或联系管理员续期。"
        if reason == "INVALID_LICENSE":
            return f"{base}\n\n请检查卡密是否输入正确，或联系管理员确认卡密状态。"
        if reason == "NOT_STARTED":
            return f"{base}\n\n请等待生效时间后再试，或联系管理员调整生效时间。"
        if reason == "MACHINE_CODE_REQUIRED":
            return f"{base}\n\n请在当前设备重新登录后重试；如多次失败，请联系管理员协助核查设备码。"
        if reason == "NEW_USER_ALREADY_USED":
            used_license = data.get("license_key") or "未知"
            used_device = data.get("device_id") or machine_code or "未知"
            return (
                f"{base}\n\n历史卡密: {used_license}\n设备码: {used_device}"
                "\n下一步：请改用普通卡密，或联系管理员核查历史记录。"
            )
        if reason == "NEW_USER_DEVICE_MISMATCH":
            used_license = data.get("license_key") or "未知"
            used_device = data.get("device_id") or "未知"
            current_device = machine_code or "未知"
            return (
                f"{base}\n\n卡密: {used_license}\n已绑定设备码: {used_device}"
                f"\n当前设备码: {current_device}\n下一步：请改用普通卡密，或联系管理员核对后处理。"
            )
        if reason == "DEVICE_MISMATCH":
            return f"{base}\n\n下一步：请在原绑定设备上登录，或联系管理员处理后再试。"
        if reason == "SOFTWARE_SCOPE_MISMATCH":
            return f"{base}\n\n下一步：请确认当前软件与卡密所属软件一致，或联系管理员核对 software_id 配置。"
        if reason == "REDEEM_SUBJECT_REQUIRED":
            return f"{base}\n\n下一步：请先登录账号或提供稳定设备码后再兑换积分卡。"
        if reason == "POINTS_ALREADY_REDEEMED":
            redeemed_at = data.get("redeemed_at") or "未知"
            return f"{base}\n\n兑换时间: {redeemed_at}\n下一步：请确认是否已在其他账号兑换，或联系管理员核查兑换记录。"
        if reason in self._verify_reason_map:
            return self._verify_reason_map[reason]
        return f"{base}\n\n请稍后重试；若持续失败，请联系管理员并提供设备码。"

    def _format_verify_success_message(self, data, update_mode="remind", latest_version="", update_url=""):
        if (data.get("license_mode") or "time") == "points":
            points = int(data.get("points_amount") or 0)
            redeemed_at = data.get("redeemed_at") or "-"
            msg = f"{data.get('message') or '积分卡兑换成功'}\n兑换积分: {points}\n兑换时间: {redeemed_at}"
        else:
            msg = f"卡密验证成功。\n有效期至: {data.get('end_date') or '-'}"
            if data.get("remaining_days") is not None:
                msg += f"\n剩余时间: {data.get('remaining_days')}天"
        if latest_version:
            msg += f"\n最新版本: {latest_version}"
        msg += "\n更新方式: 自动更新" if update_mode == "auto" else "\n更新方式: 提醒更新"
        if update_url:
            msg += f"\n更新地址: {update_url}"
        return msg

    def _map_trial_fail_message(self, data, machine_code):
        reason = (data.get("reason_code") or "").strip()
        base = data.get("message") or self._trial_reason_map.get(reason) or "试用失败，请稍后重试。"
        if reason == "TRIAL_MACHINE_CODE_REQUIRED":
            return f"{base}\n\n请重新登录后重试；如仍失败，请联系管理员核查设备码。"
        if reason == "TRIAL_DISABLED":
            return f"{base}\n\n当前软件暂未开放试用，请购买普通卡密或联系管理员。"
        if reason == "TRIAL_ALREADY_USED":
            lk = data.get("license_key") or "未知"
            end_date = data.get("end_date") or "未知"
            did = data.get("device_id") or machine_code or "未知"
            return (
                f"{base}\n\n历史试用卡密: {lk}\n到期时间: {end_date}\n设备码: {did}"
                "\n下一步：请直接使用该卡密，或购买普通卡密。"
            )
        if reason == "TRIAL_KEY_CONFLICT":
            return f"{base}\n\n请稍后重试；如连续失败，请联系管理员处理。"
        if reason == "TRIAL_SOFTWARE_NOT_FOUND":
            return f"{base}\n\n下一步：请检查软件配置是否已删除或 software_id 是否正确。"
        return f"{base}\n\n请稍后重试；若持续失败，请联系管理员并提供设备码。"

    def apply_update_policy(self, server_mode, selected_mode):
        mode = (server_mode or "optional").strip()
        value = (selected_mode or "remind").strip()
        if mode == "force":
            return {
                "value": "auto",
                "disabled": True,
                "hint": "当前软件要求强制自动更新，暂不支持切换为提醒更新。"
            }
        if mode == "remind_only":
            return {
                "value": "remind",
                "disabled": True,
                "hint": "当前软件仅支持提醒更新，暂不支持自动更新。"
            }
        return {
            "value": value,
            "disabled": False,
            "hint": "当前软件允许用户自选更新方式。"
        }

    def verify(self, conf):
        verify_url = f"{(conf.get('server_url') or '').rstrip('/')}/api/verify"
        payload = {
            "software_id": conf.get("software_id"),
            "license_key": (conf.get("license_key") or "").strip(),
            "machine_code": self.machine_code_provider()
        }
        payload["redeem_subject"] = (conf.get("redeem_subject") or payload["machine_code"] or "").strip()
        try:
            resp = requests.post(verify_url, json=payload, timeout=8)
        except requests.RequestException as e:
            return False, f"授权服务不可用: {e}", None

        if resp.status_code != 200:
            return False, f"授权服务返回异常状态码: {resp.status_code}", None

        try:
            data = resp.json()
        except Exception:
            return False, "授权服务返回了非 JSON 数据", None

        if not data.get("valid"):
            return False, self._map_verify_fail_message(data, payload["machine_code"]), None

        runtime = {
            "server_url": conf.get("server_url"),
            "software_id": conf.get("software_id"),
            "license_key": conf.get("license_key"),
            "device_id": payload["machine_code"],
            "update_mode": conf.get("update_mode", "remind"),
            "license_mode": data.get("license_mode") or "time",
            "end_date": data.get("end_date") or "",
            "remaining_days": data.get("remaining_days"),
            "points_amount": data.get("points_amount") or 0,
            "points_redeemed": bool(data.get("points_redeemed")),
            "redeemed_at": data.get("redeemed_at") or "",
            "verify_result": data
        }
        msg = self._format_verify_success_message(
            data,
            update_mode=conf.get("update_mode", "remind"),
            latest_version=conf.get("latest_version", ""),
            update_url=conf.get("update_url", "")
        )
        return True, msg, runtime

    def create_trial(self, conf):
        trial_url = f"{(conf.get('server_url') or '').rstrip('/')}/api/trial"
        machine_code = self.machine_code_provider()
        payload = {
            "software_id": conf.get("software_id"),
            "machine_code": machine_code
        }
        try:
            resp = requests.post(trial_url, json=payload, timeout=8)
        except requests.RequestException as e:
            return False, f"授权服务不可用: {e}", None

        if resp.status_code != 200:
            return False, f"授权服务返回异常状态码: {resp.status_code}", None

        try:
            data = resp.json()
        except Exception:
            return False, "授权服务返回了非 JSON 数据", None

        if not data.get("success"):
            return False, self._map_trial_fail_message(data, machine_code), None

        return True, data.get("message") or "试用开通成功", {
            "server_url": conf.get("server_url"),
            "software_id": conf.get("software_id"),
            "license_key": data.get("license_key") or "",
            "end_date": data.get("end_date") or "",
            "device_id": machine_code,
            "update_mode": conf.get("update_mode", "remind"),
            "from_trial": True,
            "trial_result": data
        }


class LicenseLoginDialog(QDialog):
    """可复用卡密登录弹窗（PySide 版，风格对齐 Web SDK）。"""

    def __init__(self, auth_client: LicenseAuthClient, base_conf, parent=None):
        super().__init__(parent)
        self.auth_client = auth_client
        self.base_conf = dict(base_conf or {})
        self.license_runtime = None
        self.server_update_mode = "optional"
        self.latest_version = ""
        self.update_url = ""
        self.trial_enabled = False
        self.trial_days = 0

        self.setWindowTitle("卡密登录")
        self.setModal(True)
        self.setFixedWidth(480)
        self.setObjectName("licenseDialog")
        self.setStyleSheet("""
            QDialog#licenseDialog {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
                    stop:0 #eef2ff, stop:1 #f5f3ff);
            }
            QFrame#card {
                background: #ffffff;
                border: 1px solid #e5e7eb;
                border-radius: 14px;
            }
            QFrame#header {
                border-top-left-radius: 14px;
                border-top-right-radius: 14px;
                background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
                    stop:0 #4f46e5, stop:1 #7c3aed);
            }
            QLabel#headerTitle {
                color: #ffffff;
                font-size: 15px;
                font-weight: 700;
            }
            QLabel#headerSubTitle {
                color: rgba(255, 255, 255, 0.92);
                font-size: 12px;
            }
            QLabel#headerSdkVersion {
                color: rgba(255, 255, 255, 0.88);
                font-size: 11px;
                font-weight: 600;
            }
            QPushButton#headerCloseBtn {
                border: 1px solid rgba(255, 255, 255, 0.45);
                background: rgba(255, 255, 255, 0.12);
                color: #ffffff;
                border-radius: 8px;
                padding: 2px 10px;
                font-size: 12px;
                font-weight: 600;
                min-width: 44px;
                height: 26px;
            }
            QLabel#noticeBox {
                border: 1px solid #e5e7eb;
                border-radius: 10px;
                background: #f8fafc;
                padding: 10px 12px;
                color: #1f2937;
                font-size: 13px;
                min-height: 64px;
            }
            QLabel#sectionTitle {
                color: #111827;
                font-size: 13px;
                font-weight: 700;
            }
            QLineEdit#licenseInput, QComboBox#updateModeInput {
                height: 40px;
                border: 1px solid #d1d5db;
                border-radius: 8px;
                padding: 0 10px;
                font-size: 13px;
                background: #ffffff;
            }
            QLabel#updateHint {
                color: #6b7280;
                font-size: 12px;
            }
            QCheckBox#rememberCheck {
                color: #374151;
                font-size: 12px;
            }
            QFrame#resultPanel {
                border: 1px solid #d1d5db;
                border-radius: 10px;
                background: #f8fafc;
            }
            QTextEdit#resultText {
                border: none;
                background: transparent;
                color: #111827;
                font-size: 12px;
            }
            QPushButton#copyResultBtn {
                min-width: 72px;
                height: 26px;
                border: 1px solid #d1d5db;
                border-radius: 8px;
                background: #ffffff;
                color: #374151;
                font-weight: 600;
            }
            QPushButton#cancelBtn {
                min-width: 90px;
                height: 40px;
                border: 1px solid #d1d5db;
                border-radius: 8px;
                background: #ffffff;
                color: #374151;
                font-weight: 600;
            }
            QPushButton#loginBtn {
                min-width: 120px;
                height: 40px;
                border: none;
                border-radius: 8px;
                background: #4f46e5;
                color: #ffffff;
                font-weight: 700;
            }
            QPushButton#trialBtn {
                min-width: 96px;
                height: 40px;
                border: 1px solid #d1d5db;
                border-radius: 8px;
                background: #ffffff;
                color: #374151;
                font-weight: 600;
            }
        """)
        self._build_ui()
        self._load_notice_and_policy()

    def _build_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(16, 16, 16, 16)
        layout.setSpacing(0)

        card = QFrame()
        card.setObjectName("card")
        shadow = QGraphicsDropShadowEffect(self)
        shadow.setBlurRadius(35)
        shadow.setOffset(0, 10)
        shadow.setColor(QColor(79, 70, 229, 45))
        card.setGraphicsEffect(shadow)
        card_layout = QVBoxLayout(card)
        card_layout.setContentsMargins(0, 0, 0, 0)
        card_layout.setSpacing(0)

        header = QFrame()
        header.setObjectName("header")
        header_layout = QHBoxLayout(header)
        header_layout.setContentsMargins(14, 12, 14, 12)
        title_wrap = QVBoxLayout()
        title = QLabel("卡密验证")
        title.setObjectName("headerTitle")
        sub_title = QLabel(f"software_id: {self.base_conf.get('software_id')}")
        sub_title.setObjectName("headerSubTitle")
        sdk_ver = QLabel(f"TK_SDK_Python {TK_SDK_PYTHON_VERSION}")
        sdk_ver.setObjectName("headerSdkVersion")
        sdk_ver.setToolTip("内置 SDK 版本标识")
        title_wrap.addWidget(title)
        title_wrap.addWidget(sub_title)
        title_wrap.addWidget(sdk_ver)
        header_layout.addLayout(title_wrap)
        header_layout.addStretch(1)
        close_btn = QPushButton("关闭")
        close_btn.setObjectName("headerCloseBtn")
        close_btn.clicked.connect(self.reject)
        header_layout.addWidget(close_btn)
        card_layout.addWidget(header)

        body = QFrame()
        body_layout = QVBoxLayout(body)
        body_layout.setContentsMargins(16, 14, 16, 16)
        body_layout.setSpacing(10)

        notice_title = QLabel("系统公告")
        notice_title.setObjectName("sectionTitle")
        body_layout.addWidget(notice_title)
        self.notice_label = QLabel("正在加载公告...")
        self.notice_label.setObjectName("noticeBox")
        self.notice_label.setWordWrap(True)
        body_layout.addWidget(self.notice_label)

        body_layout.addWidget(QLabel("卡密"))
        self.license_input = QLineEdit()
        self.license_input.setObjectName("licenseInput")
        self.license_input.setPlaceholderText("请输入卡密（例如：XXXX-XXXX-XXXX-XXXX）")
        self.license_input.setText((self.base_conf.get("license_key") or "").strip())
        body_layout.addWidget(self.license_input)

        body_layout.addWidget(QLabel("更新方式"))
        self.update_mode_input = QComboBox()
        self.update_mode_input.setObjectName("updateModeInput")
        self.update_mode_input.addItem("提醒更新", "remind")
        self.update_mode_input.addItem("自动更新", "auto")
        self.update_mode_input.currentIndexChanged.connect(self._on_update_mode_changed)
        body_layout.addWidget(self.update_mode_input)

        self.update_hint_label = QLabel("")
        self.update_hint_label.setObjectName("updateHint")
        self.update_hint_label.setWordWrap(True)
        body_layout.addWidget(self.update_hint_label)

        self.remember_checkbox = QCheckBox("记住卡密（下次自动填充，但仍需手动点登录）")
        self.remember_checkbox.setObjectName("rememberCheck")
        self.remember_checkbox.setChecked(bool((self.base_conf.get("license_key") or "").strip()))
        body_layout.addWidget(self.remember_checkbox)

        self.result_panel = QFrame()
        self.result_panel.setObjectName("resultPanel")
        result_layout = QVBoxLayout(self.result_panel)
        result_layout.setContentsMargins(10, 8, 10, 10)
        result_head = QHBoxLayout()
        result_title = QLabel("校验结果")
        result_title.setObjectName("sectionTitle")
        self.copy_result_btn = QPushButton("复制")
        self.copy_result_btn.setObjectName("copyResultBtn")
        self.copy_result_btn.clicked.connect(self._copy_result)
        result_head.addWidget(result_title)
        result_head.addStretch(1)
        result_head.addWidget(self.copy_result_btn)
        result_layout.addLayout(result_head)
        self.result_text = QTextEdit()
        self.result_text.setObjectName("resultText")
        self.result_text.setReadOnly(True)
        self.result_text.setFixedHeight(92)
        result_layout.addWidget(self.result_text)
        self.result_panel.setVisible(False)
        body_layout.addWidget(self.result_panel)

        actions = QHBoxLayout()
        actions.addStretch(1)
        cancel_btn = QPushButton("取消")
        cancel_btn.setObjectName("cancelBtn")
        self.trial_btn = QPushButton("免费试用")
        self.trial_btn.setObjectName("trialBtn")
        self.trial_btn.setVisible(False)
        login_btn = QPushButton("登录")
        login_btn.setObjectName("loginBtn")
        cancel_btn.clicked.connect(self.reject)
        self.trial_btn.clicked.connect(self._on_trial)
        login_btn.clicked.connect(self._on_login)
        actions.addWidget(cancel_btn)
        actions.addWidget(self.trial_btn)
        actions.addWidget(login_btn)
        body_layout.addLayout(actions)

        card_layout.addWidget(body)
        layout.addWidget(card)

    def _set_result(self, message, is_error=False):
        self.result_text.setPlainText(message or "")
        self.result_panel.setVisible(True)
        if is_error:
            self.result_panel.setStyleSheet("QFrame#resultPanel { border-color: #fecaca; background: #fef2f2; }")
        else:
            self.result_panel.setStyleSheet("QFrame#resultPanel { border-color: #bbf7d0; background: #f0fdf4; }")

    def _copy_result(self):
        text = self.result_text.toPlainText()
        if text.strip():
            QApplication.clipboard().setText(text)

    def _show_copyable_message(self, title, message, is_error=False):
        dlg = QDialog(self)
        dlg.setWindowTitle(title)
        dlg.setModal(True)
        dlg.setFixedWidth(460)
        layout = QVBoxLayout(dlg)
        label = QLabel(title)
        label.setObjectName("sectionTitle")
        editor = QTextEdit()
        editor.setReadOnly(True)
        editor.setPlainText(message or "")
        editor.setFixedHeight(150)
        actions = QHBoxLayout()
        copy_btn = QPushButton("复制内容")
        close_btn = QPushButton("确定")
        copy_btn.clicked.connect(lambda: QApplication.clipboard().setText(editor.toPlainText()))
        close_btn.clicked.connect(dlg.accept)
        actions.addStretch(1)
        actions.addWidget(copy_btn)
        actions.addWidget(close_btn)
        layout.addWidget(label)
        layout.addWidget(editor)
        layout.addLayout(actions)
        if is_error:
            editor.setStyleSheet("border: 1px solid #fecaca; background: #fef2f2;")
        dlg.exec()

    def _current_update_mode(self):
        return self.update_mode_input.currentData() or "remind"

    def _set_update_mode(self, mode):
        for idx in range(self.update_mode_input.count()):
            if self.update_mode_input.itemData(idx) == mode:
                self.update_mode_input.setCurrentIndex(idx)
                return

    def _apply_update_policy(self):
        policy = self.auth_client.apply_update_policy(
            self.server_update_mode, self._current_update_mode()
        )
        self._set_update_mode(policy["value"])
        self.update_mode_input.setDisabled(bool(policy["disabled"]))
        self.update_hint_label.setText(policy["hint"])

    def _on_update_mode_changed(self, _index):
        self._apply_update_policy()

    def _load_notice_and_policy(self):
        try:
            data = self.auth_client.fetch_notice_policy(self.base_conf)
            if data.get("success"):
                self.notice_label.setText((data.get("notice_text") or "").strip() or "暂无公告")
                self.server_update_mode = (data.get("update_mode") or "optional").strip()
                self.latest_version = (data.get("latest_version") or "").strip()
                self.update_url = (data.get("update_url") or "").strip()
                self.trial_enabled = bool(data.get("trial_enabled"))
                self.trial_days = int(data.get("trial_days") or 0)
            else:
                self.notice_label.setText("暂无公告")
                self.server_update_mode = "optional"
                self.trial_enabled = False
        except Exception:
            self.notice_label.setText("公告加载失败，请检查网络或服务状态。")
            self.server_update_mode = "optional"
            self.trial_enabled = False
        self.trial_btn.setVisible(bool(self.trial_enabled))
        self.trial_btn.setEnabled(bool(self.trial_enabled))
        self._apply_update_policy()

    def _on_trial(self):
        if not self.trial_enabled:
            QMessageBox.warning(self, "提示", "当前软件未开启免费试用")
            return
        conf = dict(self.base_conf)
        conf["update_mode"] = self._current_update_mode()
        ok, msg, runtime = self.auth_client.create_trial(conf)
        if not ok:
            self._set_result(msg, True)
            return
        license_key = runtime.get("license_key") or ""
        if license_key:
            self.license_input.setText(license_key)
        if self.remember_checkbox.isChecked():
            self.auth_client.save_license_key(license_key)
        else:
            self.auth_client.save_license_key("")
        runtime["latest_version"] = self.latest_version
        runtime["update_url"] = self.update_url
        self.license_runtime = runtime
        self._set_result(msg, False)
        self._show_copyable_message("试用开通成功", msg)
        self.accept()

    def _on_login(self):
        license_key = self.license_input.text().strip()
        if not license_key:
            QMessageBox.warning(self, "提示", "请输入卡密")
            return
        conf = dict(self.base_conf)
        conf["license_key"] = license_key
        conf["update_mode"] = self._current_update_mode()
        conf["latest_version"] = self.latest_version
        conf["update_url"] = self.update_url
        ok, msg, runtime = self.auth_client.verify(conf)
        if not ok:
            self._set_result(msg, True)
            return
        if self.remember_checkbox.isChecked():
            self.auth_client.save_license_key(license_key)
        else:
            self.auth_client.save_license_key("")
        runtime["latest_version"] = self.latest_version
        runtime["update_url"] = self.update_url
        self.license_runtime = runtime
        self._set_result(msg, False)
        self._show_copyable_message("授权校验成功", msg)
        self.accept()
