from __future__ import annotations

import ctypes
import json
import os
import re
import subprocess
import sys
import threading
import time
import urllib.error
import urllib.request
import uuid
import webbrowser
from ctypes import wintypes
from pathlib import Path
from typing import Any, Optional


APP_TITLE_HUAWEI = "\u534e\u4e3a\u4e91\u7ba1\u7406"
APP_VERSION_HUAWEI = "6.18.59"
ALLOW_PLAIN_SOURCE_ENV = "WAWA_ALLOW_HUAWEI_PLAIN_SOURCE"
HUAWEI_STANDALONE_LIMITED_ENV = "WAWA_HUAWEI_STANDALONE_LIMITED"
MANAGER_AUTH_CODE_ENV = "WAWA_MANAGER_AUTH_CODE"
CUSTOMER_MANAGER_BINDING_LINKS: dict[str, str] = {}
HUAWEI_AK_SK_TUTORIAL_URL = "https://www.wawaapi.com/YunTools/Huawei/AKSK"
CUSTOMER_MANAGER_AUTH_API_URL = "https://www.wawaapi.com/api/software/manager-auth"
CUSTOMER_MANAGER_AUTH_USAGE_API_URL = "https://www.wawaapi.com/api/software/manager-auth-usage"
CUSTOMER_MANAGER_AUTH_TIMEOUT_SECONDS = 8
CUSTOMER_MANAGER_AUTH_USAGE_TIMEOUT_SECONDS = 2
CUSTOMER_MANAGER_AUTH_USAGE_HEARTBEAT_INTERVAL_SECONDS = 6 * 60
RUNTIME_UPDATE_POLL_INTERVAL_MS = 18 * 60 * 1000
CURRENT_MANAGER_AUTH_CODE = ""
INVALID_MANAGER_AUTH_MESSAGE = "校验授权码失败，请联系软件获取渠道重新获取授权码。"
MANAGER_AUTH_SERVICE_UNAVAILABLE_MESSAGE = "授权码校验服务暂不可用，请检查网络后重试。"
_manager_auth_usage_sessions: set[tuple[str, str]] = set()
LOCAL_INTERNAL_MANAGER_AUTH_LINKS = {
    "WW": "https://account.huaweicloud.com/obmgr/invitation1/invitation1.html?id=cadb48ee-e215-4ebb-a7af-382232de23b8#/",
    "WT": "https://account.huaweicloud.com/obmgr/invitation/invitation.html?id=75d03aff-b5b4-4218-ac47-6381a46a2550",
    "ND": "https://account.huaweicloud.com/obmgr/invitation/invitation.html?id=6d612c30-f205-4078-86e2-2182130a8917",
}


os.environ.setdefault(HUAWEI_STANDALONE_LIMITED_ENV, "1")


def normalize_manager_auth_code(code: str) -> str:
    return re.sub(r"\s+", "", str(code or "")).upper()


def set_current_manager_auth_code(code: str) -> str:
    global CURRENT_MANAGER_AUTH_CODE
    normalized_code = normalize_manager_auth_code(code)
    CURRENT_MANAGER_AUTH_CODE = normalized_code
    if normalized_code:
        os.environ[MANAGER_AUTH_CODE_ENV] = normalized_code
    else:
        os.environ.pop(MANAGER_AUTH_CODE_ENV, None)
    set_manager_auth_code_for_log_filter(normalized_code)
    return normalized_code


def _authorize_local_internal_manager_auth_code(normalized_code: str) -> bool:
    url = str(LOCAL_INTERNAL_MANAGER_AUTH_LINKS.get(normalized_code) or "").strip()
    if not url.startswith("https://"):
        return False
    CUSTOMER_MANAGER_BINDING_LINKS[normalized_code] = url
    return True


def authorize_manager_auth_code(code: str) -> tuple[bool, str]:
    normalized_code = normalize_manager_auth_code(code)
    if not normalized_code:
        return False, INVALID_MANAGER_AUTH_MESSAGE
    if _authorize_local_internal_manager_auth_code(normalized_code):
        return True, ""

    body = json.dumps(
        {
            "code": normalized_code,
            "package": "huawei",
            "app_version": APP_VERSION_HUAWEI,
        },
        ensure_ascii=False,
    ).encode("utf-8")
    request = urllib.request.Request(
        CUSTOMER_MANAGER_AUTH_API_URL,
        data=body,
        method="POST",
        headers={
            "Content-Type": "application/json; charset=utf-8",
            "Accept": "application/json",
            "User-Agent": "WawaCloudHuaweiAuth/1.0",
        },
    )
    try:
        with urlopen_with_fallback(request, timeout=CUSTOMER_MANAGER_AUTH_TIMEOUT_SECONDS) as response:
            payload = json.loads(response.read().decode("utf-8-sig"))
    except (OSError, ValueError, urllib.error.URLError, urllib.error.HTTPError):
        return False, MANAGER_AUTH_SERVICE_UNAVAILABLE_MESSAGE

    if not isinstance(payload, dict) or not payload.get("ok"):
        message = payload.get("message") if isinstance(payload, dict) else ""
        return False, str(message or MANAGER_AUTH_SERVICE_UNAVAILABLE_MESSAGE)
    if not payload.get("valid"):
        return False, str(payload.get("message") or INVALID_MANAGER_AUTH_MESSAGE)

    raw_links = payload.get("links")
    url = str(raw_links.get("huawei") or "").strip() if isinstance(raw_links, dict) else ""
    if not url.startswith("https://"):
        return False, MANAGER_AUTH_SERVICE_UNAVAILABLE_MESSAGE

    CUSTOMER_MANAGER_BINDING_LINKS[normalized_code] = url
    return True, ""


def _manager_auth_device_id() -> str:
    try:
        from common.runtime_paths import app_data_root

        data_dir = app_data_root()
    except Exception:
        data_dir = Path.home() / "Documents" / "多云管理"
        data_dir.mkdir(parents=True, exist_ok=True)
    path = data_dir / "manager_auth_device_id.txt"
    try:
        existing = re.sub(r"[^0-9a-fA-F-]", "", path.read_text(encoding="utf-8").strip())[:64]
    except Exception:
        existing = ""
    if existing:
        return existing
    device_id = uuid.uuid4().hex
    try:
        path.write_text(device_id + "\n", encoding="utf-8")
    except Exception:
        pass
    return device_id


def _post_manager_auth_usage(code: str, event: str) -> None:
    normalized_code = normalize_manager_auth_code(code)
    if not normalized_code:
        return
    body = json.dumps(
        {
            "code": normalized_code,
            "package": "huawei",
            "app_version": APP_VERSION_HUAWEI,
            "event": str(event or "auth"),
            "device_id": _manager_auth_device_id(),
        },
        ensure_ascii=False,
    ).encode("utf-8")
    request = urllib.request.Request(
        CUSTOMER_MANAGER_AUTH_USAGE_API_URL,
        data=body,
        method="POST",
        headers={
            "Content-Type": "application/json; charset=utf-8",
            "Accept": "application/json",
            "User-Agent": f"WawaCloudHuaweiAuthUsage/{APP_VERSION_HUAWEI}",
        },
    )
    try:
        with urlopen_with_fallback(request, timeout=CUSTOMER_MANAGER_AUTH_USAGE_TIMEOUT_SECONDS):
            pass
    except Exception:
        pass


def start_manager_auth_usage_reporting(code: str) -> None:
    normalized_code = normalize_manager_auth_code(code)
    if not normalized_code:
        return
    key = (normalized_code, "huawei")
    if key in _manager_auth_usage_sessions:
        return
    _manager_auth_usage_sessions.add(key)

    def worker() -> None:
        _post_manager_auth_usage(normalized_code, "auth")
        while True:
            time.sleep(CUSTOMER_MANAGER_AUTH_USAGE_HEARTBEAT_INTERVAL_SECONDS)
            _post_manager_auth_usage(normalized_code, "heartbeat")

    threading.Thread(target=worker, daemon=True).start()


def _desktop_dir() -> Path:
    try:
        desktop = Path.home() / "Desktop"
        if desktop.exists():
            return desktop
    except Exception:
        pass
    return Path.cwd()


def _running_from_frozen_exe() -> bool:
    return bool(getattr(sys, "frozen", False))


def _exe_dir() -> Path:
    try:
        if _running_from_frozen_exe():
            return Path(sys.executable).resolve().parent
    except OSError:
        pass
    return Path.cwd()


def _is_huawei_source_dir(path: Path) -> bool:
    return (
        (path / "main_huawei.py").exists()
        and (path / "HUAWEI" / "app.py").exists()
        and (path / "common" / "shared.py").exists()
    )


def _hot_update_candidates() -> list[Path]:
    candidates: list[Path] = []
    frozen = _running_from_frozen_exe()
    allow_plain_source = os.environ.get(ALLOW_PLAIN_SOURCE_ENV, "").strip() == "1"
    env_source = os.environ.get("WAWA_HOT_UPDATE_SOURCE_DIR", "").strip()
    if env_source and (not frozen or allow_plain_source):
        candidates.append(Path(env_source))
    if not frozen:
        candidates.append(Path(__file__).resolve().parent)
    exe_dir = _exe_dir()
    candidates.append(exe_dir / "source")
    if not frozen or allow_plain_source:
        candidates.append(_desktop_dir() / APP_TITLE_HUAWEI / "source")
    return candidates


def _cleanup_legacy_plain_source() -> None:
    # 华为分包从 6.18.40 起随包保留 source，用于补丁热更新。
    return


def _activate_hot_update_source() -> None:
    for candidate in _hot_update_candidates():
        try:
            source_dir = candidate.resolve()
        except OSError:
            continue
        if not _is_huawei_source_dir(source_dir):
            continue
        source_text = str(source_dir)
        if source_text not in sys.path:
            sys.path.insert(0, source_text)
        os.environ["WAWA_HOT_UPDATE_SOURCE_DIR"] = source_text
        if source_dir.name.lower() == "source":
            portable_root = source_dir.parent
            os.environ.setdefault("WAWA_PORTABLE_ROOT", str(portable_root))
            os.environ.setdefault("WAWA_APP_DATA_DIR", str(portable_root / "data"))
        try:
            os.chdir(source_text)
        except OSError:
            pass
        return


_cleanup_legacy_plain_source()
_activate_hot_update_source()

try:
    from common.package_metadata_fallbacks import ensure_package_metadata_fallbacks

    ensure_package_metadata_fallbacks()
except Exception:
    pass

try:
    from common.codec_fallbacks import ensure_idna_codec

    ensure_idna_codec()
except Exception:
    pass

if len(sys.argv) > 1 and sys.argv[1] == "--task-worker":
    from common.task_worker import main as task_worker_main

    raise SystemExit(task_worker_main(sys.argv[2:]))

if len(sys.argv) > 1 and sys.argv[1] == "--huawei-wwwroot-worker":
    from HUAWEI.source_runner import run_huawei_wwwroot_worker

    raise SystemExit(run_huawei_wwwroot_worker())


import tkinter as tk
from tkinter import font as tkfont
from tkinter import ttk

from common.credential_mark_cache import cached_mark_for_credentials, credential_history_for_cloud, delete_credential_history_record, remember_mark_for_credentials
from common.credential_parser import extract_ak_sk, extract_mark
from common.realtime_log_filter import set_manager_auth_code_for_log_filter
from common.task_manager import TaskProcessManager
from common.update_guard import (
    HUAWEI_DOWNLOAD_PAGE_URL,
    HUAWEI_UPDATE_PACKAGE,
    UPDATE_COUNTDOWN_SECONDS,
    check_latest_version,
    perform_auto_update,
    read_saved_manager_auth_code,
    save_manager_auth_code,
    show_release_announcement_if_needed,
    urlopen_with_fallback,
)
from common.ui_helpers import UI_FONT_FAMILY, UI_LARGE_FONT_FAMILY, apply_window_icon, bind_canvas_mousewheel, build_manager_auth_row, maximize_window, set_windows_app_user_model_id, show_app_message_dialog
from common.user_messages import friendly_popup_text
from common.shared import (
    ACCENT_BG,
    APP_BG,
    BORDER_COLOR,
    BUTTON_ACTIVE_BG,
    CONTENT_BG,
    MUTED_FG,
    PANEL_BG,
    SCROLL_ACTIVE,
    SCROLL_THUMB,
    SCROLL_TROUGH,
    TABLE_BORDER_COLOR,
    TABLE_HEADER_BG,
    TEXT_BG,
    TEXT_FG,
    resource_path,
)
from HUAWEI.app import HuaweiAutoBuyApp


HUAWEI_VENDOR_KEY = "huawei"
HUAWEI_SUBTITLE = "购买搭建、资源查询、重装系统、退订/删除资源"
CREDENTIAL_HISTORY_PLACEHOLDER = "选择历史账号（点击输入搜索）"
NOTICE_PANEL_WIDTH = 760
NOTICE_CARD_BG = "#ffffff"
NOTICE_META_BG = "#f9fafb"
NOTICE_CARD_PAD = 28
NOTICE_CARD_GAP = 14
NOTICE_TITLE_CONTENT_GAP = 6
NOTICE_ITEM_GAP = 5
NOTICE_MAIN_TITLE_FONT = (UI_LARGE_FONT_FAMILY, 20)
NOTICE_SUBTITLE_FONT = (UI_FONT_FAMILY, 11)
NOTICE_SECTION_TITLE_FONT = (UI_FONT_FAMILY, 12, "bold")
NOTICE_CONTENT_FONT = (UI_FONT_FAMILY, 11)
STARTUP_DISCLAIMER_BODY = (
    "本软件仅用于本人或已获授权账号的云资源管理、购买搭建、资源查询、重装系统、退订/删除资源等合法合规用途。\n\n"
    "严禁用于翻墙、诈骗、攻击、滥用云资源、规避监管或其他违法违规行为。"
    "因使用者操作造成的费用、数据丢失、资源释放、业务中断或法律责任，由使用者自行承担。\n\n"
    "下载并使用本软件，表示你已阅读并同意以上内容。"
)
DEFAULT_RELEASE_NOTES_HUAWEI = (
    "新增功能：软件内可直接登录各云账号并自动提取访问密钥AK/SK。",
    "新增功能：华为云、火山云余额显示（请备足服务器费用和流量费，余额不足将自动停机断网！）",
    "优化软件：优化启动检测、欢迎提示、窗口缩放显示和整体稳定性，界面信息更清晰。",
    "推荐最大化全屏使用软件，便于资源管理。",
)


def _compact_notice_lines(text: str) -> list[str]:
    return [line.strip() for line in str(text or "").splitlines() if line.strip()]


def _notice_label(
    parent: tk.Misc,
    text: str,
    font: tuple,
    *,
    justify: str = tk.LEFT,
    anchor: str = "w",
    pady: tuple[int, int] = (0, 0),
    padx: tuple[int, int] = (0, 0),
    bg: str = APP_BG,
    fg: str = TEXT_FG,
    wraplength: int | None = None,
) -> tk.Label:
    label = tk.Label(
        parent,
        text=text,
        bg=bg,
        fg=fg,
        font=font,
        justify=justify,
        anchor=anchor,
        wraplength=wraplength if wraplength is not None else NOTICE_PANEL_WIDTH,
    )
    label.pack(fill=tk.X, padx=padx, pady=pady)
    return label


def _notice_card(parent: tk.Misc, title: str, *, version_text: str = "") -> tk.Frame:
    outer = tk.Frame(parent, bg=BORDER_COLOR)
    outer.pack(fill=tk.X, pady=(0, NOTICE_CARD_GAP))
    panel = tk.Frame(outer, bg=NOTICE_CARD_BG)
    panel.pack(fill=tk.X, padx=1, pady=1)
    content = tk.Frame(panel, bg=NOTICE_CARD_BG)
    content.pack(fill=tk.X, padx=NOTICE_CARD_PAD, pady=NOTICE_CARD_PAD)
    header = tk.Frame(content, bg=NOTICE_CARD_BG)
    header.pack(fill=tk.X, pady=(0, NOTICE_TITLE_CONTENT_GAP))
    _notice_label(
        header,
        title,
        NOTICE_SECTION_TITLE_FONT,
        justify=tk.CENTER,
        anchor="center",
        bg=NOTICE_CARD_BG,
        wraplength=NOTICE_PANEL_WIDTH - (NOTICE_CARD_PAD * 2),
    )
    if version_text:
        _notice_label(
            header,
            version_text,
            (UI_FONT_FAMILY, 10, "bold"),
            justify=tk.CENTER,
            anchor="center",
            pady=(4, 0),
            bg=NOTICE_CARD_BG,
            fg=MUTED_FG,
        )
    return content


def _notice_card_line(parent: tk.Misc, text: str, *, number: int | None = None) -> None:
    row = tk.Frame(parent, bg=NOTICE_CARD_BG)
    row.pack(fill=tk.X, pady=(0, NOTICE_ITEM_GAP))
    text_wrap = NOTICE_PANEL_WIDTH - (NOTICE_CARD_PAD * 2)
    if number is not None:
        tk.Label(
            row,
            text=f"{number}.",
            bg=NOTICE_CARD_BG,
            fg=TEXT_FG,
            font=NOTICE_CONTENT_FONT,
            anchor="ne",
            width=3,
        ).pack(side=tk.LEFT, anchor="n")
        text_wrap -= 34
    tk.Label(
        row,
        text=text,
        bg=NOTICE_CARD_BG,
        fg=TEXT_FG,
        font=NOTICE_CONTENT_FONT,
        justify=tk.LEFT,
        anchor="w",
        wraplength=text_wrap,
    ).pack(side=tk.LEFT, fill=tk.X, expand=True)


def _notice_logo(parent: tk.Misc, filename: str) -> None:
    logo_path = resource_path(filename)
    if not os.path.exists(logo_path):
        return
    try:
        photo = tk.PhotoImage(file=logo_path)
    except tk.TclError:
        return
    label = tk.Label(
        parent,
        image=photo,
        bg=NOTICE_CARD_BG,
        bd=0,
        highlightthickness=0,
        width=78,
        height=78,
    )
    label.pack(side=tk.LEFT, padx=(0, 16))
    setattr(parent, f"_notice_logo_{filename}", photo)


def _notice_meta(parent: tk.Misc, items: list[tuple[str, str]]) -> None:
    row = tk.Frame(parent, bg=NOTICE_CARD_BG)
    row.pack(anchor="center", pady=(14, 0))
    for index, (label_text, value_text) in enumerate(items):
        outer = tk.Frame(row, bg=BORDER_COLOR)
        outer.pack(side=tk.LEFT, padx=(0 if index == 0 else 10, 0))
        box = tk.Frame(outer, bg=NOTICE_META_BG)
        box.pack(padx=1, pady=1)
        tk.Label(box, text=f"{label_text}：", bg=NOTICE_META_BG, fg=MUTED_FG, font=(UI_FONT_FAMILY, 10)).pack(side=tk.LEFT, padx=(12, 0), pady=9)
        tk.Label(box, text=value_text, bg=NOTICE_META_BG, fg=TEXT_FG, font=(UI_FONT_FAMILY, 10, "bold")).pack(side=tk.LEFT, padx=(0, 12), pady=9)


def _notice_welcome_card(parent: tk.Misc, app_title: str, logo_filename: str) -> None:
    outer = tk.Frame(parent, bg=BORDER_COLOR)
    outer.pack(fill=tk.X, pady=(0, NOTICE_CARD_GAP))
    panel = tk.Frame(outer, bg=NOTICE_CARD_BG)
    panel.pack(fill=tk.X, padx=1, pady=1)
    content = tk.Frame(panel, bg=NOTICE_CARD_BG)
    content.pack(fill=tk.X, padx=NOTICE_CARD_PAD, pady=NOTICE_CARD_PAD)
    row = tk.Frame(content, bg=NOTICE_CARD_BG)
    row.pack(anchor="center")
    _notice_logo(row, logo_filename)
    tk.Label(
        row,
        text=f"欢迎使用 {app_title}",
        bg=NOTICE_CARD_BG,
        fg=TEXT_FG,
        font=(UI_LARGE_FONT_FAMILY, 18, "bold"),
        anchor="center",
    ).pack(side=tk.LEFT)


def _outer_window_rect(window: tk.Toplevel | tk.Tk) -> tuple[int, int, int, int] | None:
    if os.name != "nt":
        return None
    handles: list[int] = []
    try:
        frame_id = str(window.tk.call("wm", "frame", window._w))
        if frame_id:
            handles.append(int(frame_id, 0))
    except Exception:
        pass
    try:
        handles.append(int(window.winfo_id()))
    except Exception:
        pass
    for hwnd in handles:
        try:
            rect = wintypes.RECT()
            if ctypes.windll.user32.GetWindowRect(wintypes.HWND(hwnd), ctypes.byref(rect)):  # type: ignore[attr-defined]
                width = rect.right - rect.left
                height = rect.bottom - rect.top
                if width > 0 and height > 0:
                    return rect.left, rect.top, rect.right, rect.bottom
        except Exception:
            continue
    return None


def _window_work_area(window: tk.Toplevel | tk.Tk) -> tuple[int, int, int, int]:
    screen_width = window.winfo_screenwidth()
    screen_height = window.winfo_screenheight()
    if os.name == "nt":
        try:
            rect = wintypes.RECT()
            if ctypes.windll.user32.SystemParametersInfoW(0x0030, 0, ctypes.byref(rect), 0):  # type: ignore[attr-defined]
                width = rect.right - rect.left
                height = rect.bottom - rect.top
                if width > 0 and height > 0:
                    return rect.left, rect.top, width, height
        except Exception:
            pass
    return 0, 0, screen_width, screen_height


def _center_window_on_screen(window: tk.Toplevel | tk.Tk) -> None:
    window.update_idletasks()
    width = max(window.winfo_width(), window.winfo_reqwidth())
    height = max(window.winfo_height(), window.winfo_reqheight())
    screen_width = window.winfo_screenwidth()
    screen_height = window.winfo_screenheight()
    x = max(0, (screen_width - width) // 2)
    y = max(0, (screen_height - height) // 2)
    window.geometry(f"+{x}+{y}")
    window.update_idletasks()
    rect = _outer_window_rect(window)
    if not rect:
        return
    left, top, right, bottom = rect
    outer_width = right - left
    outer_height = bottom - top
    target_left = max(0, (screen_width - outer_width) // 2)
    target_top = max(0, (screen_height - outer_height) // 2)
    adjusted_x = max(0, x + target_left - left)
    adjusted_y = max(0, y + target_top - top)
    window.geometry(f"+{adjusted_x}+{adjusted_y}")


def _startup_notice_content_frame(window: tk.Misc) -> ttk.Frame:
    container = ttk.Frame(window)
    container.pack(fill=tk.BOTH, expand=True)
    container.columnconfigure(0, weight=1)
    container.rowconfigure(0, weight=1)

    canvas = tk.Canvas(container, bg=APP_BG, bd=0, highlightthickness=0)
    v_scrollbar = ttk.Scrollbar(container, orient=tk.VERTICAL, command=canvas.yview)
    h_scrollbar = ttk.Scrollbar(container, orient=tk.HORIZONTAL, command=canvas.xview)
    canvas.grid(row=0, column=0, sticky="nsew")
    v_scrollbar.grid(row=0, column=1, sticky="ns")
    h_scrollbar.grid(row=1, column=0, sticky="ew")

    def yview_changed(first: str, last: str) -> None:
        v_scrollbar.set(first, last)
        if float(first) <= 0.0 and float(last) >= 1.0:
            v_scrollbar.grid_remove()
        else:
            v_scrollbar.grid(row=0, column=1, sticky="ns")

    def xview_changed(first: str, last: str) -> None:
        h_scrollbar.set(first, last)
        if float(first) <= 0.0 and float(last) >= 1.0:
            h_scrollbar.grid_remove()
        else:
            h_scrollbar.grid(row=1, column=0, sticky="ew")

    canvas.configure(yscrollcommand=yview_changed, xscrollcommand=xview_changed)
    frame = ttk.Frame(canvas, padding=20)
    window_id = canvas.create_window((0, 0), window=frame, anchor="nw")

    def refresh_scroll_region(_event: tk.Event | None = None) -> None:
        canvas.configure(scrollregion=canvas.bbox("all"))

    def resize_content(event: tk.Event) -> None:
        target_width = max(frame.winfo_reqwidth(), NOTICE_PANEL_WIDTH + 40)
        canvas.itemconfigure(window_id, width=max(event.width, target_width))
        refresh_scroll_region()

    def on_mousewheel(event: tk.Event) -> str:
        if getattr(event, "state", 0) & 0x0001:
            canvas.xview_scroll(int(-1 * (event.delta / 120)), "units")
        else:
            canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
        return "break"

    frame.bind("<Configure>", refresh_scroll_region)
    canvas.bind("<Configure>", resize_content)
    container.bind("<Enter>", lambda _event: canvas.bind_all("<MouseWheel>", on_mousewheel))
    container.bind("<Leave>", lambda _event: canvas.unbind_all("<MouseWheel>"))
    container.bind("<Destroy>", lambda _event: canvas.unbind_all("<MouseWheel>"), add="+")
    return frame


def _fit_startup_notice_window(window: tk.Toplevel | tk.Tk, content_frame: tk.Misc | None = None) -> None:
    window.update_idletasks()
    work_x, work_y, work_width, work_height = _window_work_area(window)
    margin = 14
    max_width = max(360, work_width - margin * 2)
    max_height = max(320, work_height - margin * 2)
    content_width = content_frame.winfo_reqwidth() if content_frame is not None else 0
    content_height = content_frame.winfo_reqheight() if content_frame is not None else 0
    width = min(max(NOTICE_PANEL_WIDTH + 84, window.winfo_reqwidth(), content_width + 8), max_width)
    height = min(max(520, window.winfo_reqheight(), content_height + 8), max_height)

    for _ in range(2):
        x = work_x + max(margin, (work_width - width) // 2)
        y = work_y + max(margin, (work_height - height) // 2)
        window.geometry(f"{int(width)}x{int(height)}+{int(x)}+{int(y)}")
        window.update_idletasks()
        rect = _outer_window_rect(window)
        if not rect:
            break
        left, top, right, bottom = rect
        overflow_width = max(0, right - (work_x + work_width - margin), (work_x + margin) - left)
        overflow_height = max(0, bottom - (work_y + work_height - margin), (work_y + margin) - top)
        if overflow_width <= 0 and overflow_height <= 0:
            break
        width = max(360, width - overflow_width)
        height = max(320, height - overflow_height)

    window.minsize(min(640, int(width)), min(420, int(height)))


def pack_huawei_startup_notice(parent: tk.Misc) -> None:
    panel = tk.Frame(parent, bg=APP_BG, width=NOTICE_PANEL_WIDTH)
    panel.pack(fill=tk.X)

    notice_card = _notice_card(panel, "免责声明")
    for index, line in enumerate(_compact_notice_lines(STARTUP_DISCLAIMER_BODY), start=1):
        _notice_card_line(notice_card, line, number=index)

    _notice_welcome_card(panel, APP_TITLE_HUAWEI, "logo_app_68.png")

    updates_card = _notice_card(panel, "更新说明", version_text=f"版本 {APP_VERSION_HUAWEI}")
    for index, line in enumerate(DEFAULT_RELEASE_NOTES_HUAWEI, start=1):
        _notice_card_line(updates_card, line, number=index)


def show_startup_notice_window() -> bool:
    result = {"ok": False}
    version_state = {"allowed": False, "download_url": HUAWEI_DOWNLOAD_PAGE_URL, "check_result": None, "checking": False}
    notice_root = tk.Tk()
    notice_root.withdraw()
    notice_root.title("欢迎使用")
    notice_root.resizable(True, True)
    notice_root.attributes("-topmost", True)
    notice_root.configure(bg=APP_BG)
    apply_window_icon(notice_root)
    try:
        style = ttk.Style(notice_root)
        if "vista" in style.theme_names():
            style.theme_use("vista")
        style.configure("TFrame", background=APP_BG)
        style.configure("TLabel", background=APP_BG, foreground=TEXT_FG)
        style.configure("TButton", padding=(8, 5), font=(UI_FONT_FAMILY, 10))
        style.configure("Notice.TButton", padding=(8, 5), font=(UI_FONT_FAMILY, 10))
    except Exception:
        pass

    frame = _startup_notice_content_frame(notice_root)
    pack_huawei_startup_notice(frame)

    auth_code_var = tk.StringVar(value=read_saved_manager_auth_code(HUAWEI_UPDATE_PACKAGE))
    auth_entry = build_manager_auth_row(frame, auth_code_var)

    status_var = tk.StringVar(value="软件版本检测中，请稍候...")
    ttk.Label(
        frame,
        textvariable=status_var,
        font=(UI_FONT_FAMILY, 10),
        wraplength=520,
        justify=tk.CENTER,
        anchor="center",
    ).pack(fill=tk.X, pady=(12, 0))

    buttons = ttk.Frame(frame)
    buttons.pack(anchor="center", pady=(14, 0))
    buttons.columnconfigure(0, weight=1)
    buttons.columnconfigure(1, weight=1)
    buttons.columnconfigure(2, weight=1)
    buttons.columnconfigure(3, weight=1)

    def refresh_agree_button() -> None:
        ready = bool(version_state["allowed"]) and bool(auth_code_var.get().strip())
        agree_button.configure(state="normal" if ready else "disabled")

    def agree() -> None:
        global CURRENT_MANAGER_AUTH_CODE
        if not version_state["allowed"]:
            return
        code = normalize_manager_auth_code(auth_code_var.get())
        valid, auth_message = authorize_manager_auth_code(code)
        if not valid:
            if auth_message == INVALID_MANAGER_AUTH_MESSAGE:
                save_manager_auth_code("", HUAWEI_UPDATE_PACKAGE)
            show_app_message_dialog(notice_root, "授权码校验", auth_message, modal=True)
            auth_entry.focus_set()
            return
        set_current_manager_auth_code(code)
        save_manager_auth_code(code, HUAWEI_UPDATE_PACKAGE)
        start_manager_auth_usage_reporting(code)
        show_release_announcement_if_needed(notice_root, version_state.get("check_result"))
        result["ok"] = True
        notice_root.destroy()

    def cancel() -> None:
        notice_root.destroy()

    def open_download() -> None:
        webbrowser.open(str(version_state.get("download_url") or HUAWEI_DOWNLOAD_PAGE_URL))

    agree_button = ttk.Button(buttons, text="同意并进入", command=agree, style="Notice.TButton", width=14, state="disabled")
    retry_button = ttk.Button(buttons, text="重新检测", command=lambda: start_version_check(), style="Notice.TButton", width=14, state="disabled")
    download_button = ttk.Button(buttons, text="下载最新版", command=open_download, style="Notice.TButton", width=14, state="disabled")
    exit_button = ttk.Button(buttons, text="退出", command=cancel, style="Notice.TButton", width=14)
    agree_button.grid(row=0, column=0, padx=(0, 10))
    exit_button.grid(row=0, column=3)
    retry_button_visible = {"value": False}
    download_button_visible = {"value": False}
    auth_code_var.trace_add("write", lambda *_args: refresh_agree_button())

    def submit_auth_on_enter(_event: tk.Event | None = None) -> str:
        if str(agree_button.cget("state")) != "disabled":
            agree()
        return "break"

    auth_entry.bind("<Return>", submit_auth_on_enter)
    auth_entry.bind("<KP_Enter>", submit_auth_on_enter)

    def show_download_button() -> None:
        download_button.configure(state="normal")
        if not download_button_visible["value"]:
            download_button.grid(row=0, column=2, padx=(0, 10))
            download_button_visible["value"] = True

    def hide_download_button() -> None:
        download_button.configure(state="disabled")
        if download_button_visible["value"]:
            download_button.grid_remove()
            download_button_visible["value"] = False

    def show_retry_button() -> None:
        retry_button.configure(state="normal")
        if not retry_button_visible["value"]:
            retry_button.grid(row=0, column=1, padx=(0, 10))
            retry_button_visible["value"] = True

    def hide_retry_button() -> None:
        retry_button.configure(state="disabled")
        if retry_button_visible["value"]:
            retry_button.grid_remove()
            retry_button_visible["value"] = False

    def relaunch_after_update() -> None:
        if not notice_root.winfo_exists():
            return
        status_var.set("自动更新完成，正在重新打开软件...")
        agree_button.configure(state="disabled")
        hide_retry_button()
        hide_download_button()
        exit_button.configure(state="disabled")

        def relaunch() -> None:
            if not notice_root.winfo_exists():
                return
            try:
                if getattr(sys, "frozen", False):
                    command = [sys.executable]
                    cwd = os.path.dirname(os.path.abspath(sys.executable)) or None
                else:
                    script = Path(__file__).resolve()
                    command = [sys.executable, str(script), *sys.argv[1:]]
                    cwd = str(script.parent)
                subprocess.Popen(command, cwd=cwd, close_fds=True)
            except Exception as exc:
                status_var.set(f"自动更新完成，但自动重新打开失败：{exc}\n请手动重新打开软件。")
                exit_button.configure(state="normal")
                return
            notice_root.destroy()

        notice_root.after(800, relaunch)

    def finish_update_without_restart() -> None:
        version_state["allowed"] = True
        status_var.set("更新完毕，欢迎使用。")
        agree_button.configure(text="进入软件")
        refresh_agree_button()
        hide_retry_button()
        hide_download_button()
        exit_button.configure(state="normal")

    def start_auto_update(check_result: Any) -> None:
        def set_progress(message: str) -> None:
            notice_root.after(0, lambda: status_var.set(message) if notice_root.winfo_exists() else None)

        def worker() -> None:
            update_result = perform_auto_update(check_result, progress=set_progress)

            def apply_update_result() -> None:
                if not notice_root.winfo_exists():
                    return
                if update_result.ok:
                    if update_result.restart_required:
                        relaunch_after_update()
                    else:
                        finish_update_without_restart()
                else:
                    status_var.set(f"{update_result.message}\n请下载最新版运行：{version_state.get('download_url') or HUAWEI_DOWNLOAD_PAGE_URL}")
                    agree_button.configure(state="disabled")
                    show_retry_button()
                    show_download_button()

            notice_root.after(0, apply_update_result)

        threading.Thread(target=worker, daemon=True).start()

    def start_update_countdown(check_result: Any, seconds: int = UPDATE_COUNTDOWN_SECONDS) -> None:
        version_state["download_url"] = check_result.download_page_url or HUAWEI_DOWNLOAD_PAGE_URL
        agree_button.configure(state="disabled")
        hide_retry_button()
        hide_download_button()

        def tick(remaining: int) -> None:
            if not notice_root.winfo_exists():
                return
            if remaining <= 0:
                status_var.set("正在自动更新，请稍候...")
                start_auto_update(check_result)
                return
            status_var.set(f"{check_result.message}\n将在 {remaining} 秒后自动更新，请勿关闭软件。")
            notice_root.after(1000, lambda: tick(remaining - 1))

        tick(seconds)

    def run_version_check() -> None:
        check_result = check_latest_version(APP_VERSION_HUAWEI, package_key=HUAWEI_UPDATE_PACKAGE)

        def apply_check_result() -> None:
            if not notice_root.winfo_exists():
                return
            version_state["checking"] = False
            version_state["allowed"] = check_result.allowed
            version_state["download_url"] = check_result.download_page_url or HUAWEI_DOWNLOAD_PAGE_URL
            version_state["check_result"] = check_result
            if getattr(check_result, "disabled", False):
                status_var.set(check_result.message)
                agree_button.configure(state="disabled")
                hide_retry_button()
                hide_download_button()
            elif check_result.allowed:
                status_var.set("版本检测完成：最新版，欢迎使用!")
                agree_button.configure(text="同意并进入")
                refresh_agree_button()
                hide_retry_button()
                hide_download_button()
            elif check_result.auto_update_available:
                start_update_countdown(check_result)
            else:
                status_var.set(check_result.message)
                agree_button.configure(state="disabled")
                if check_result.error:
                    show_retry_button()
                else:
                    hide_retry_button()
                show_download_button()

        notice_root.after(0, apply_check_result)

    def start_version_check() -> None:
        if version_state["checking"]:
            return
        version_state["checking"] = True
        version_state["allowed"] = False
        status_var.set("正在重新连接更新服务器，请稍候...")
        agree_button.configure(state="disabled")
        hide_retry_button()
        hide_download_button()
        threading.Thread(target=run_version_check, daemon=True).start()

    start_version_check()
    notice_root.protocol("WM_DELETE_WINDOW", cancel)
    notice_root.deiconify()
    _fit_startup_notice_window(notice_root, frame)
    notice_root.lift()
    notice_root.focus_force()
    auth_entry.focus_set()
    notice_root.after(500, lambda: notice_root.attributes("-topmost", False) if notice_root.winfo_exists() else None)
    notice_root.mainloop()
    return bool(result["ok"])


def _scale_launcher_fonts() -> None:
    for name in (
        "TkDefaultFont",
        "TkTextFont",
        "TkFixedFont",
        "TkMenuFont",
        "TkHeadingFont",
        "TkCaptionFont",
        "TkSmallCaptionFont",
        "TkIconFont",
        "TkTooltipFont",
    ):
        try:
            font = tkfont.nametofont(name)
        except Exception:
            continue
        try:
            base_size = int(font.cget("size"))
            font.configure(family=UI_FONT_FAMILY, size=max(11, int(base_size * 1.18)))
        except Exception:
            pass


def _configure_launcher_style() -> None:
    style = ttk.Style()
    try:
        if "vista" in style.theme_names():
            style.theme_use("vista")
    except Exception:
        pass
    style.configure("TFrame", background=CONTENT_BG)
    style.configure("Content.TFrame", background=CONTENT_BG)
    style.configure("TLabel", background=CONTENT_BG, foreground=TEXT_FG)
    style.configure("Header.TLabel", background=CONTENT_BG, foreground=TEXT_FG, font=(UI_LARGE_FONT_FAMILY, 18, "bold"))
    style.configure("HeaderSubtitle.TLabel", background=CONTENT_BG, foreground=MUTED_FG, font=(UI_FONT_FAMILY, 11))
    style.configure("Subheader.TLabel", background=CONTENT_BG, foreground=MUTED_FG, font=(UI_FONT_FAMILY, 10))
    style.configure("TLabelframe", background=CONTENT_BG)
    style.configure("TLabelframe.Label", background=CONTENT_BG, foreground="#344054", font=(UI_FONT_FAMILY, 10, "bold"))
    style.configure("TButton", padding=(8, 5), font=(UI_FONT_FAMILY, 10))
    style.configure("LinkAction.TButton", padding=(8, 4), font=(UI_FONT_FAMILY, 10))
    style.configure("Counter.TButton", padding=(0, 0), font=(UI_LARGE_FONT_FAMILY, 13, "bold"))
    style.configure("CloneWindow.TButton", padding=(0, 0))
    style.configure("TRadiobutton", background=CONTENT_BG, foreground=TEXT_FG)
    style.map("TRadiobutton", background=[("active", CONTENT_BG), ("selected", CONTENT_BG)], foreground=[("active", TEXT_FG), ("selected", TEXT_FG)])
    style.configure("TCheckbutton", background=CONTENT_BG, foreground=TEXT_FG)
    style.map("TCheckbutton", background=[("active", CONTENT_BG), ("selected", CONTENT_BG)], foreground=[("active", TEXT_FG), ("selected", TEXT_FG)])
    style.configure("TNotebook", background=CONTENT_BG, tabmargins=(0, 0, 0, 0))
    style.configure(
        "TNotebook.Tab",
        padding=(37, 18),
        font=(UI_LARGE_FONT_FAMILY, 14, "bold"),
        background=CONTENT_BG,
        foreground=TEXT_FG,
    )
    style.map("TNotebook.Tab", background=[("selected", CONTENT_BG), ("active", BUTTON_ACTIVE_BG)], foreground=[("selected", TEXT_FG), ("active", TEXT_FG)])
    style.configure("Treeview", rowheight=28, font=(UI_FONT_FAMILY, 10), background=TEXT_BG, fieldbackground=TEXT_BG, foreground=TEXT_FG)
    style.configure("Treeview.Heading", font=(UI_FONT_FAMILY, 10), background=TABLE_HEADER_BG, foreground=TEXT_FG, bordercolor=TABLE_BORDER_COLOR)
    style.configure(
        "Grid.Treeview",
        rowheight=28,
        font=(UI_FONT_FAMILY, 10),
        background=TEXT_BG,
        fieldbackground=TEXT_BG,
        foreground=TEXT_FG,
        borderwidth=1,
        relief="solid",
    )
    style.configure(
        "Grid.Treeview.Heading",
        font=(UI_FONT_FAMILY, 10),
        background=TABLE_HEADER_BG,
        foreground=TEXT_FG,
        bordercolor=TABLE_BORDER_COLOR,
        relief="solid",
    )
    for name in ("Vertical.TScrollbar", "Horizontal.TScrollbar", "App.Vertical.TScrollbar", "App.Horizontal.TScrollbar"):
        style.configure(
            name,
            background=SCROLL_THUMB,
            darkcolor=SCROLL_THUMB,
            lightcolor=SCROLL_THUMB,
            troughcolor=SCROLL_TROUGH,
            bordercolor=SCROLL_TROUGH,
            arrowcolor=MUTED_FG,
            relief="flat",
            width=12,
        )
        style.map(
            name,
            background=[("active", SCROLL_ACTIVE), ("pressed", SCROLL_ACTIVE)],
            arrowcolor=[("active", TEXT_FG)],
        )


class HuaweiStandaloneApp(tk.Tk):
    def __init__(self) -> None:
        super().__init__()
        self.title(APP_TITLE_HUAWEI)
        self.geometry("1380x900")
        self.minsize(1024, 640)
        self.configure(bg=APP_BG)
        self.shell_ak_var = tk.StringVar()
        self.shell_sk_var = tk.StringVar()
        self.shell_mark_var = tk.StringVar()
        self.clone_count_var = tk.StringVar(value="1")
        self.page_title_var = tk.StringVar(value=APP_TITLE_HUAWEI)
        self.page_subtitle_var = tk.StringVar(value=HUAWEI_SUBTITLE)
        self.global_status_var = tk.StringVar(value="就绪")
        self._syncing_credentials = False
        self._mark_lookup_job: Optional[str] = None
        self._mark_lookup_token = 0
        self.child_windows: list[dict[str, Any]] = []
        self.credential_history_controls: list[dict[str, Any]] = []
        self._credential_history_popup: Optional[tk.Toplevel] = None
        self._credential_history_outside_bind: Optional[tuple[tk.Misc, str]] = None
        self.subprocess_task_manager = TaskProcessManager()
        self.vendor_buttons: dict[str, tk.Button] = {}
        self.vendor_button_images: dict[str, tk.PhotoImage] = {}
        self.vendor_progressbars: dict[str, tk.Canvas] = {}
        self.vendor_progress_active: dict[str, bool] = {}
        self.vendor_progress_jobs: dict[str, str] = {}
        self.vendor_progress_phase: dict[str, int] = {}
        self.images: dict[str, tk.PhotoImage] = {}
        self._status_poll_job: Optional[str] = None
        self._announcement_poll_job: Optional[str] = None
        self._runtime_update_dialog: Optional[tk.Toplevel] = None
        self._closing = False

        _scale_launcher_fonts()
        _configure_launcher_style()
        apply_window_icon(self)
        self._build_shell()
        self.protocol("WM_DELETE_WINDOW", self.close)
        self.shell_ak_var.trace_add("write", self._on_shell_credentials_changed)
        self.shell_sk_var.trace_add("write", self._on_shell_credentials_changed)
        self.shell_mark_var.trace_add("write", lambda *_args: (self._sync_active_credentials(), self._remember_active_shell_mark()))
        self._load_app_credentials()
        self._maximize_main_window()
        self._schedule_status_poll()
        self._schedule_release_announcement_poll()

    def _maximize_main_window(self) -> None:
        self.after_idle(self._show_initial_window_maximized)

    def _show_initial_window_maximized(self) -> None:
        if self._closing:
            return
        try:
            self.update_idletasks()
            self._set_system_maximized()
            self.update_idletasks()
            self._set_system_maximized()
            self.after_idle(self._force_shell_repaint)
        except tk.TclError:
            pass

    def _set_system_maximized(self) -> None:
        for method_name in ("state", "wm_state"):
            method = getattr(self, method_name, None)
            if not callable(method):
                continue
            try:
                method("zoomed")
                return
            except tk.TclError:
                continue
        try:
            maximize_window(self)
        except tk.TclError:
            pass

    def _force_shell_repaint(self) -> None:
        try:
            self.configure(bg=APP_BG)
            self.update_idletasks()
            if hasattr(self, "page_host"):
                self.page_host.configure(style="Content.TFrame")
                self.page_host.update_idletasks()
            self.update_idletasks()
        except tk.TclError:
            pass

    def _load_photo(self, filename: str) -> Optional[tk.PhotoImage]:
        path = resource_path(filename)
        if not os.path.exists(path):
            return None
        try:
            image = tk.PhotoImage(file=path)
            self.images[filename] = image
            return image
        except tk.TclError:
            return None

    def _build_shell(self) -> None:
        self.columnconfigure(0, weight=0)
        self.columnconfigure(1, weight=1)
        self.rowconfigure(0, weight=1)
        self.rowconfigure(1, weight=0)

        sidebar = tk.Frame(self, bg=PANEL_BG, width=100, highlightthickness=1, highlightbackground=BORDER_COLOR)
        sidebar.grid(row=0, column=0, sticky="ns")
        sidebar.grid_propagate(False)
        sidebar.columnconfigure(0, weight=1)
        sidebar.rowconfigure(0, weight=1)
        sidebar.rowconfigure(2, weight=0)

        vendor_area = tk.Frame(sidebar, bg=PANEL_BG)
        vendor_area.grid(row=0, column=0, sticky="nsew", pady=(18, 0))
        vendor_area.columnconfigure(0, weight=1)
        vendor_area.rowconfigure(0, weight=1)
        vendor_area.rowconfigure(2, weight=1)
        self._add_vendor_button(vendor_area, HUAWEI_VENDOR_KEY, APP_TITLE_HUAWEI, "", "logo_huawei_45.png", 1)

        menu_area = tk.Frame(sidebar, bg=PANEL_BG)
        menu_area.grid(row=2, column=0, sticky="ew", pady=(0, 18))
        menu_area.columnconfigure(0, weight=1)
        self.sidebar_menu_button = tk.Canvas(
            menu_area,
            width=38,
            height=30,
            bg=PANEL_BG,
            highlightthickness=0,
            bd=0,
            cursor="hand2",
        )
        self.sidebar_menu_button.grid(row=0, column=0)
        self._draw_sidebar_menu_icon(PANEL_BG)
        self.sidebar_menu_button.bind("<Button-1>", lambda _event: self._show_sidebar_menu())
        self.sidebar_menu_button.bind("<Enter>", lambda _event: self._draw_sidebar_menu_icon(ACCENT_BG))
        self.sidebar_menu_button.bind("<Leave>", lambda _event: self._draw_sidebar_menu_icon(PANEL_BG))

        main = ttk.Frame(self, style="Content.TFrame", padding=(14, 12, 14, 14))
        main.grid(row=0, column=1, sticky="nsew")
        main.columnconfigure(0, weight=1)
        main.rowconfigure(1, weight=1)

        header = ttk.Frame(main, style="Content.TFrame")
        header.grid(row=0, column=0, sticky="ew", pady=(0, 10))
        header.columnconfigure(0, weight=1)
        header.columnconfigure(1, weight=0)
        header.columnconfigure(2, weight=0)
        title_block = ttk.Frame(header, style="Content.TFrame", padding=(0, 18, 0, 0))
        title_block.grid(row=0, column=0, rowspan=2, sticky="nw")
        ttk.Label(title_block, textvariable=self.page_title_var, style="Header.TLabel").grid(row=0, column=0, sticky="w")
        ttk.Label(title_block, textvariable=self.page_subtitle_var, style="HeaderSubtitle.TLabel").grid(row=1, column=0, sticky="w", pady=(4, 0))

        clone_box = tk.Frame(header, width=195, height=118, bg=CONTENT_BG)
        clone_box.grid(row=0, column=1, rowspan=3, sticky="se", padx=(0, 10))
        clone_box.grid_propagate(False)
        tk.Label(
            clone_box,
            text="新增数量",
            bg=CONTENT_BG,
            fg=TEXT_FG,
            font=(UI_FONT_FAMILY, 10),
        ).place(x=0, y=6, width=195, height=24)
        ttk.Button(
            clone_box,
            text="-",
            command=lambda: self._adjust_clone_count(-1),
            style="Counter.TButton",
        ).place(x=49, y=40, width=24, height=30)
        ttk.Entry(
            clone_box,
            textvariable=self.clone_count_var,
            justify="center",
        ).place(x=73, y=40, width=48, height=30)
        ttk.Button(
            clone_box,
            text="+",
            command=lambda: self._adjust_clone_count(1),
            style="Counter.TButton",
        ).place(x=121, y=40, width=24, height=30)
        ttk.Button(
            clone_box,
            text="新增窗口",
            command=self._open_subwindows,
            style="LinkAction.TButton",
        ).place(x=49, y=88, width=96, height=30)

        self._build_credential_panel(
            header,
            ak_var=self.shell_ak_var,
            sk_var=self.shell_sk_var,
            mark_var=self.shell_mark_var,
            paste_command=self.paste_current_credentials,
        )
        self.binding_link_button, self.binding_tutorial_button = self._build_binding_action_row(
            header,
            row=2,
            column=1,
            columnspan=2,
        )

        page_host = ttk.Frame(main, style="Content.TFrame")
        self.page_host = page_host
        page_host.grid(row=1, column=0, sticky="nsew")
        page_host.columnconfigure(0, weight=1)
        page_host.rowconfigure(0, weight=1)
        self.huawei_app = HuaweiAutoBuyApp(page_host, embedded=True)
        self.huawei_app.subprocess_task_manager = self.subprocess_task_manager
        self.huawei_app.grid(row=0, column=0, sticky="nsew")
        self.huawei_app.status_var.trace_add("write", lambda *_args: self._update_global_status())

        status_bar = tk.Frame(self, bg=PANEL_BG, height=28, highlightthickness=1, highlightbackground=BORDER_COLOR)
        status_bar.grid(row=1, column=0, columnspan=2, sticky="ew")
        status_bar.grid_propagate(False)
        status_bar.columnconfigure(0, weight=1)
        tk.Label(
            status_bar,
            textvariable=self.global_status_var,
            bg=PANEL_BG,
            fg=TEXT_FG,
            anchor="e",
            font=(UI_FONT_FAMILY, 10),
        ).grid(row=0, column=0, sticky="e", padx=16, pady=4)
        self._select_huawei_vendor()

    def _show_sidebar_menu(self) -> None:
        menu = tk.Menu(self, tearoff=0)
        menu.add_command(label="界面更新", command=self._refresh_huawei_view)
        menu.add_separator()
        menu.add_command(label=f"当前版本：{APP_VERSION_HUAWEI}", state="disabled")
        try:
            x = self.sidebar_menu_button.winfo_rootx()
            y = self.sidebar_menu_button.winfo_rooty() + self.sidebar_menu_button.winfo_height()
            menu.tk_popup(x, y)
        finally:
            menu.grab_release()

    def _draw_sidebar_menu_icon(self, background: str) -> None:
        """用画布绘制三横菜单，避免打包后字体缺失导致按钮看不见。"""
        try:
            self.sidebar_menu_button.delete("all")
            self.sidebar_menu_button.configure(bg=background)
            for y in (9, 15, 21):
                self.sidebar_menu_button.create_line(10, y, 28, y, fill=TEXT_FG, width=2, capstyle=tk.ROUND)
        except tk.TclError:
            pass

    def _add_vendor_button(self, sidebar: tk.Frame, key: str, title: str, subtitle: str, logo_name: str, row: int) -> None:
        image = self._load_photo(logo_name)
        box = tk.Frame(sidebar, bg=PANEL_BG)
        box.grid(row=row, column=0, sticky="ew", padx=8, pady=(0, 18))
        box.columnconfigure(0, weight=1)
        button = tk.Button(
            box,
            text=title if not subtitle else f"{title}\n{subtitle}",
            image=image,
            compound=tk.TOP,
            anchor="center",
            justify="center",
            bg=CONTENT_BG,
            fg=TEXT_FG,
            activebackground=ACCENT_BG,
            activeforeground=TEXT_FG,
            relief="flat",
            bd=0,
            highlightthickness=1,
            highlightbackground=BORDER_COLOR,
            padx=2,
            pady=4,
            font=(UI_FONT_FAMILY, 8, "bold"),
            command=self._select_huawei_vendor,
        )
        button.grid(row=0, column=0, sticky="ew")
        progress_slot = tk.Frame(box, bg=PANEL_BG, height=13)
        progress_slot.grid(row=1, column=0, sticky="ew", padx=5, pady=(0, 0))
        progress_slot.grid_propagate(False)
        progress = tk.Canvas(
            progress_slot,
            bg=PANEL_BG,
            highlightthickness=0,
            bd=0,
            width=74,
            height=13,
        )
        self.vendor_buttons[key] = button
        self.vendor_progressbars[key] = progress
        if image is not None:
            self.vendor_button_images[key] = image

    def _draw_round_rect(self, canvas: tk.Canvas, x1: int, y1: int, x2: int, y2: int, radius: int, **kwargs: Any) -> None:
        radius = max(1, min(radius, (x2 - x1) // 2, (y2 - y1) // 2))
        canvas.create_rectangle(x1 + radius, y1, x2 - radius, y2, **kwargs)
        canvas.create_rectangle(x1, y1 + radius, x2, y2 - radius, **kwargs)
        canvas.create_oval(x1, y1, x1 + radius * 2, y1 + radius * 2, **kwargs)
        canvas.create_oval(x2 - radius * 2, y1, x2, y1 + radius * 2, **kwargs)

    def _draw_vendor_progress(self, key: str) -> None:
        progress = self.vendor_progressbars.get(key)
        if progress is None:
            return
        progress.delete("all")
        width = int(progress.winfo_width() or 74)
        height = int(progress.winfo_height() or 13)
        self._draw_round_rect(progress, 1, 1, width - 1, height - 1, 6, fill="#d1d5db", outline="")
        phase = int(self.vendor_progress_phase.get(key, 0))
        cycle = 64
        offset = phase % (cycle * 2)
        ratio = (offset / cycle) if offset <= cycle else ((cycle * 2 - offset) / cycle)
        block_width = max(18, min(24, width // 3))
        block_height = max(7, height - 4)
        travel = max(1, width - block_width - 4)
        x = 2 + ratio * travel
        y = (height - block_height) / 2
        self._draw_round_rect(progress, x, y, x + block_width, y + block_height, 4, fill="#22c55e", outline="")

    def _animate_vendor_progress(self, key: str) -> None:
        if not self.vendor_progress_active.get(key):
            return
        self.vendor_progress_phase[key] = int(self.vendor_progress_phase.get(key, 0)) + 3
        self._draw_vendor_progress(key)
        try:
            job = self.after(35, lambda k=key: self._animate_vendor_progress(k))
            self.vendor_progress_jobs[key] = str(job)
        except tk.TclError:
            pass

    def _set_vendor_progress(self, key: str, active: bool) -> None:
        progress = self.vendor_progressbars.get(key)
        if progress is None:
            return
        if bool(self.vendor_progress_active.get(key)) == bool(active):
            return
        self.vendor_progress_active[key] = bool(active)
        if active:
            self.vendor_progress_phase[key] = 0
            progress.place(relx=0.5, y=0, anchor="n", width=74, height=13)
            self._animate_vendor_progress(key)
        else:
            job = self.vendor_progress_jobs.pop(key, "")
            if job:
                try:
                    self.after_cancel(job)
                except tk.TclError:
                    pass
            progress.delete("all")
            progress.place_forget()

    def _select_huawei_vendor(self) -> None:
        button = self.vendor_buttons.get(HUAWEI_VENDOR_KEY)
        if button is not None:
            button.configure(bg=ACCENT_BG, highlightbackground=BUTTON_ACTIVE_BG)
        self.page_title_var.set(APP_TITLE_HUAWEI)
        self.page_subtitle_var.set(HUAWEI_SUBTITLE)

    def _refresh_huawei_view(self) -> None:
        self._select_huawei_vendor()
        self._update_global_status()

    def _build_binding_action_row(
        self,
        parent: tk.Misc,
        *,
        row: int,
        column: int,
        columnspan: int,
        parent_window: Optional[tk.Misc] = None,
    ) -> tuple[ttk.Button, ttk.Button]:
        action_bar = ttk.Frame(parent, style="Content.TFrame")
        action_bar.grid(row=row, column=column, columnspan=columnspan, sticky="ew", pady=(8, 0))
        action_bar.columnconfigure(0, weight=1, uniform="binding_action")
        action_bar.columnconfigure(1, weight=1, uniform="binding_action")

        link_button = ttk.Button(
            action_bar,
            text="进入华为云注册绑定链接",
            command=lambda: self._open_huawei_binding_link(parent_window),
            style="LinkAction.TButton",
        )
        tutorial_button = ttk.Button(
            action_bar,
            text="查看华为云获取Ak/Sk教程",
            command=lambda: self._show_huawei_aksk_tutorial(parent_window),
            style="LinkAction.TButton",
        )
        link_button.grid(row=0, column=0, sticky="ew", padx=(0, 8))
        tutorial_button.grid(row=0, column=1, sticky="ew", padx=(8, 0))
        return link_button, tutorial_button

    def _current_manager_auth_code(self) -> str:
        code = normalize_manager_auth_code(CURRENT_MANAGER_AUTH_CODE)
        return code if code in CUSTOMER_MANAGER_BINDING_LINKS else ""

    def _open_huawei_binding_link(self, parent: Optional[tk.Misc] = None) -> None:
        code = self._current_manager_auth_code()
        url = CUSTOMER_MANAGER_BINDING_LINKS.get(code, "")
        if not url:
            show_app_message_dialog(parent or self, "授权码校验", INVALID_MANAGER_AUTH_MESSAGE, modal=True)
            return
        webbrowser.open(url)

    def _show_huawei_aksk_tutorial(self, parent: Optional[tk.Misc] = None) -> None:
        if not HUAWEI_AK_SK_TUTORIAL_URL:
            show_app_message_dialog(parent or self, "华为云获取AK/SK教程", "请联系软件获取渠道查看华为云获取 AK/SK 教程。", modal=True)
            return
        webbrowser.open(HUAWEI_AK_SK_TUTORIAL_URL)

    def _build_credential_panel(
        self,
        parent: ttk.Frame,
        *,
        ak_var: tk.StringVar,
        sk_var: tk.StringVar,
        mark_var: tk.StringVar,
        paste_command: Any,
        app: Any = None,
        parent_window: Optional[tk.Misc] = None,
        title_var: Optional[tk.StringVar] = None,
    ) -> None:
        cred = ttk.Frame(parent, style="Content.TFrame")
        cred.grid(row=0, column=2, rowspan=2, sticky="e")
        cred.columnconfigure(1, minsize=488)
        ttk.Label(cred, text="AccessKey ID", style="Subheader.TLabel").grid(row=0, column=0, sticky="w", padx=(0, 6))
        ttk.Entry(cred, textvariable=ak_var, width=68).grid(row=0, column=1, sticky="ew", padx=(0, 10), pady=(0, 4))
        ttk.Label(cred, text="SecurityKey", style="Subheader.TLabel").grid(row=1, column=0, sticky="w", padx=(0, 6))
        ttk.Entry(cred, textvariable=sk_var, width=68).grid(row=1, column=1, sticky="ew", padx=(0, 10), pady=(0, 4))
        ttk.Label(cred, text="User(可编辑)", style="Subheader.TLabel").grid(row=2, column=0, sticky="w", padx=(0, 6))
        self._build_credential_history_row(
            cred,
            app=app,
            parent_window=parent_window or self,
            ak_var=ak_var,
            sk_var=sk_var,
            mark_var=mark_var,
            title_var=title_var,
        )
        paste_box = tk.Frame(cred, width=75, height=88, bg=CONTENT_BG)
        paste_box.grid(row=0, column=2, rowspan=3, sticky="ns")
        paste_box.grid_propagate(False)
        ttk.Button(paste_box, text="识别\n粘贴", command=paste_command).place(x=0, y=0, width=75, height=88)

    def _refresh_credential_history_combo(self, combo: Any, value_var: tk.StringVar) -> None:
        records: dict[str, dict[str, str]] = {}
        try:
            for item in credential_history_for_cloud(HUAWEI_VENDOR_KEY):
                mark = str(item.get("mark") or "").strip()
                ak = str(item.get("ak") or "").strip()
                sk = str(item.get("sk") or "").strip()
                if not mark or not ak or not sk or mark in records:
                    continue
                records[mark] = {"mark": mark, "ak": ak, "sk": sk}
        except Exception:
            records = {}
        try:
            setattr(combo, "_credential_history_records", records)
            if value_var.get().strip() not in records:
                value_var.set(CREDENTIAL_HISTORY_PLACEHOLDER)
        except tk.TclError:
            pass

    def _refresh_credential_history_controls(self) -> None:
        live_controls: list[dict[str, Any]] = []
        for control in self.credential_history_controls:
            combo = control.get("combo")
            value_var = control.get("var")
            if not isinstance(value_var, tk.StringVar):
                continue
            try:
                if combo is None or not combo.winfo_exists():
                    continue
            except tk.TclError:
                continue
            live_controls.append(control)
            self._refresh_credential_history_combo(combo, value_var)
        self.credential_history_controls = live_controls

    def _build_credential_history_row(
        self,
        parent: ttk.Frame,
        *,
        app: Any,
        parent_window: tk.Misc,
        ak_var: tk.StringVar,
        sk_var: tk.StringVar,
        mark_var: tk.StringVar,
        title_var: Optional[tk.StringVar] = None,
    ) -> None:
        row = ttk.Frame(parent, style="Content.TFrame")
        row.grid(row=2, column=1, sticky="ew", padx=(0, 10))
        row.columnconfigure(0, weight=1, minsize=238)
        row.columnconfigure(1, weight=1, minsize=238)
        ttk.Entry(row, textvariable=mark_var, width=34).grid(row=0, column=0, sticky="ew", padx=(0, 6))
        history_var = tk.StringVar()
        history_box = tk.Frame(row, bg=BORDER_COLOR)
        history_box.grid(row=0, column=1, sticky="ew")
        history_box.columnconfigure(0, weight=1)
        history_entry = tk.Entry(
            history_box,
            textvariable=history_var,
            width=31,
            bd=0,
            relief=tk.FLAT,
            bg=TEXT_BG,
            fg=TEXT_FG,
            font=(UI_FONT_FAMILY, 10),
            highlightthickness=0,
        )
        history_entry.grid(row=0, column=0, sticky="ew", padx=(1, 0), pady=1, ipady=3)
        open_command = lambda: self._open_credential_history_popup(
            anchor=history_box,
            history_widget=history_entry,
            value_var=history_var,
            app=app,
            parent_window=parent_window,
            ak_var=ak_var,
            sk_var=sk_var,
            mark_var=mark_var,
            title_var=title_var,
        )
        tk.Button(
            history_box,
            text="\u25be",
            width=3,
            command=open_command,
            bd=0,
            relief=tk.FLAT,
            bg=TEXT_BG,
            fg=MUTED_FG,
            activebackground=BUTTON_ACTIVE_BG,
            activeforeground=TEXT_FG,
            font=(UI_FONT_FAMILY, 9),
            highlightthickness=0,
            takefocus=False,
            cursor="hand2",
        ).grid(row=0, column=1, sticky="ns", padx=(0, 1), pady=1)
        history_entry.bind("<Button-1>", lambda _event: open_command())
        history_entry.bind("<Key>", lambda _event: "break")
        self.credential_history_controls.append({"combo": history_entry, "var": history_var})
        self._refresh_credential_history_combo(history_entry, history_var)

    def _is_widget_inside(self, ancestor: tk.Misc, widget: Optional[tk.Misc]) -> bool:
        while widget is not None:
            if widget is ancestor:
                return True
            try:
                parent_name = widget.winfo_parent()
                if not parent_name:
                    return False
                widget = widget._nametowidget(parent_name)
            except (KeyError, tk.TclError):
                return False
        return False

    def _close_credential_history_popup(self) -> None:
        bind_info = self._credential_history_outside_bind
        self._credential_history_outside_bind = None
        if bind_info is not None:
            bind_widget, bind_id = bind_info
            try:
                bind_widget.unbind("<Button-1>", bind_id)
            except tk.TclError:
                pass
        popup = self._credential_history_popup
        self._credential_history_popup = None
        try:
            if popup is not None and popup.winfo_exists():
                popup.destroy()
        except tk.TclError:
            pass

    def _open_credential_history_popup(
        self,
        *,
        anchor: tk.Misc,
        history_widget: Any,
        value_var: tk.StringVar,
        app: Any,
        parent_window: tk.Misc,
        ak_var: tk.StringVar,
        sk_var: tk.StringVar,
        mark_var: tk.StringVar,
        title_var: Optional[tk.StringVar] = None,
    ) -> None:
        self._refresh_credential_history_combo(history_widget, value_var)
        records = getattr(history_widget, "_credential_history_records", {})
        if not isinstance(records, dict):
            records = {}
        values = list(records.keys())
        self._close_credential_history_popup()

        popup = tk.Toplevel(parent_window)
        self._credential_history_popup = popup
        popup.withdraw()
        popup.overrideredirect(True)
        popup.configure(bg=BORDER_COLOR)
        try:
            popup.transient(parent_window)
        except tk.TclError:
            pass

        popup_border = tk.Frame(popup, bg=BORDER_COLOR)
        popup_border.pack(fill=tk.BOTH, expand=True)
        frame = ttk.Frame(popup_border, style="Content.TFrame", padding=(6, 6, 6, 6))
        frame.pack(fill=tk.BOTH, expand=True, padx=1, pady=1)
        search_row = ttk.Frame(frame, style="Content.TFrame")
        search_row.pack(fill=tk.X)
        tk.Label(search_row, text="搜索：", bg=CONTENT_BG, fg=TEXT_FG, font=(UI_FONT_FAMILY, 10)).pack(side=tk.LEFT, padx=(0, 4))
        search_var = tk.StringVar()
        search_entry = ttk.Entry(search_row, textvariable=search_var)
        search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
        list_frame = ttk.Frame(frame, style="Content.TFrame")
        list_frame.pack(fill=tk.BOTH, expand=True, pady=(6, 0))
        list_frame.columnconfigure(0, weight=1)
        list_frame.rowconfigure(0, weight=1)
        visible_rows = max(3, min(8, len(values) or 3))
        list_canvas = tk.Canvas(
            list_frame,
            height=visible_rows * 28,
            bg=TEXT_BG,
            bd=0,
            highlightthickness=1,
            highlightbackground=BORDER_COLOR,
        )
        list_canvas.grid(row=0, column=0, sticky="nsew")
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=list_canvas.yview)
        scrollbar.grid(row=0, column=1, sticky="ns")
        list_canvas.configure(yscrollcommand=scrollbar.set)
        rows_frame = tk.Frame(list_canvas, bg=TEXT_BG)
        rows_window = list_canvas.create_window((0, 0), window=rows_frame, anchor="nw")
        filtered_values: list[str] = []

        def resize_rows(event: tk.Event) -> None:
            try:
                list_canvas.itemconfigure(rows_window, width=event.width)
            except tk.TclError:
                pass

        def update_scroll_region(*_args: Any) -> None:
            try:
                list_canvas.configure(scrollregion=list_canvas.bbox("all"))
            except tk.TclError:
                pass

        list_canvas.bind("<Configure>", resize_rows)
        rows_frame.bind("<Configure>", update_scroll_region)
        bind_canvas_mousewheel(list_canvas, popup, frame, search_row, search_entry, list_frame, rows_frame)

        def choose_value(selected_value: str) -> None:
            value_var.set(selected_value)
            self._apply_selected_credential_history(
                combo=history_widget,
                value_var=value_var,
                app=app,
                parent_window=parent_window,
                ak_var=ak_var,
                sk_var=sk_var,
                mark_var=mark_var,
                title_var=title_var,
            )
            value_var.set(CREDENTIAL_HISTORY_PLACEHOLDER)
            self._close_credential_history_popup()

        def delete_value(selected_value: str) -> str:
            nonlocal records, values
            record = records.get(selected_value) if isinstance(records, dict) else None
            if not isinstance(record, dict):
                return "break"
            deleted = delete_credential_history_record(
                HUAWEI_VENDOR_KEY,
                str(record.get("mark") or selected_value),
                str(record.get("ak") or ""),
                str(record.get("sk") or ""),
            )
            if not deleted:
                return "break"
            self._refresh_credential_history_combo(history_widget, value_var)
            records = getattr(history_widget, "_credential_history_records", {})
            if not isinstance(records, dict):
                records = {}
            values = list(records.keys())
            value_var.set(CREDENTIAL_HISTORY_PLACEHOLDER)
            self._refresh_credential_history_controls()
            refresh_list()
            return "break"

        def refresh_list(*_args: Any) -> None:
            nonlocal filtered_values
            keyword = search_var.get().strip().lower()
            filtered_values = [item for item in values if keyword in item.lower()]
            for child in rows_frame.winfo_children():
                child.destroy()
            if not filtered_values:
                empty_text = "无匹配账号" if keyword else "暂无历史账号"
                tk.Label(
                    rows_frame,
                    text=empty_text,
                    anchor="w",
                    bg=TEXT_BG,
                    fg=MUTED_FG,
                    font=(UI_FONT_FAMILY, 10),
                ).pack(fill=tk.X, padx=8, pady=6)
                update_scroll_region()
                return
            def draw_delete_icon(canvas: tk.Canvas, hovered: bool = False) -> None:
                bg_color = "#fff1f2" if hovered else "#ffffff"
                border_color = "#fb7185" if hovered else "#ef4444"
                line_color = "#b91c1c" if hovered else "#dc2626"
                canvas.configure(bg=bg_color)
                canvas.delete("all")
                image = tk.PhotoImage(width=19, height=19)
                image.put(bg_color, to=(0, 0, 19, 19))
                for pos in range(19):
                    image.put(border_color, to=(pos, 0, pos + 1, 1))
                    image.put(border_color, to=(pos, 18, pos + 1, 19))
                    image.put(border_color, to=(0, pos, 1, pos + 1))
                    image.put(border_color, to=(18, pos, 19, pos + 1))
                for pos in range(4, 15):
                    image.put(line_color, to=(pos, pos, pos + 1, pos + 1))
                    mirror_x = 18 - pos
                    image.put(line_color, to=(mirror_x, pos, mirror_x + 1, pos + 1))
                canvas.create_image(0, 0, anchor=tk.NW, image=image)
                setattr(canvas, "_delete_icon_image", image)

            def set_row_hover(row_widget: tk.Frame, label_widget: tk.Label, box_widget: tk.Frame, base_bg: str, hovered: bool = False) -> None:
                bg_color = "#eef6ff" if hovered else base_bg
                try:
                    row_widget.configure(bg=bg_color)
                    label_widget.configure(bg=bg_color, fg="#0f172a" if hovered else TEXT_FG)
                    box_widget.configure(bg=bg_color)
                except tk.TclError:
                    pass

            for index, item in enumerate(filtered_values):
                row_bg = TEXT_BG if index % 2 == 0 else "#f8fafc"
                row = tk.Frame(rows_frame, bg=row_bg)
                row.pack(fill=tk.X)
                row.columnconfigure(0, weight=1)
                row.rowconfigure(0, weight=1)
                label = tk.Label(
                    row,
                    text=item,
                    anchor="w",
                    bg=row_bg,
                    fg=TEXT_FG,
                    font=(UI_FONT_FAMILY, 10),
                    padx=6,
                    pady=4,
                    cursor="hand2",
                )
                label.grid(row=0, column=0, sticky="ew")
                delete_box = tk.Frame(row, width=19, height=19, bg=row_bg)
                delete_box.grid(row=0, column=1, sticky="e", padx=(4, 4), pady=0)
                delete_box.grid_propagate(False)
                delete_button = tk.Canvas(
                    delete_box,
                    width=19,
                    height=19,
                    bd=0,
                    highlightthickness=0,
                    bg=row_bg,
                    cursor="hand2",
                )
                draw_delete_icon(delete_button)
                delete_button.bind("<Button-1>", lambda _event, value=item: delete_value(value))
                delete_button.bind("<Enter>", lambda _event, canvas=delete_button: draw_delete_icon(canvas, True))
                delete_button.bind("<Leave>", lambda _event, canvas=delete_button: draw_delete_icon(canvas, False))
                delete_button.place(x=0, y=0, width=19, height=19)
                bind_canvas_mousewheel(list_canvas, row, label, delete_box, delete_button)
                row.bind("<Button-1>", lambda _event, value=item: choose_value(value))
                label.bind("<Button-1>", lambda _event, value=item: choose_value(value))
                row.bind("<Enter>", lambda _event, row_widget=row, label_widget=label, box_widget=delete_box, base_bg=row_bg: set_row_hover(row_widget, label_widget, box_widget, base_bg, True))
                row.bind("<Leave>", lambda _event, row_widget=row, label_widget=label, box_widget=delete_box, base_bg=row_bg: set_row_hover(row_widget, label_widget, box_widget, base_bg, False))
                label.bind("<Enter>", lambda _event, row_widget=row, label_widget=label, box_widget=delete_box, base_bg=row_bg: set_row_hover(row_widget, label_widget, box_widget, base_bg, True))
                label.bind("<Leave>", lambda _event, row_widget=row, label_widget=label, box_widget=delete_box, base_bg=row_bg: set_row_hover(row_widget, label_widget, box_widget, base_bg, False))
            update_scroll_region()

        def choose_selected(*_args: Any) -> None:
            if not filtered_values:
                return
            choose_value(filtered_values[0])

        search_var.trace_add("write", refresh_list)
        search_entry.bind("<Return>", lambda _event: choose_selected())
        search_entry.bind("<Down>", lambda _event: "break")
        popup.bind("<Escape>", lambda _event: self._close_credential_history_popup())

        def close_on_parent_click(event: tk.Event) -> None:
            popup_now = self._credential_history_popup
            if popup_now is None:
                return
            widget = event.widget
            if self._is_widget_inside(popup_now, widget) or self._is_widget_inside(anchor, widget):
                return
            self._close_credential_history_popup()

        def bind_parent_click_guard() -> None:
            popup_now = self._credential_history_popup
            if popup_now is not popup:
                return
            try:
                bind_id = parent_window.bind("<Button-1>", close_on_parent_click, add="+")
                self._credential_history_outside_bind = (parent_window, bind_id)
            except tk.TclError:
                self._credential_history_outside_bind = None

        def focus_search_entry() -> None:
            if self._credential_history_popup is not popup:
                return
            try:
                popup.lift()
                search_entry.focus_set()
                search_entry.focus_force()
                search_entry.icursor(tk.END)
            except tk.TclError:
                pass

        refresh_list()
        try:
            anchor.update_idletasks()
            width = max(anchor.winfo_width(), 260)
            height = 36 + 28 * visible_rows + 18
            x = anchor.winfo_rootx()
            y = anchor.winfo_rooty() + anchor.winfo_height()
            popup.geometry(f"{width}x{height}+{x}+{y}")
            popup.deiconify()
            popup.lift()
            popup.after(30, focus_search_entry)
            popup.after(120, bind_parent_click_guard)
        except tk.TclError:
            self._close_credential_history_popup()

    def _apply_selected_credential_history(
        self,
        *,
        combo: Any,
        value_var: tk.StringVar,
        app: Any,
        parent_window: tk.Misc,
        ak_var: tk.StringVar,
        sk_var: tk.StringVar,
        mark_var: tk.StringVar,
        title_var: Optional[tk.StringVar] = None,
    ) -> None:
        selected = value_var.get().strip()
        records = getattr(combo, "_credential_history_records", {})
        record = records.get(selected) if isinstance(records, dict) else None
        if not isinstance(record, dict):
            self._refresh_credential_history_combo(combo, value_var)
            return
        ak = str(record.get("ak") or "").strip()
        sk = str(record.get("sk") or "").strip()
        mark = str(record.get("mark") or selected).strip()
        if not ak or not sk or not mark:
            return
        target_app = app or getattr(self, "huawei_app", None)
        old_syncing = self._syncing_credentials
        if parent_window is self:
            self._syncing_credentials = True
        try:
            ak_var.set(ak)
            sk_var.set(sk)
            mark_var.set(mark)
        finally:
            if parent_window is self:
                self._syncing_credentials = old_syncing
        if target_app is not None:
            self._set_app_credentials(target_app, ak, sk, mark)
        if title_var is not None:
            title_var.set(self._credential_window_title(ak, mark))
        if isinstance(parent_window, tk.Toplevel):
            parent_window.title(self._credential_window_title(ak, mark))
        self._remember_mark_for_credentials(ak, sk, mark)
        if target_app is not None:
            self._validate_and_read_after_history_selection(target_app, parent_window)

    def _load_app_credentials(self) -> None:
        self._syncing_credentials = True
        try:
            self.shell_ak_var.set(self.huawei_app.ak_var.get())
            self.shell_sk_var.set(self.huawei_app.sk_var.get())
            self.shell_mark_var.set(self.huawei_app.mark_var.get())
        finally:
            self._syncing_credentials = False
        self._refresh_credential_history_controls()

    def _sync_active_credentials(self) -> None:
        if self._syncing_credentials:
            return
        self._set_app_credentials(self.huawei_app, self.shell_ak_var.get().strip(), self.shell_sk_var.get().strip(), self.shell_mark_var.get().strip())

    def _on_shell_credentials_changed(self, *_args: Any) -> None:
        if self._syncing_credentials:
            return
        self._sync_active_credentials()
        self._schedule_mark_lookup(
            app=self.huawei_app,
            ak_var=self.shell_ak_var,
            sk_var=self.shell_sk_var,
            mark_var=self.shell_mark_var,
            parent=self,
        )

    def _show_message(self, message: str) -> None:
        show_app_message_dialog(self, APP_TITLE_HUAWEI, friendly_popup_text(message))

    def _show_credential_message(self, parent: tk.Misc, message: str) -> None:
        show_app_message_dialog(parent, APP_TITLE_HUAWEI, friendly_popup_text(message))

    @staticmethod
    def _set_app_credentials(app: Any, ak: str, sk: str, mark: str = "", *, silent: bool = False) -> None:
        if silent:
            setter = getattr(app, "set_credentials_silently", None)
            if callable(setter):
                setter(ak or "", sk or "", mark or "")
                return
        ak_var = getattr(app, "ak_var", None)
        sk_var = getattr(app, "sk_var", None)
        mark_var = getattr(app, "mark_var", None)
        if isinstance(ak_var, tk.StringVar) and ak_var.get() != (ak or ""):
            ak_var.set(ak or "")
        if isinstance(sk_var, tk.StringVar) and sk_var.get() != (sk or ""):
            sk_var.set(sk or "")
        if isinstance(mark_var, tk.StringVar) and mark_var.get() != (mark or ""):
            mark_var.set(mark or "")

    @staticmethod
    def _force_customer_guard_check(app: Any, delay_ms: int = 0) -> None:
        scheduler = getattr(app, "_schedule_customer_guard_check", None)
        if not callable(scheduler):
            return
        try:
            scheduler(delay_ms=delay_ms, force=True)
        except TypeError:
            try:
                scheduler(delay_ms=delay_ms)
            except TypeError:
                try:
                    scheduler(delay_ms)
                except Exception:
                    pass
            except Exception:
                pass
        except Exception:
            pass

    def _validate_and_read_after_history_selection(self, app: Any, parent: tk.Misc) -> None:
        self._force_customer_guard_check(app, delay_ms=0)
        try:
            parent.after(100, lambda: self._auto_read_resources(app, parent))
        except tk.TclError:
            pass

    @staticmethod
    def _clean_resolved_mark(mark: Any) -> str:
        value = "".join(str(mark or "").split()).strip(" ,;:/\\|，；：、\"'<>[](){}")
        return value[:80]

    @staticmethod
    def _is_low_quality_mark(mark: str) -> bool:
        value = (mark or "").strip()
        if not value or value.isdigit():
            return True
        return bool(re.fullmatch(r"[0-9a-fA-F-]{24,}", value))

    @staticmethod
    def _credentials_ready_for_mark_lookup(ak: str, sk: str) -> bool:
        return len((ak or "").strip()) >= 16 and len((sk or "").strip()) >= 16

    def _remember_mark_for_credentials(self, ak: str, sk: str, mark: str) -> None:
        cleaned = self._clean_resolved_mark(mark)
        if not cleaned or self._is_low_quality_mark(cleaned):
            return
        try:
            remember_mark_for_credentials(HUAWEI_VENDOR_KEY, ak, sk, cleaned)
            self._refresh_credential_history_controls()
        except Exception:
            pass

    def _remember_active_shell_mark(self) -> None:
        if self._syncing_credentials:
            return
        self._remember_mark_for_credentials(
            self.shell_ak_var.get().strip(),
            self.shell_sk_var.get().strip(),
            self.shell_mark_var.get().strip(),
        )

    def _resolve_mark(self, app: Any, ak: str, sk: str) -> str:
        try:
            cached = self._clean_resolved_mark(cached_mark_for_credentials(HUAWEI_VENDOR_KEY, ak, sk))
        except Exception:
            cached = ""
        if cached and not self._is_low_quality_mark(cached):
            return cached
        resolver = getattr(app, "resolve_mark_from_credentials", None)
        if callable(resolver):
            return self._clean_resolved_mark(resolver(ak, sk))
        return ""

    def _note_mark_lookup_result(self, app: Any, message: str) -> None:
        logger = getattr(app, "log", None)
        if callable(logger):
            try:
                logger(message)
            except Exception:
                pass

    def _schedule_mark_lookup(
        self,
        *,
        app: Any,
        ak_var: tk.StringVar,
        sk_var: tk.StringVar,
        mark_var: tk.StringVar,
        parent: tk.Misc,
        delay_ms: int = 800,
    ) -> None:
        ak = ak_var.get().strip()
        sk = sk_var.get().strip()
        if self._mark_lookup_job:
            try:
                parent.after_cancel(self._mark_lookup_job)
            except tk.TclError:
                pass
            self._mark_lookup_job = None
        if not self._credentials_ready_for_mark_lookup(ak, sk):
            return
        self._mark_lookup_token += 1
        token = self._mark_lookup_token

        def start_lookup() -> None:
            self._mark_lookup_job = None

            def worker() -> None:
                try:
                    mark = self._resolve_mark(app, ak, sk)
                except Exception:
                    return
                if not mark:
                    return
                try:
                    parent.after(0, lambda: self._apply_mark_lookup_result(app, ak, sk, mark, token, ak_var, sk_var, mark_var))
                except tk.TclError:
                    pass

            threading.Thread(target=worker, daemon=True).start()

        self._mark_lookup_job = parent.after(delay_ms, start_lookup)

    def _apply_mark_lookup_result(
        self,
        app: Any,
        ak: str,
        sk: str,
        mark: str,
        token: int,
        ak_var: tk.StringVar,
        sk_var: tk.StringVar,
        mark_var: tk.StringVar,
    ) -> None:
        if token != self._mark_lookup_token:
            return
        if ak_var.get().strip() != ak or sk_var.get().strip() != sk:
            return
        cleaned = self._clean_resolved_mark(mark)
        if not cleaned or self._is_low_quality_mark(cleaned):
            return
        if mark_var.get().strip() != cleaned:
            mark_var.set(cleaned)
        self._set_app_credentials(app, ak, sk, cleaned)
        self._remember_mark_for_credentials(ak, sk, cleaned)
        self._note_mark_lookup_result(app, f"已自动识别账号用户名到 User(可编辑)：{cleaned}")

    def paste_current_credentials(self) -> None:
        self._paste_credentials_into_app(
            app=self.huawei_app,
            ak_var=self.shell_ak_var,
            sk_var=self.shell_sk_var,
            mark_var=self.shell_mark_var,
            parent=self,
        )

    def _paste_credentials_into_app(
        self,
        *,
        app: Any,
        ak_var: tk.StringVar,
        sk_var: tk.StringVar,
        mark_var: tk.StringVar,
        parent: tk.Misc,
    ) -> None:
        try:
            text = parent.clipboard_get()
        except tk.TclError:
            text = ""
        if not text:
            self._show_credential_message(parent, "剪贴板没有可识别的 AK/SK。")
            return
        ak, sk = extract_ak_sk(text)
        mark = extract_mark(text, HUAWEI_VENDOR_KEY)
        if not ak or not sk:
            self._show_credential_message(parent, "未识别到完整 AK/SK。")
            return
        prepare = getattr(app, "prepare_for_new_credentials", None)
        if callable(prepare):
            try:
                prepare()
            except Exception:
                pass
        ak_var.set(ak)
        sk_var.set(sk)
        mark_var.set(mark or "")
        self._set_app_credentials(app, ak, sk, mark_var.get())
        if mark:
            self._remember_mark_for_credentials(ak, sk, mark)
        else:
            self._schedule_mark_lookup(app=app, ak_var=ak_var, sk_var=sk_var, mark_var=mark_var, parent=parent, delay_ms=100)
        try:
            parent.after(100, lambda: self._auto_read_resources(app, parent))
        except tk.TclError:
            pass

    def _auto_read_resources(self, app: Any, parent: tk.Misc) -> None:
        try:
            app.query_manage_resources(silent=True)
        except Exception as exc:
            self._show_credential_message(parent, f"自动读取账号资源失败：{exc}")

    def _update_global_status(self) -> None:
        if self._closing:
            return
        detail = self.huawei_app.status_var.get() or "就绪"
        huawei_busy = bool(getattr(self.huawei_app, "busy", False))
        subprocess_count = len(self.subprocess_task_manager._running_tasks())
        self._set_vendor_progress(HUAWEI_VENDOR_KEY, huawei_busy or subprocess_count > 0)
        if subprocess_count:
            detail = f"{detail} | 子进程任务 {subprocess_count} 个"
        self.global_status_var.set(f"{APP_TITLE_HUAWEI} | {detail}")

    def _schedule_status_poll(self) -> None:
        if self._closing:
            return
        self._update_global_status()
        try:
            self._status_poll_job = str(self.after(800, self._schedule_status_poll))
        except tk.TclError:
            self._status_poll_job = None

    def _runtime_update_message(self, check_result: Any) -> str:
        current_version = str(getattr(check_result, "current_version", APP_VERSION_HUAWEI) or APP_VERSION_HUAWEI).strip()
        latest_version = str(getattr(check_result, "latest_version", "") or "").strip()
        lines = [
            "检测到重要更新或功能更新。",
            "本次更新用于优化软件体验并提升稳定性。",
            "当前正在执行的任务不会受到影响，建议在方便时更新后继续使用。",
        ]
        if current_version:
            lines.append(f"当前版本：{current_version}")
        if latest_version and latest_version != "未知":
            lines.append(f"最新版本：{latest_version}")
        return "\n".join(lines)

    def _show_runtime_update_reminder(self, check_result: Any) -> None:
        def open_update() -> None:
            target = (
                str(getattr(check_result, "download_page_url", "") or "").strip()
                or str(getattr(check_result, "download_url", "") or "").strip()
                or HUAWEI_DOWNLOAD_PAGE_URL
            )
            webbrowser.open(target)

        self._runtime_update_dialog = show_app_message_dialog(
            self,
            "软件更新提醒",
            self._runtime_update_message(check_result),
            existing_dialog=self._runtime_update_dialog,
            actions=(
                ("立即更新", open_update),
                ("稍后更新", lambda: None),
            ),
        )

    def _schedule_release_announcement_poll(self, delay_ms: int = RUNTIME_UPDATE_POLL_INTERVAL_MS) -> None:
        if self._closing:
            return

        def poll() -> None:
            self._announcement_poll_job = None
            if self._closing:
                return

            def worker() -> None:
                try:
                    check_result = check_latest_version(APP_VERSION_HUAWEI, package_key=HUAWEI_UPDATE_PACKAGE)
                except Exception:
                    return

                def show() -> None:
                    if self._closing:
                        return
                    if not getattr(check_result, "error", "") and (
                        bool(getattr(check_result, "auto_update_available", False))
                        or bool(getattr(check_result, "needs_update", False))
                    ):
                        self._show_runtime_update_reminder(check_result)
                    else:
                        show_release_announcement_if_needed(self, check_result)

                try:
                    self.after(0, show)
                except tk.TclError:
                    pass

            threading.Thread(target=worker, daemon=True).start()
            self._schedule_release_announcement_poll()

        try:
            self._announcement_poll_job = str(self.after(delay_ms, poll))
        except tk.TclError:
            self._announcement_poll_job = None

    def _adjust_clone_count(self, delta: int) -> None:
        try:
            current = int((self.clone_count_var.get() or "").strip())
        except ValueError:
            current = 1
        self.clone_count_var.set(str(max(1, min(7, current + int(delta)))))

    def _live_child_windows(self) -> list[dict[str, Any]]:
        live: list[dict[str, Any]] = []
        for record in self.child_windows:
            window = record.get("window")
            try:
                if window is not None and window.winfo_exists():
                    live.append(record)
            except Exception:
                pass
        self.child_windows = live
        return live

    def _parse_clone_count(self) -> Optional[int]:
        raw = (self.clone_count_var.get() or "").strip()
        try:
            count = int(raw)
        except ValueError:
            self._show_message("副窗口数量请输入 1 到 7。")
            return None
        if count < 1 or count > 7:
            self._show_message("副窗口数量请输入 1 到 7。")
            return None
        return count

    def _open_subwindows(self) -> None:
        count = self._parse_clone_count()
        if count is None:
            return
        remaining = max(0, 7 - len(self._live_child_windows()))
        if remaining <= 0:
            self._show_message("华为云管理最多同时打开 7 个副窗口。")
            return
        open_count = min(count, remaining)
        if open_count < count:
            self._show_message(f"华为云管理只剩 {remaining} 个副窗口名额，本次打开 {open_count} 个。")
        for _index in range(open_count):
            self._create_subwindow()

    def _create_subwindow(self) -> None:
        window = tk.Toplevel(self)
        window.withdraw()
        window.title(self._credential_window_title())
        window.geometry("1380x900")
        window.minsize(1024, 640)
        window.configure(bg=CONTENT_BG)
        apply_window_icon(window)
        window.columnconfigure(0, weight=1)
        window.rowconfigure(0, weight=1)

        main = ttk.Frame(window, style="Content.TFrame", padding=(14, 12, 14, 14))
        main.grid(row=0, column=0, sticky="nsew")
        main.columnconfigure(0, weight=1)
        main.rowconfigure(1, weight=1)
        title_var = tk.StringVar(value=self._credential_window_title())
        ak_var = tk.StringVar()
        sk_var = tk.StringVar()
        mark_var = tk.StringVar()

        header = ttk.Frame(main, style="Content.TFrame")
        header.grid(row=0, column=0, sticky="ew", pady=(0, 10))
        header.columnconfigure(0, weight=1)
        header.columnconfigure(1, weight=0)
        header.columnconfigure(2, weight=0)
        ttk.Label(header, textvariable=title_var, style="Header.TLabel").grid(row=0, column=0, sticky="w")
        ttk.Label(header, text="同云副窗口：AK/SK、User(可编辑)、日志和结果独立", style="HeaderSubtitle.TLabel").grid(row=1, column=0, sticky="w", pady=(4, 0))

        body = ttk.Frame(main, style="Content.TFrame")
        body.grid(row=1, column=0, sticky="nsew")
        body.columnconfigure(0, weight=1)
        body.rowconfigure(0, weight=1)
        app = HuaweiAutoBuyApp(body, embedded=True)
        app.subprocess_task_manager = self.subprocess_task_manager
        app.grid(row=0, column=0, sticky="nsew")

        self._build_credential_panel(
            header,
            ak_var=ak_var,
            sk_var=sk_var,
            mark_var=mark_var,
            paste_command=lambda: self._paste_credentials_into_app(app=app, ak_var=ak_var, sk_var=sk_var, mark_var=mark_var, parent=window),
            app=app,
            parent_window=window,
            title_var=title_var,
        )
        self._build_binding_action_row(
            header,
            row=2,
            column=0,
            columnspan=3,
            parent_window=window,
        )
        self._bind_subwindow_credentials(app, ak_var, sk_var, mark_var, title_var, window)

        status_var = tk.StringVar(value="就绪")
        app.status_var.trace_add("write", lambda *_args: status_var.set(app.status_var.get()))
        status_bar = tk.Frame(window, bg=PANEL_BG, height=28, highlightthickness=1, highlightbackground=BORDER_COLOR)
        status_bar.grid(row=1, column=0, sticky="ew")
        status_bar.grid_propagate(False)
        status_bar.columnconfigure(0, weight=1)
        tk.Label(status_bar, textvariable=status_var, bg=PANEL_BG, fg=TEXT_FG, anchor="e", font=(UI_FONT_FAMILY, 10)).grid(row=0, column=0, sticky="e", padx=16, pady=4)

        def close_child() -> None:
            try:
                app.shutdown()
            finally:
                try:
                    window.destroy()
                except tk.TclError:
                    pass

        window.protocol("WM_DELETE_WINDOW", close_child)
        self.child_windows.append({"window": window, "app": app})
        try:
            window.update_idletasks()
            window.state("zoomed")
        except tk.TclError:
            try:
                window.deiconify()
                window.state("zoomed")
            except tk.TclError:
                pass
        try:
            window.deiconify()
        except tk.TclError:
            pass

    def _bind_subwindow_credentials(
        self,
        app: Any,
        ak_var: tk.StringVar,
        sk_var: tk.StringVar,
        mark_var: tk.StringVar,
        title_var: tk.StringVar,
        window: tk.Toplevel,
    ) -> None:
        def sync(*_args: Any) -> None:
            self._set_app_credentials(app, ak_var.get().strip(), sk_var.get().strip(), mark_var.get().strip())
            title = self._credential_window_title(ak_var.get().strip(), mark_var.get().strip())
            title_var.set(title)
            window.title(title)
            self._remember_mark_for_credentials(ak_var.get().strip(), sk_var.get().strip(), mark_var.get().strip())

        def keys_changed(*_args: Any) -> None:
            sync()
            self._schedule_mark_lookup(app=app, ak_var=ak_var, sk_var=sk_var, mark_var=mark_var, parent=window)

        ak_var.trace_add("write", keys_changed)
        sk_var.trace_add("write", keys_changed)
        mark_var.trace_add("write", sync)

    @staticmethod
    def _credential_window_title(ak: str = "", mark: str = "") -> str:
        account = (mark or "").strip()
        if not account:
            suffix = (ak or "").strip()[-6:]
            account = f"AK后6位-{suffix}" if suffix else "未粘贴AK/SK"
        return f"华为|{account}"

    def close(self) -> None:
        if self._closing:
            return
        self._closing = True
        if self._announcement_poll_job:
            try:
                self.after_cancel(self._announcement_poll_job)
            except tk.TclError:
                pass
            self._announcement_poll_job = None
        if self._status_poll_job:
            try:
                self.after_cancel(self._status_poll_job)
            except tk.TclError:
                pass
            self._status_poll_job = None
        for job in list(self.vendor_progress_jobs.values()):
            if job:
                try:
                    self.after_cancel(job)
                except tk.TclError:
                    pass
        self.vendor_progress_jobs.clear()
        for record in self._live_child_windows():
            app = record.get("app")
            window = record.get("window")
            try:
                if app is not None:
                    app.shutdown()
            except Exception:
                pass
            try:
                if window is not None and window.winfo_exists():
                    window.destroy()
            except Exception:
                pass
        try:
            self.huawei_app.shutdown()
        finally:
            self.destroy()


def run_self_test() -> None:
    from HUAWEI import app as huawei_app
    from HUAWEI.api import huawei_customer_guard

    assert hasattr(huawei_app, "HuaweiAutoBuyApp")
    assert hasattr(huawei_app.HuaweiAutoBuyApp, "_customer_guard_block_message")
    assert hasattr(huawei_app.HuaweiAutoBuyApp, "_schedule_customer_guard_check")
    assert huawei_app.CUSTOMER_OWNERSHIP_REQUIRED_MESSAGE
    assert huawei_app.huawei_standalone_limited_features()
    limited_view = type("LimitedHuaweiView", (), {
        "_standalone_limited_features": lambda self: True,
        "_current_manager_auth_code": lambda self: "ND",
        "_customer_manage_eip_only": huawei_app.HuaweiAutoBuyApp._customer_manage_eip_only,
    })()
    limited_items = huawei_app.HuaweiAutoBuyApp._manage_display_items(
        limited_view,
        [{"_kind": "ecs"}, {"_kind": "eip"}, {"_kind": "vpc"}],
    )
    assert [item["_kind"] for item in limited_items] == ["ecs", "eip", "vpc"]
    customer_view = type("CustomerHuaweiView", (), {
        "_current_manager_auth_code": lambda self: "KHJLNDHW",
        "_customer_manage_eip_only": huawei_app.HuaweiAutoBuyApp._customer_manage_eip_only,
    })()
    customer_items = huawei_app.HuaweiAutoBuyApp._manage_display_items(
        customer_view,
        [{"_kind": "ecs"}, {"_kind": "eip"}, {"_kind": "vpc"}, {"_kind": "security_group"}],
    )
    assert [item["_kind"] for item in customer_items] == ["eip"]
    test_var = type("SelfTestVar", (), {
        "__init__": lambda self, value: setattr(self, "value", value),
        "get": lambda self: self.value,
        "set": lambda self, value: setattr(self, "value", value),
    })
    limited_purchase_view = type("LimitedHuaweiPurchaseView", (), {
        "_standalone_limited_features": lambda self: True,
        "show_info": lambda self, _message: None,
    })()
    limited_purchase_view.flavor_mode_var = test_var(huawei_app.FLAVOR_MODE_WAWACLOUD_20)
    limited_purchase_view.static_bgp_var = test_var(True)
    assert huawei_app.HuaweiAutoBuyApp._selected_flavor_mode_or_warn(limited_purchase_view) == huawei_app.FLAVOR_MODE_WAWACLOUD_LOW
    assert limited_purchase_view.flavor_mode_var.get() == huawei_app.FLAVOR_MODE_WAWACLOUD_LOW
    assert not huawei_app.HuaweiAutoBuyApp._static_bgp_purchase_enabled(limited_purchase_view)
    assert not limited_purchase_view.static_bgp_var.get()
    assert APP_VERSION_HUAWEI == "6.18.59"
    assert normalize_manager_auth_code(" khjlnd ") == "KHJLND"
    assert CUSTOMER_MANAGER_AUTH_USAGE_HEARTBEAT_INTERVAL_SECONDS == 6 * 60
    assert CUSTOMER_MANAGER_BINDING_LINKS == {}
    valid, message = authorize_manager_auth_code(" nd ")
    assert valid and not message
    assert CUSTOMER_MANAGER_BINDING_LINKS["ND"].startswith("https://")
    CUSTOMER_MANAGER_BINDING_LINKS.clear()
    assert CUSTOMER_MANAGER_BINDING_LINKS == {}
    assert HUAWEI_AK_SK_TUTORIAL_URL == "https://www.wawaapi.com/YunTools/Huawei/AKSK"
    assert HuaweiStandaloneApp._credential_window_title(ak="HPUA123456", mark="w123") == "华为|w123"
    assert callable(huawei_customer_guard.validate_customer_ownership)
    assert HuaweiStandaloneApp._clean_resolved_mark(" user ") == "user"
    print("huawei self-test ok")


def main() -> None:
    from common.portable_runtime import ensure_portable_desktop_shortcut
    from common.process_utils import cleanup_stale_pyinstaller_dirs
    from common.task_runtime import cleanup_stale_task_locks

    cleanup_stale_pyinstaller_dirs()
    cleanup_stale_task_locks()
    set_windows_app_user_model_id()
    ensure_portable_desktop_shortcut(APP_TITLE_HUAWEI)

    if "--self-test" in sys.argv:
        run_self_test()
        return

    if not show_startup_notice_window():
        return

    app = HuaweiStandaloneApp()
    app.mainloop()


if __name__ == "__main__":
    main()
