"""
updater.py — kiểm tra và áp dụng cập nhật tự động cho bản EXE Windows.
"""
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
import traceback
import zipfile
from datetime import datetime
from typing import Callable, Optional
from urllib.parse import urljoin

import requests
from requests.adapters import HTTPAdapter
from PySide6.QtCore import QThread, Qt, QTimer, Signal
from PySide6.QtWidgets import QDialog, QLabel, QMessageBox, QProgressBar, QVBoxLayout
from urllib3.util.retry import Retry

from core.utils import get_app_data_path, get_resource_path


APP_NAME = "INI HD Hợp Đồng & Doanh Thu"
DEFAULT_PACKAGE_URL = "https://update.ini.vn/inihd/update-contract.zip"
DEFAULT_VERSION_URL = "https://update.ini.vn/inihd/version.txt"
VERSION_FILE_NAME = "version.txt"
UPDATE_LOG_FILE_NAME = "update_debug.log"
CHECK_TIMEOUT = (10, 20)
DOWNLOAD_TIMEOUT = (15, 180)


def _default_version_text() -> str:
    return (
        "version=1.0.5\n"
        f"package_url={DEFAULT_PACKAGE_URL}\n"
        "notes=Hỗ trợ cập nhật tự động khi mở phần mềm|Backup và khôi phục dữ liệu local và Google Drive|Lưu metadata hợp đồng để đưa sang Sổ Thu Chi\n"
    )


def _normalize_text(value: str) -> str:
    return str(value or "").replace("\r\n", "\n").replace("\r", "\n").lstrip("\ufeff").strip()


def _decode_response_text(response: requests.Response) -> str:
    content = response.content or b""
    if not content:
        return ""

    for encoding in ("utf-8-sig", "utf-8"):
        try:
            return content.decode(encoding)
        except UnicodeDecodeError:
            continue

    fallback_encoding = response.encoding or getattr(response, "apparent_encoding", None)
    if fallback_encoding:
        try:
            return content.decode(fallback_encoding, errors="replace")
        except LookupError:
            pass

    return response.text


def _get_update_log_path() -> str:
    return get_app_data_path(UPDATE_LOG_FILE_NAME)


def _append_update_log(message: str):
    try:
        log_path = _get_update_log_path()
        directory = os.path.dirname(log_path)
        if directory:
            os.makedirs(directory, exist_ok=True)
        stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        with open(log_path, "a", encoding="utf-8") as file_obj:
            file_obj.write(f"[{stamp}] {message}\n")
    except Exception:
        pass


def _get_ssl_verify_value():
    try:
        import certifi

        bundle_path = certifi.where()
        if bundle_path and os.path.exists(bundle_path):
            return bundle_path
        _append_update_log(f"Không tìm thấy CA bundle từ certifi tại đường dẫn: {bundle_path}")
    except Exception as exc:
        _append_update_log(f"Không lấy được certifi CA bundle: {exc!r}")
    return True


def _build_update_session() -> requests.Session:
    retry = Retry(
        total=2,
        connect=2,
        read=2,
        status=2,
        backoff_factor=0.8,
        status_forcelist=(429, 500, 502, 503, 504),
        allowed_methods=frozenset({"GET"}),
        raise_on_status=False,
    )
    adapter = HTTPAdapter(max_retries=retry)
    session = requests.Session()
    session.mount("https://", adapter)
    session.mount("http://", adapter)
    session.headers.update({"User-Agent": f"INI-HD-Updater/{get_current_version()}"})
    return session


def _ensure_local_version_file() -> str:
    local_path = get_app_data_path(VERSION_FILE_NAME)
    if os.path.exists(local_path):
        return local_path

    bundled_path = get_resource_path(VERSION_FILE_NAME)
    os.makedirs(os.path.dirname(local_path), exist_ok=True)
    if os.path.exists(bundled_path):
        shutil.copy2(bundled_path, local_path)
    else:
        with open(local_path, "w", encoding="utf-8") as file_obj:
            file_obj.write(_default_version_text())
    return local_path


def parse_version_text(text: str, source_url: str = "") -> dict:
    cleaned = _normalize_text(text)
    info = {
        "version": "",
        "package_url": DEFAULT_PACKAGE_URL,
        "notes": "",
        "raw_text": cleaned,
    }
    if not cleaned:
        return info

    if "=" not in cleaned and ":" not in cleaned:
        info["version"] = cleaned.splitlines()[0].strip()
        return info

    lines = cleaned.split("\n")
    index = 0
    while index < len(lines):
        raw_line = lines[index]
        stripped = raw_line.strip()
        index += 1
        if not stripped or stripped.startswith("#"):
            continue

        separator = "=" if "=" in stripped else ":" if ":" in stripped else ""
        if not separator:
            continue

        key, value = stripped.split(separator, 1)
        key = key.strip().lower()
        value = value.strip()

        if key == "notes":
            note_lines = [value] if value else []
            while index < len(lines):
                next_line = lines[index]
                next_stripped = next_line.strip()
                if not next_stripped:
                    note_lines.append("")
                    index += 1
                    continue
                if ("=" in next_stripped or ":" in next_stripped) and not next_line.startswith((" ", "\t", "-", "*")):
                    break
                note_lines.append(next_line.rstrip())
                index += 1
            info["notes"] = "\n".join(part.strip() for part in note_lines if part is not None).strip()
            continue

        if key == "version":
            info["version"] = value
        elif key in {"package_url", "download_url", "url"}:
            resolved = urljoin(source_url or DEFAULT_PACKAGE_URL, value) if value else DEFAULT_PACKAGE_URL
            info["package_url"] = resolved

    if not info["package_url"]:
        info["package_url"] = DEFAULT_PACKAGE_URL
    if info["notes"]:
        info["notes"] = info["notes"].replace("|", "\n")
    return info


def load_local_version_info() -> dict:
    path = _ensure_local_version_file()
    try:
        with open(path, "r", encoding="utf-8") as file_obj:
            info = parse_version_text(file_obj.read(), source_url=DEFAULT_VERSION_URL)
    except Exception:
        info = parse_version_text(_default_version_text(), source_url=DEFAULT_VERSION_URL)
    info["path"] = path
    if not info.get("version"):
        info["version"] = "1.0.5"
    return info


def build_version_text(version: str, package_url: str = DEFAULT_PACKAGE_URL, notes: str = "") -> str:
    lines = [
        f"version={str(version or '1.0.5').strip()}",
        f"package_url={str(package_url or DEFAULT_PACKAGE_URL).strip()}",
        f"notes={_normalize_text(notes).replace(chr(10), '|')}",
        "",
    ]
    return "\n".join(lines)


def get_current_version() -> str:
    return str(load_local_version_info().get("version") or "1.0.5").strip()


def _version_key(version: str) -> tuple[int, ...]:
    numbers = [int(part) for part in re.findall(r"\d+", str(version or ""))]
    return tuple(numbers or [0])


def is_newer_version(remote_version: str, local_version: str) -> bool:
    remote_key = list(_version_key(remote_version))
    local_key = list(_version_key(local_version))
    size = max(len(remote_key), len(local_key))
    remote_key.extend([0] * (size - len(remote_key)))
    local_key.extend([0] * (size - len(local_key)))
    return tuple(remote_key) > tuple(local_key)


def check_remote_version(timeout: int = 6) -> dict:
    local_info = load_local_version_info()
    verify_value = _get_ssl_verify_value()
    try:
        request_timeout = timeout if timeout and timeout > 6 else CHECK_TIMEOUT
        with _build_update_session() as session:
            response = session.get(DEFAULT_VERSION_URL, timeout=request_timeout, verify=verify_value)
            response.raise_for_status()
            response_text = _decode_response_text(response)
    except requests.RequestException as exc:
        log_path = _get_update_log_path()
        _append_update_log(
            "Kiểm tra cập nhật thất bại | "
            f"url={DEFAULT_VERSION_URL} | verify={verify_value!r} | error={exc!r}\n{traceback.format_exc()}"
        )
        return {
            "success": False,
            "network_error": True,
            "message": (
                "Không có mạng hoặc không thể kết nối máy chủ để kiểm tra cập nhật. "
                "Phần mềm sẽ chạy với phiên bản hiện tại.\n\n"
                f"Nhật ký chi tiết: {log_path}"
            ),
            "local": local_info,
            "log_path": log_path,
        }

    remote_info = parse_version_text(response_text, source_url=DEFAULT_VERSION_URL)
    if not remote_info.get("version"):
        _append_update_log(
            "File version.txt trên máy chủ không có phiên bản hợp lệ | "
            f"url={DEFAULT_VERSION_URL} | raw={remote_info.get('raw_text', '')!r}"
        )
        return {
            "success": False,
            "network_error": False,
            "message": "File version.txt trên máy chủ không có thông tin phiên bản hợp lệ.",
            "local": local_info,
            "remote": remote_info,
        }

    remote_info.setdefault("package_url", DEFAULT_PACKAGE_URL)
    return {
        "success": True,
        "network_error": False,
        "message": "OK",
        "local": local_info,
        "remote": remote_info,
        "has_update": is_newer_version(remote_info["version"], local_info["version"]),
    }


def _normalize_extract_root(root_dir: str) -> str:
    current = root_dir
    while os.path.isdir(current):
        entries = [name for name in os.listdir(current) if name not in {"__MACOSX"}]
        files = [name for name in entries if os.path.isfile(os.path.join(current, name))]
        dirs = [name for name in entries if os.path.isdir(os.path.join(current, name))]
        if files or len(dirs) != 1:
            return current
        current = os.path.join(current, dirs[0])
    return root_dir


def stage_update_package(remote_info: dict, progress_callback: Optional[Callable[[int, str], None]] = None) -> dict:
    package_url = str(remote_info.get("package_url") or DEFAULT_PACKAGE_URL).strip() or DEFAULT_PACKAGE_URL
    work_dir = tempfile.mkdtemp(prefix="ini_auto_update_")
    package_path = os.path.join(work_dir, "downloaded_package.bin")
    extracted_dir = os.path.join(work_dir, "payload")
    os.makedirs(extracted_dir, exist_ok=True)
    verify_value = _get_ssl_verify_value()

    def emit(progress: int, message: str):
        if progress_callback:
            progress_callback(progress, message)

    emit(5, "Đang tải gói cập nhật...")
    try:
        with _build_update_session() as session:
            with session.get(package_url, stream=True, timeout=DOWNLOAD_TIMEOUT, verify=verify_value) as response:
                response.raise_for_status()

                total_size = int(response.headers.get("content-length") or 0)
                downloaded = 0
                with open(package_path, "wb") as file_obj:
                    for chunk in response.iter_content(chunk_size=1024 * 128):
                        if not chunk:
                            continue
                        file_obj.write(chunk)
                        downloaded += len(chunk)
                        if total_size > 0:
                            percent = 5 + int((downloaded / total_size) * 60)
                            emit(min(percent, 65), f"Đang tải gói cập nhật... {downloaded // 1024} KB")
    except requests.RequestException as exc:
        log_path = _get_update_log_path()
        _append_update_log(
            "Tải gói cập nhật thất bại | "
            f"url={package_url} | verify={verify_value!r} | error={exc!r}\n{traceback.format_exc()}"
        )
        raise RuntimeError(
            "Không thể tải gói cập nhật từ máy chủ. "
            f"Xem nhật ký chi tiết tại: {log_path}"
        ) from exc

    emit(70, "Đang giải nén gói cập nhật...")
    staging_root = extracted_dir
    if zipfile.is_zipfile(package_path):
        with zipfile.ZipFile(package_path, "r") as zip_obj:
            members = [name for name in zip_obj.namelist() if not name.endswith("/")]
            total_members = max(len(members), 1)
            for index, member in enumerate(members, start=1):
                zip_obj.extract(member, extracted_dir)
                percent = 70 + int((index / total_members) * 15)
                emit(min(percent, 85), f"Đang giải nén: {os.path.basename(member) or member}")
        staging_root = _normalize_extract_root(extracted_dir)
    else:
        target_exe_name = os.path.basename(sys.executable) if getattr(sys, "frozen", False) else "Hợp Đồng INI.exe"
        shutil.copy2(package_path, os.path.join(extracted_dir, target_exe_name))

    version_file = os.path.join(staging_root, VERSION_FILE_NAME)
    if not os.path.exists(version_file):
        with open(version_file, "w", encoding="utf-8") as file_obj:
            file_obj.write(build_version_text(remote_info.get("version", "1.0.5"), package_url, remote_info.get("notes", "")))

    emit(90, "Đã chuẩn bị xong gói cập nhật.")
    return {
        "success": True,
        "work_dir": work_dir,
        "staging_root": staging_root,
        "package_url": package_url,
    }


def _build_staged_file_manifest(staging_root: str) -> list[dict]:
    manifest: list[dict] = []
    if not staging_root or not os.path.isdir(staging_root):
        return manifest

    for current_root, _, files in os.walk(staging_root):
        for file_name in files:
            full_path = os.path.join(current_root, file_name)
            rel_path = os.path.relpath(full_path, staging_root).replace(os.sep, "\\")
            manifest.append({"path": rel_path, "size": os.path.getsize(full_path)})
    return manifest


def _write_windows_updater_script(payload_path: str, script_path: str):
    script = r'''Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$payloadJson = [System.IO.File]::ReadAllText($args[0], [System.Text.Encoding]::UTF8)
$payload = $payloadJson | ConvertFrom-Json

$form = New-Object System.Windows.Forms.Form
$form.Text = "Cập nhật phần mềm"
$form.StartPosition = "CenterScreen"
$form.Width = 520
$form.Height = 170
$form.TopMost = $true
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$form.MaximizeBox = $false
$form.MinimizeBox = $false

$label = New-Object System.Windows.Forms.Label
$label.AutoSize = $false
$label.Width = 470
$label.Height = 42
$label.Left = 20
$label.Top = 18
$label.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$label.Text = "Đang chuẩn bị cập nhật..."

$progress = New-Object System.Windows.Forms.ProgressBar
$progress.Left = 20
$progress.Top = 74
$progress.Width = 470
$progress.Height = 24
$progress.Minimum = 0
$progress.Maximum = 100
$progress.Value = 0

$form.Controls.Add($label)
$form.Controls.Add($progress)

function Set-UiProgress([int]$value, [string]$message) {
    if ($value -lt 0) { $value = 0 }
    if ($value -gt 100) { $value = 100 }
    $progress.Value = $value
    $label.Text = $message
    [System.Windows.Forms.Application]::DoEvents()
}

try {
    $form.Show()
    Set-UiProgress 5 "Đang chờ phần mềm đóng..."
    while (Get-Process -Id $payload.current_pid -ErrorAction SilentlyContinue) {
        Start-Sleep -Milliseconds 250
        [System.Windows.Forms.Application]::DoEvents()
    }

    $files = @(Get-ChildItem -LiteralPath $payload.staging_root -File -Recurse)
    $backupRoot = Join-Path $payload.work_dir "backup"
    New-Item -ItemType Directory -Path $backupRoot -Force | Out-Null
    $total = [Math]::Max($files.Count, 1)
    $index = 0
    foreach ($file in $files) {
        $relative = $file.FullName.Substring($payload.staging_root.Length).TrimStart('\\')
        $destination = Join-Path $payload.target_dir $relative
        $destinationDir = Split-Path -Parent $destination
        if ($destinationDir -and -not (Test-Path -LiteralPath $destinationDir)) {
            New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null
        }
        if (Test-Path -LiteralPath $destination -PathType Leaf) {
            $backupPath = Join-Path $backupRoot $relative
            $backupDir = Split-Path -Parent $backupPath
            if ($backupDir -and -not (Test-Path -LiteralPath $backupDir)) {
                New-Item -ItemType Directory -Path $backupDir -Force | Out-Null
            }
            Copy-Item -LiteralPath $destination -Destination $backupPath -Force
        }
        Copy-Item -LiteralPath $file.FullName -Destination $destination -Force
        $index++
        $percent = 10 + [int](($index / $total) * 85)
        Set-UiProgress $percent ("Đang cập nhật: " + $relative)
    }

    if ($payload.version_text) {
        Set-Content -LiteralPath (Join-Path $payload.target_dir "version.txt") -Value $payload.version_text -Encoding UTF8
    }

    if ($payload.expected_files) {
        foreach ($expected in $payload.expected_files) {
            $expectedPath = [string]$expected.path
            if ([string]::IsNullOrWhiteSpace($expectedPath)) {
                continue
            }
            $targetPath = Join-Path $payload.target_dir $expectedPath
            if (-not (Test-Path -LiteralPath $targetPath -PathType Leaf)) {
                throw ("File cap nhat bi thieu: " + $expectedPath)
            }
            if ($expectedPath -ieq "version.txt") {
                continue
            }
            $actualSize = (Get-Item -LiteralPath $targetPath).Length
            $expectedSize = [Int64]$expected.size
            if ($expectedSize -gt 0 -and $actualSize -ne $expectedSize) {
                throw ("File cap nhat sai kich thuoc: " + $expectedPath)
            }
        }
    }

    Set-UiProgress 100 "Cập nhật hoàn tất. Đang mở lại phần mềm..."
    $notes = [string]$payload.notes
    if ([string]::IsNullOrWhiteSpace($notes)) {
        $notes = "Không có ghi chú phiên bản mới."
    }
    [System.Windows.Forms.MessageBox]::Show(
        "Đã cập nhật thành công lên phiên bản " + $payload.new_version + ".`n`nTính năng mới:`n" + $notes,
        "Cập nhật thành công",
        [System.Windows.Forms.MessageBoxButtons]::OK,
        [System.Windows.Forms.MessageBoxIcon]::Information
    ) | Out-Null
    Start-Sleep -Milliseconds 800
    Start-Process -FilePath $payload.restart_exe -WorkingDirectory $payload.target_dir
}
catch {
    try {
        if ($backupRoot -and (Test-Path -LiteralPath $backupRoot)) {
            $backupFiles = @(Get-ChildItem -LiteralPath $backupRoot -File -Recurse)
            foreach ($backupFile in $backupFiles) {
                $relative = $backupFile.FullName.Substring($backupRoot.Length).TrimStart('\\')
                $restoreDestination = Join-Path $payload.target_dir $relative
                $restoreDir = Split-Path -Parent $restoreDestination
                if ($restoreDir -and -not (Test-Path -LiteralPath $restoreDir)) {
                    New-Item -ItemType Directory -Path $restoreDir -Force | Out-Null
                }
                Copy-Item -LiteralPath $backupFile.FullName -Destination $restoreDestination -Force
            }
        }
    }
    catch {
    }
    [System.Windows.Forms.MessageBox]::Show(
        "Cập nhật thất bại: " + $_.Exception.Message,
        "Lỗi cập nhật",
        [System.Windows.Forms.MessageBoxButtons]::OK,
        [System.Windows.Forms.MessageBoxIcon]::Error
    ) | Out-Null
}
finally {
    try {
        Remove-Item -LiteralPath $payload.work_dir -Recurse -Force -ErrorAction SilentlyContinue
    }
    catch {
    }
    $form.Close()
}
'''
    with open(script_path, "w", encoding="utf-16") as file_obj:
        file_obj.write(script)


def launch_windows_updater(staging_result: dict, remote_info: dict) -> dict:
    if not getattr(sys, "frozen", False):
        return {
            "success": False,
            "message": "Tự động cập nhật chỉ hoạt động trên bản EXE đã build.",
        }

    exe_path = os.path.abspath(sys.executable)
    target_dir = os.path.dirname(exe_path)
    work_dir = str(staging_result.get("work_dir") or tempfile.mkdtemp(prefix="ini_apply_update_"))
    payload_path = os.path.join(work_dir, "update_payload.json")
    script_path = os.path.join(work_dir, "apply_update.ps1")
    payload = {
        "current_pid": os.getpid(),
        "staging_root": os.path.abspath(str(staging_result.get("staging_root") or "")),
        "target_dir": target_dir,
        "restart_exe": exe_path,
        "new_version": str(remote_info.get("version") or "").strip(),
        "notes": _normalize_text(str(remote_info.get("notes") or "")),
        "version_text": build_version_text(remote_info.get("version", "1.0.5"), remote_info.get("package_url", DEFAULT_PACKAGE_URL), remote_info.get("notes", "")),
        "expected_files": _build_staged_file_manifest(str(staging_result.get("staging_root") or "")),
        "work_dir": work_dir,
    }
    with open(payload_path, "w", encoding="utf-8-sig") as file_obj:
        json.dump(payload, file_obj, ensure_ascii=False, indent=2)
    _write_windows_updater_script(payload_path, script_path)

    subprocess.Popen(
        [
            "powershell.exe",
            "-NoProfile",
            "-ExecutionPolicy",
            "Bypass",
            "-WindowStyle",
            "Hidden",
            "-File",
            script_path,
            payload_path,
        ],
        close_fds=True,
        creationflags=getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0),
    )
    return {"success": True, "message": "Updater đã được khởi chạy."}


class UpdateWorker(QThread):
    progress_changed = Signal(int)
    status_changed = Signal(str)
    done = Signal(dict)

    def __init__(self, remote_info: dict, parent=None):
        super().__init__(parent)
        self.remote_info = dict(remote_info or {})

    def run(self):
        try:
            staging = stage_update_package(self.remote_info, self._emit_progress)
            launch_result = launch_windows_updater(staging, self.remote_info)
            if not launch_result.get("success"):
                self.done.emit({"success": False, "message": launch_result.get("message") or "Không thể chạy updater."})
                return
            self.progress_changed.emit(100)
            self.status_changed.emit("Đã chuyển sang trình cập nhật. Phần mềm sẽ tự đóng để hoàn tất.")
            result = {"success": True, "message": "Đang khởi chạy updater.", "restart_required": True}
            result.update(staging)
            self.done.emit(result)
        except Exception as exc:
            self.done.emit({"success": False, "message": str(exc)})

    def _emit_progress(self, progress: int, message: str):
        self.progress_changed.emit(progress)
        self.status_changed.emit(message)


class AutoUpdateDialog(QDialog):
    def __init__(self, remote_info: dict, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Đang cập nhật phần mềm")
        self.setModal(True)
        self.setWindowFlag(Qt.WindowContextHelpButtonHint, False)
        self.setWindowFlag(Qt.WindowCloseButtonHint, False)
        self.resize(460, 140)
        self.update_started = False
        self.restart_requested = False
        self.remote_info = dict(remote_info or {})

        layout = QVBoxLayout(self)
        layout.setContentsMargins(18, 18, 18, 18)
        layout.setSpacing(12)

        version_text = f"Đang tải bản cập nhật {self.remote_info.get('version', '').strip()}..."
        self.lbl_title = QLabel(version_text)
        self.lbl_title.setWordWrap(True)
        self.lbl_status = QLabel("Đang chuẩn bị tải gói cập nhật...")
        self.lbl_status.setWordWrap(True)

        self.progress = QProgressBar()
        self.progress.setRange(0, 100)
        self.progress.setValue(0)
        self.progress.setTextVisible(True)

        layout.addWidget(self.lbl_title)
        layout.addWidget(self.lbl_status)
        layout.addWidget(self.progress)

        self.worker = UpdateWorker(self.remote_info, self)
        self.worker.progress_changed.connect(self._on_progress_changed)
        self.worker.status_changed.connect(self.lbl_status.setText)
        self.worker.done.connect(self._on_done)
        QTimer.singleShot(0, self.worker.start)

    def _on_progress_changed(self, value: int):
        if value < 0:
            self.progress.setRange(0, 0)
            return
        if self.progress.maximum() == 0:
            self.progress.setRange(0, 100)
        self.progress.setValue(max(0, min(value, 100)))

    def _on_done(self, result: dict):
        if result.get("success"):
            self.restart_requested = bool(result.get("restart_required"))
            self.accept()
            return
        QMessageBox.critical(self, "Lỗi cập nhật", result.get("message") or "Không thể cập nhật phần mềm.")
        self.reject()


def run_startup_update_flow() -> str:
    if not getattr(sys, "frozen", False):
        return "skip"

    check_result = check_remote_version()
    if not check_result.get("success"):
        if check_result.get("network_error"):
            QMessageBox.information(
                None,
                "Cập nhật tự động",
                check_result.get("message") or "Không có mạng hoặc không thể kết nối máy chủ để kiểm tra cập nhật.",
            )
        else:
            QMessageBox.warning(
                None,
                "Cập nhật tự động",
                check_result.get("message") or "Không thể đọc thông tin cập nhật từ máy chủ.",
            )
        return "continue"

    if not check_result.get("has_update"):
        return "continue"

    dialog = AutoUpdateDialog(check_result["remote"])
    if dialog.exec() == QDialog.DialogCode.Accepted and dialog.restart_requested:
        return "restart"
    return "continue"
