#!/usr/bin/env python3
import argparse
import html
import json
import os
import pathlib
import platform
import shutil
import stat
import subprocess
import sys
import time
from typing import Optional


ROOT = pathlib.Path(__file__).resolve().parents[1]
SCHEMA = "field_replay.install_report.v1"
# Pruned at EVERY depth (genuine junk anywhere in the tree).
ALWAYS_IGNORED_DIRS = {".git", ".pytest_cache", "__pycache__"}
# Pruned ONLY at the source root: these are build outputs at the top level, but a legitimately
# nested directory named "build"/"out" deeper in the tree must NOT be dropped from the install.
TOP_LEVEL_IGNORED_DIRS = {"build", "out"}
IGNORED_SUFFIXES = {".pyc", ".pyo"}


def default_install_dir() -> pathlib.Path:
    system = platform.system()
    home = pathlib.Path.home()
    if system == "Windows":
        base = os.environ.get("LOCALAPPDATA")
        return pathlib.Path(base).expanduser() / "BestPerf" if base else home / "AppData" / "Local" / "BestPerf"
    if system == "Darwin":
        return home / "Applications" / "BestPerf"
    return home / ".local" / "share" / "BestPerf"


def default_desktop_dir() -> pathlib.Path:
    override = os.environ.get("FIELDREPLAY_DESKTOP_DIR", "").strip()
    if override:
        return pathlib.Path(override).expanduser()
    if platform.system() == "Windows":
        return default_windows_desktop_dir()
    return pathlib.Path.home() / "Desktop"


def default_windows_desktop_dir() -> pathlib.Path:
    shell_desktop = windows_shell_desktop_dir()
    if shell_desktop is not None:
        return shell_desktop
    for env_name in ["OneDriveCommercial", "OneDriveConsumer", "OneDrive"]:
        value = os.environ.get(env_name, "").strip()
        if value:
            return pathlib.Path(value).expanduser() / "Desktop"
    user_profile = os.environ.get("USERPROFILE", "").strip()
    if user_profile:
        return pathlib.Path(user_profile).expanduser() / "Desktop"
    return pathlib.Path.home() / "Desktop"


def windows_shell_desktop_dir() -> Optional[pathlib.Path]:
    try:
        proc = subprocess.run([
            "powershell.exe",
            "-NoLogo",
            "-NoProfile",
            "-NonInteractive",
            "-ExecutionPolicy",
            "Bypass",
            "-Command",
            # Force the child PowerShell to emit UTF-8 WITHOUT a BOM so a non-ASCII (e.g. Chinese)
            # username in the Desktop path isn't mangled by the console's default OEM/GBK code page.
            "[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding $false; "
            "[Environment]::GetFolderPath('Desktop')",
        ], text=True, capture_output=True, timeout=5, encoding="utf-8-sig", errors="replace")
    except (OSError, subprocess.SubprocessError):
        return None
    if proc.returncode != 0:
        return None
    for line in proc.stdout.splitlines():
        value = line.strip().lstrip("﻿")
        if value:
            candidate = pathlib.Path(value).expanduser()
            # A mis-decoded path won't exist -> fall through to the env-based fallback (decoded by
            # Python's Windows wide-char API) rather than returning a corrupted directory.
            return candidate if candidate.exists() else None
    return None


def default_out_dir(root: pathlib.Path) -> pathlib.Path:
    return pathlib.Path(os.environ.get("FIELDREPLAY_INSTALL_OUT", str(root))).expanduser().resolve()


def make_executable(path: pathlib.Path) -> None:
    mode = path.stat().st_mode
    path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)


def copy_release_tree(source: pathlib.Path, destination: pathlib.Path) -> None:
    source = source.resolve()
    destination = destination.resolve()
    if source == destination:
        return
    if destination.exists():
        shutil.rmtree(destination)

    def ignore(_directory, names):
        # build/out are pruned only at the source root; .git/__pycache__/etc. at any depth.
        at_root = pathlib.Path(_directory).resolve() == source
        ignored = []
        for name in names:
            path = pathlib.Path(name)
            if (path.suffix in IGNORED_SUFFIXES
                    or name in ALWAYS_IGNORED_DIRS
                    or (at_root and name in TOP_LEVEL_IGNORED_DIRS)):
                ignored.append(name)
        return ignored

    shutil.copytree(source, destination, ignore=ignore)


def launcher_specs() -> list[tuple[str, str, str]]:
    system = platform.system()
    if system == "Windows":
        return [
            ("main", "1 启动 BestPerf.cmd", "BestPerf.bat"),
            # 小白用户不用懂防火墙:双击这个编号入口会自动请求一次 Windows 授权(点"是"),
            # 开放"被别人测向本机"所需的入站端口。只有当别人要测向这台机器时才需要点它。
            ("firewall", "2 开放网络权限 BestPerf.cmd", "BestPerf Fix UDP.bat"),
        ]
    if system == "Darwin":
        return [
            ("main", "1 启动 BestPerf.command", "BestPerf.command"),
        ]
    return [
        ("main", "1 启动 BestPerf.sh", "BestPerf.sh"),
    ]


def desktop_entry_paths(desktop_dir: pathlib.Path) -> dict[str, pathlib.Path]:
    return {role: desktop_dir / entry_name for role, entry_name, _target_name in launcher_specs()}


def installed_launcher_paths(install_dir: pathlib.Path) -> dict[str, pathlib.Path]:
    return {role: install_dir / target_name for role, _entry_name, target_name in launcher_specs()}


def write_posix_desktop_entries(desktop_dir: pathlib.Path, install_dir: pathlib.Path, install_report_path: pathlib.Path) -> dict[str, pathlib.Path]:
    desktop_dir.mkdir(parents=True, exist_ok=True)
    entries = {}
    for role, entry_name, target_name in launcher_specs():
        entry = desktop_dir / entry_name
        entry.write_text(
            "#!/usr/bin/env bash\n"
            "set -e\n"
            f"cd {shell_quote(str(install_dir))}\n"
            f"export FIELDREPLAY_INSTALL_REPORT={shell_quote(str(install_report_path))}\n"
            f"export FIELDREPLAY_INSTALL_LOG={shell_quote(str(install_report_path.with_name('install.latest.log')))}\n"
            f"exec {shell_quote('./' + target_name)}\n",
            encoding="utf-8",
        )
        make_executable(entry)
        entries[role] = entry
    return entries


def write_windows_desktop_entries(desktop_dir: pathlib.Path, install_dir: pathlib.Path, install_report_path: pathlib.Path) -> dict[str, pathlib.Path]:
    desktop_dir.mkdir(parents=True, exist_ok=True)
    entries = {}
    for role, entry_name, target_name in launcher_specs():
        entry = desktop_dir / entry_name
        entry.write_text(
            "@echo off\r\n"
            f"cd /d \"{str(install_dir)}\"\r\n"
            f"set \"FIELDREPLAY_INSTALL_REPORT={str(install_report_path)}\"\r\n"
            f"set \"FIELDREPLAY_INSTALL_LOG={str(install_report_path.with_name('install.latest.log'))}\"\r\n"
            f"call \"{target_name}\"\r\n",
            encoding="utf-8",
        )
        entries[role] = entry
    return entries


def shell_quote(value: str) -> str:
    return "'" + value.replace("'", "'\"'\"'") + "'"


def path_base_name(value: object) -> str:
    text = str(value or "").strip()
    if not text:
        return ""
    return pathlib.Path(text).name or text


def launcher_for_platform(install_dir: pathlib.Path) -> pathlib.Path:
    return installed_launcher_paths(install_dir)["main"]


def launch_bestperf(install_dir: pathlib.Path, install_report_path: pathlib.Path) -> Optional[int]:
    launcher = launcher_for_platform(install_dir)
    if not launcher.is_file():
        return None
    env = os.environ.copy()
    env["FIELDREPLAY_INSTALL_REPORT"] = str(install_report_path)
    env["FIELDREPLAY_INSTALL_LOG"] = str(install_report_path.with_name("install.latest.log"))
    if platform.system() == "Windows":
        subprocess.Popen(["cmd.exe", "/c", str(launcher)], cwd=str(install_dir), env=env)
    else:
        subprocess.Popen([str(launcher)], cwd=str(install_dir), env=env)
    return 0


def report_action(action_id: str, label: str, severity: str, action: str) -> dict:
    return {
        "id": action_id,
        "label": label,
        "severity": severity,
        "action": action,
    }


def build_operator_guide(desktop_entries: dict[str, pathlib.Path]) -> dict:
    return {
        "start": {
            "label": "启动 BestPerf",
            "path": str(desktop_entries.get("main") or ""),
            "detail": "Double-click this first to start the GUI.",
        },
    }


def first_issue(report: dict) -> str:
    for check in report.get("checks", []):
        if check.get("status") != "pass":
            label = check.get("label") or check.get("id") or "Install check"
            detail = check.get("detail") or check.get("action") or ""
            return f"{label}: {detail}".strip()
    return ""


def build_operator_summary(report: dict) -> dict:
    operator_guide = report.get("operator_guide") if isinstance(report.get("operator_guide"), dict) else {}
    start = operator_guide.get("start") if isinstance(operator_guide.get("start"), dict) else {}
    status = str(report.get("status") or "")
    problem = first_issue(report)

    base = {
        "status_label": install_status_label(status),
        "problem": problem,
    }
    if status == "installed":
        return {
            **base,
            "level": "pass",
            "headline": "安装成功，可以启动 BestPerf",
            "next_click_label": "启动 BestPerf",
            "next_click_path": str(start.get("path") or report.get("desktop_entry") or ""),
            "next_click_detail": f"双击桌面上的 {path_base_name(start.get('path') or report.get('desktop_entry') or '1 启动 BestPerf')} 入口启动 GUI。",
            "help_detail": "如果 GUI 打不开，把安装报告和日志发给支持人员。",
        }
    if status == "dry_run":
        return {
            **base,
            "level": "warn",
            "headline": "这是预演，还没有安装",
            "next_click_label": "运行安装器",
            "next_click_path": "",
            "next_click_detail": "重新运行安装器，不要勾选 dry-run，安装完成后再打开桌面入口。",
            "help_detail": "预演只检查计划，不会复制文件，也不会创建真正的桌面入口。",
        }
    return {
        **base,
        "level": "fail",
        "headline": "安装没有完成",
        "next_click_label": "重新安装",
        "next_click_path": "",
        "next_click_detail": "使用完整的 BestPerf 安装包重新运行安装器。",
        "help_detail": "如果重新安装后仍失败，把安装报告和日志发给支持人员。",
    }


def runtime_python_entrypoints(install_dir: pathlib.Path) -> list[str]:
    entries = []
    for rel in [
        "runtime/python/bin/python3",
        "runtime/python/bin/python",
        "runtime/python/python3",
        "runtime/python/python",
        "runtime/python/python.exe",
    ]:
        candidate = install_dir / rel
        if not candidate.is_file():
            continue
        if candidate.suffix.lower() != ".exe" and not os.access(candidate, os.X_OK):
            continue
        entries.append(rel)
    return entries


def read_runtime_manifest(install_dir: pathlib.Path) -> dict:
    manifest_path = install_dir / "runtime" / "runtime_manifest.json"
    if not manifest_path.is_file():
        return {}
    try:
        payload = json.loads(manifest_path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return {"error": "runtime_manifest.json is not readable"}
    return payload if isinstance(payload, dict) else {"error": "runtime_manifest.json is not an object"}


def build_runtime_status(install_dir: pathlib.Path) -> dict:
    entrypoints = runtime_python_entrypoints(install_dir)
    manifest = read_runtime_manifest(install_dir)
    if entrypoints:
        manifest_status = str(manifest.get("smoke_status") or "unknown")
        detail = "已随包，无需用户安装 Python。启动、诊断和支持包都会优先使用随包 Python。"
        if manifest_status not in {"passed", "unknown", "skipped"}:
            return {
                "status": "bundled_review",
                "level": "warn",
                "bundled": True,
                "python_entrypoint": entrypoints[0],
                "manifest_status": manifest_status,
                "detail": "已随包，但 runtime_manifest.json 显示需要确认。支持人员应重新准备 runtime。",
            }
        return {
            "status": "bundled",
            "level": "pass",
            "bundled": True,
            "python_entrypoint": entrypoints[0],
            "manifest_status": manifest_status,
            "detail": detail,
        }
    return {
        "status": "system_python",
        "level": "warn",
        "bundled": False,
        "python_entrypoint": "",
        "manifest_status": "",
        "detail": f"未随包，当前依赖系统 Python：{sys.executable}。给电脑小白发包前建议使用带 runtime 的平台包。",
    }


def install_check(check_id: str, label: str, status: str, detail: str, action: str = "") -> dict:
    item = {
        "id": check_id,
        "label": label,
        "status": status,
        "detail": detail,
    }
    if action:
        item["action"] = action
    return item


def summarize_checks(checks: list[dict]) -> dict:
    return {
        "pass": sum(1 for item in checks if item.get("status") == "pass"),
        "warn": sum(1 for item in checks if item.get("status") == "warn"),
        "fail": sum(1 for item in checks if item.get("status") == "fail"),
    }


def build_install_checks(install_dir: pathlib.Path, desktop_entries: dict[str, pathlib.Path], dry_run: bool) -> list[dict]:
    installed_launchers = installed_launcher_paths(install_dir)
    bootstrap_script = install_dir / "scripts" / "bestperf_bootstrap.py"
    gui_index = install_dir / "gui" / "index.html"
    if dry_run:
        return [
            install_check("copy_release", "Copy release", "warn", f"planned install to {install_dir}", "Run installer without dry-run."),
            install_check("desktop_entry", "Desktop launcher", "warn", f"planned launcher at {desktop_entries['main']}", "Run installer without dry-run."),
        ]
    return [
        install_check(
            "install_dir",
            "Install folder",
            "pass" if install_dir.is_dir() else "fail",
            str(install_dir),
            "Run Install BestPerf again." if not install_dir.is_dir() else "",
        ),
        install_check(
            "main_launcher",
            "Main launcher",
            "pass" if installed_launchers["main"].is_file() else "fail",
            str(installed_launchers["main"]),
            "Use a complete BestPerf release package, then run Install BestPerf again." if not installed_launchers["main"].is_file() else "",
        ),
        install_check(
            "desktop_entry",
            "Desktop launcher",
            "pass" if desktop_entries["main"].is_file() else "fail",
            str(desktop_entries["main"]),
            "Run Install BestPerf again." if not desktop_entries["main"].is_file() else "",
        ),
        install_check(
            "bootstrap_script",
            "Bootstrap script",
            "pass" if bootstrap_script.is_file() else "fail",
            str(bootstrap_script),
            "Use a complete BestPerf release package, then run Install BestPerf again." if not bootstrap_script.is_file() else "",
        ),
        install_check(
            "gui_assets",
            "GUI assets",
            "pass" if gui_index.is_file() else "fail",
            str(gui_index),
            "Use a complete BestPerf release package, then run Install BestPerf again." if not gui_index.is_file() else "",
        ),
    ]


def install(args) -> dict:
    source_dir = pathlib.Path(args.root).expanduser().resolve()
    install_dir = pathlib.Path(args.install_dir).expanduser().resolve() if args.install_dir else default_install_dir().resolve()
    desktop_dir = pathlib.Path(args.desktop_dir).expanduser().resolve() if args.desktop_dir else default_desktop_dir().resolve()
    out_dir = pathlib.Path(args.out_dir).expanduser().resolve() if args.out_dir else default_out_dir(source_dir)
    report_path = out_dir / "install_report.latest.json"
    log_path = out_dir / "install.latest.log"
    summary_path = out_dir / "install_summary.latest.txt"
    summary_html_path = out_dir / "install_summary.latest.html"
    launch_requested = not args.no_launch
    steps = []
    desktop_entries = desktop_entry_paths(desktop_dir)
    desktop_entry = desktop_entries["main"]
    install_report_path = install_dir / "install_report.latest.json"

    if not source_dir.is_dir():
        raise FileNotFoundError(f"release root not found: {source_dir}")

    steps.append({"id": "copy_release", "status": "planned", "source": str(source_dir), "destination": str(install_dir)})
    steps.append({"id": "create_desktop_entries", "status": "planned", "paths": {role: str(path) for role, path in desktop_entries.items()}})
    if launch_requested:
        steps.append({"id": "launch_bestperf", "status": "planned", "path": str(launcher_for_platform(install_dir))})

    if args.dry_run:
        checks = build_install_checks(install_dir, desktop_entries, dry_run=True)
        runtime = {
            "status": "not_installed",
            "level": "warn",
            "bundled": False,
            "python_entrypoint": "",
            "manifest_status": "",
            "detail": "预演不会复制 runtime；正式安装后会检查随包 Python。",
        }
        summary = summarize_checks(checks)
        status = "dry_run"
        action_plan = [report_action("run_installer", "运行安装器", "next", "重新运行安装器，不要勾选 dry-run。")]
    else:
        copy_release_tree(source_dir, install_dir)
        steps[0]["status"] = "done"
        if platform.system() == "Windows":
            desktop_entries = write_windows_desktop_entries(desktop_dir, install_dir, install_report_path)
        else:
            desktop_entries = write_posix_desktop_entries(desktop_dir, install_dir, install_report_path)
        desktop_entry = desktop_entries["main"]
        steps[1]["status"] = "done"
        steps[1]["paths"] = {role: str(path) for role, path in desktop_entries.items()}
        if launch_requested:
            launch_bestperf(install_dir, install_report_path)
            steps[-1]["status"] = "started"
        checks = build_install_checks(install_dir, desktop_entries, dry_run=False)
        runtime = build_runtime_status(install_dir)
        summary = summarize_checks(checks)
        if summary["fail"]:
            status = "failed"
            action_plan = [report_action("repair_install", "重新安装", "blocker", "使用完整的 BestPerf 安装包重新运行安装器。")]
        else:
            status = "installed"
            action_plan = [report_action("open_desktop_entry", "启动 BestPerf", "next", f"双击桌面上的 {desktop_entry.name}。")]

    report = {
        "schema": SCHEMA,
        "status": status,
        "generated_at_unix_ns": time.time_ns(),
        "dry_run": bool(args.dry_run),
        "platform": {
            "system": platform.system(),
            "release": platform.release(),
            "machine": platform.machine(),
        },
        "source_dir": str(source_dir),
        "install_dir": str(install_dir),
        "desktop_dir": str(desktop_dir),
        "desktop_entry": str(desktop_entry),
        "desktop_entries": {role: str(path) for role, path in desktop_entries.items()},
        "operator_guide": build_operator_guide(desktop_entries),
        "launch_requested": launch_requested,
        "report_path": str(report_path),
        "log_path": str(log_path),
        "summary_path": str(summary_path),
        "summary_html_path": str(summary_html_path),
        "steps": steps,
        "checks": checks,
        "runtime": runtime,
        "summary": summary,
        "action_plan": action_plan,
    }
    report["operator_summary"] = build_operator_summary(report)
    out_dir.mkdir(parents=True, exist_ok=True)
    report_path.write_text(json.dumps(report, indent=2, sort_keys=True), encoding="utf-8")
    log_path.write_text(render_log(report), encoding="utf-8")
    summary_path.write_text(render_summary(report), encoding="utf-8")
    summary_html_path.write_text(render_summary_html(report), encoding="utf-8")
    if not args.dry_run and install_dir.is_dir():
        installed_report = {
            **report,
            "report_path": str(install_report_path),
            "log_path": str(install_report_path.with_name("install.latest.log")),
            "summary_path": str(install_report_path.with_name("install_summary.latest.txt")),
            "summary_html_path": str(install_report_path.with_name("install_summary.latest.html")),
        }
        install_report_path.write_text(json.dumps(installed_report, indent=2, sort_keys=True), encoding="utf-8")
        install_report_path.with_name("install.latest.log").write_text(render_log(report), encoding="utf-8")
        install_report_path.with_name("install_summary.latest.txt").write_text(render_summary(installed_report), encoding="utf-8")
        install_report_path.with_name("install_summary.latest.html").write_text(render_summary_html(installed_report), encoding="utf-8")
    return report


def render_summary(report: dict) -> str:
    summary = report.get("summary") if isinstance(report.get("summary"), dict) else {}
    action_plan = report.get("action_plan") if isinstance(report.get("action_plan"), list) else []
    next_action = action_plan[0] if action_plan else {}
    desktop_entries = report.get("desktop_entries") if isinstance(report.get("desktop_entries"), dict) else {}
    operator_guide = report.get("operator_guide") if isinstance(report.get("operator_guide"), dict) else {}
    operator_summary = report.get("operator_summary") if isinstance(report.get("operator_summary"), dict) else {}
    status = str(report.get("status") or "")
    next_click_label = str(operator_summary.get("next_click_label") or install_next_action_label(str(next_action.get("label") or "")))
    next_click_path = str(operator_summary.get("next_click_path") or "")
    next_click_detail = str(operator_summary.get("next_click_detail") or str(next_action.get("action") or ""))
    primary_text = primary_action_text(status, next_click_label, next_click_path)
    runtime = report.get("runtime") if isinstance(report.get("runtime"), dict) else {}
    runtime_detail = str(runtime.get("detail") or "")
    issue = first_issue(report)
    user_steps = operator_user_steps(status, primary_text, next_click_detail, next_click_path)
    lines = [
        "BestPerf 安装摘要",
        "",
        f"状态: {install_status_label(status)}",
        "普通用户只看这一段:",
    ]
    lines.extend(f"{index}. {step}" for index, step in enumerate(user_steps, 1))
    if issue and status == "failed":
        lines.append(f"问题原因: {short_issue_text(issue)}")
    lines.extend([
        "支持人员再看:",
        "给支持人员: install_report.latest.json 和 install.latest.log",
    ])
    if runtime_detail:
        lines.append(f"Python 环境: {runtime_detail}")
    lines.extend([
        f"安装报告: {report.get('report_path')}",
        f"安装日志: {report.get('log_path')}",
        "",
        "BestPerf Install Summary",
        "",
        f"Status: {report.get('status')}",
        f"Next action: {next_action.get('label') or 'Review install report'}",
        f"Next action detail: {next_action.get('action') or ''}",
        "",
        f"Install dir: {report.get('install_dir')}",
        f"Desktop: {report.get('desktop_entry')}",
        "Desktop entries:",
    ])
    if desktop_entries:
        for role in ["main"]:
            if desktop_entries.get(role):
                lines.append(f"- {role}: {desktop_entries[role]}")
    else:
        lines.append("- none")
    if operator_guide:
        lines.extend(["", "What to click:"])
        click_labels = {
            "start": "启动 BestPerf",
        }
        for index, key in enumerate(["start"], start=1):
            entry = operator_guide.get(key) if isinstance(operator_guide.get(key), dict) else {}
            label = entry.get("label") or click_labels[key]
            path = entry.get("path") or ""
            lines.append(f"{index}. {label}: {path}")
    lines.extend([
        "",
        f"Checks: pass={int(summary.get('pass') or 0)} warn={int(summary.get('warn') or 0)} fail={int(summary.get('fail') or 0)}",
    ])
    if issue:
        lines.append(f"First issue: {issue}")
    lines.extend([
        "",
        f"Report: {report.get('report_path')}",
        f"Log: {report.get('log_path')}",
    ])
    return "\n".join(lines) + "\n"


def install_status_label(status: str) -> str:
    labels = {
        "installed": "安装成功",
        "dry_run": "安装预演",
        "failed": "安装失败",
    }
    return labels.get(status, status or "未知状态")


def install_next_action_label(label: str) -> str:
    labels = {
        "Open BestPerf": "打开 BestPerf",
        "启动 BestPerf": "启动 BestPerf",
        "Run installer": "运行安装器",
        "运行安装器": "运行安装器",
        "Repair install": "修复安装",
        "重新安装": "重新安装",
    }
    return labels.get(label, label or "查看安装报告")


def primary_action_text(status: str, next_click_label: str, next_click_path: str) -> str:
    if status == "installed" and next_click_path:
        name = path_base_name(next_click_path)
        if name:
            return f"只双击桌面上的 {name}"
    return next_click_label


def short_issue_text(issue: str) -> str:
    if not issue:
        return ""
    return issue.split(":", 1)[0].strip() or issue


def operator_user_steps(status: str, primary_text: str, next_click_detail: str, next_click_path: str) -> list[str]:
    if status == "installed":
        launcher_name = path_base_name(next_click_path)
        return [
            f"双击桌面上的 {launcher_name or primary_text}",
            "浏览器打开后点击 开始自动准备",
            "通过后再选要做的事",
        ]
    if status == "dry_run":
        return [
            "重新运行安装器，不要勾选 dry-run",
            "安装完成后只点桌面上的 1 启动 BestPerf",
        ]
    if status == "failed":
        return [
            next_click_detail or primary_text or "使用完整的 BestPerf 安装包重新运行安装器。",
            "如果还是失败，生成支持包发给支持人员。",
        ]
    return [next_click_detail or primary_text or "查看安装结果。"]


def render_summary_html(report: dict) -> str:
    summary = report.get("summary") if isinstance(report.get("summary"), dict) else {}
    action_plan = report.get("action_plan") if isinstance(report.get("action_plan"), list) else []
    next_action = action_plan[0] if action_plan else {}
    operator_guide = report.get("operator_guide") if isinstance(report.get("operator_guide"), dict) else {}
    operator_summary = report.get("operator_summary") if isinstance(report.get("operator_summary"), dict) else {}
    status = str(report.get("status") or "")
    status_label = install_status_label(status)
    next_label = install_next_action_label(str(next_action.get("label") or ""))
    headline = str(operator_summary.get("headline") or status_label)
    next_click_label = str(operator_summary.get("next_click_label") or next_label)
    next_click_detail = str(operator_summary.get("next_click_detail") or str(next_action.get("action") or ""))
    next_click_path = str(operator_summary.get("next_click_path") or "")
    primary_text = primary_action_text(status, next_click_label, next_click_path)
    runtime = report.get("runtime") if isinstance(report.get("runtime"), dict) else {}
    runtime_detail = str(runtime.get("detail") or "")
    help_detail = str(operator_summary.get("help_detail") or "")
    operator_level = str(operator_summary.get("level") or "warn")
    if operator_level not in {"pass", "warn", "fail"}:
        operator_level = "warn"
    guide_labels = {
        "start": "启动 BestPerf",
    }
    guide_details = {
        "start": "双击这个入口启动 GUI。",
    }
    guide_html = []
    for key in ["start"]:
        entry = operator_guide.get(key) if isinstance(operator_guide.get(key), dict) else {}
        path = str(entry.get("path") or "")
        detail = guide_details[key]
        guide_html.append(
            "<section>"
            f"<h2>{html.escape(guide_labels[key])}</h2>"
            f"<p>{html.escape(detail)}</p>"
            f"<code>{html.escape(path or '-')}</code>"
            "</section>"
        )
    issue = first_issue(report)
    issue_block = ""
    if issue and status == "failed":
        issue_block = f"<p class=\"issue\">问题原因：{html.escape(issue)}</p>"
    runtime_block = ""
    if runtime_detail:
        runtime_block = (
            "<section>"
            "<h2>Python 环境</h2>"
            f"<p>{html.escape(runtime_detail)}</p>"
            "</section>"
        )
    primary_path = path_base_name(next_click_path) if next_click_path else str(next_action.get("action") or "-")
    primary_path_label = "桌面文件名" if next_click_path else "照着做"
    return f"""<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>BestPerf 安装结果</title>
    <style>
      :root {{
        color-scheme: light;
        --bg: #f5f7fa;
        --ink: #17212b;
        --muted: #657282;
        --line: #cfd8e1;
        --panel: #ffffff;
        --primary: #0b6374;
        --ok: #1f7a4c;
        --warn: #9a6414;
        --bad: #a83d3d;
      }}
      * {{ box-sizing: border-box; }}
      body {{
        margin: 0;
        background: var(--bg);
        color: var(--ink);
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", sans-serif;
        line-height: 1.5;
      }}
      main {{
        max-width: 980px;
        margin: 0 auto;
        padding: 28px 18px 42px;
      }}
      header, section {{
        border: 1px solid var(--line);
        background: var(--panel);
        padding: 18px;
      }}
      .operator-card {{
        border-width: 2px;
        background: #ffffff;
      }}
      .operator-card.level-pass {{ border-color: var(--ok); }}
      .operator-card.level-warn {{ border-color: var(--warn); }}
      .operator-card.level-fail {{ border-color: var(--bad); }}
      .status-line {{
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        color: var(--muted);
        font-size: 14px;
        font-weight: 700;
      }}
      .status-line strong {{
        color: var(--primary);
        font-size: 20px;
      }}
      .level-pass .status-line strong {{ color: var(--ok); }}
      .level-warn .status-line strong {{ color: var(--warn); }}
      .level-fail .status-line strong {{ color: var(--bad); }}
      h1 {{
        margin: 12px 0 0;
        font-size: 30px;
        line-height: 1.2;
      }}
      h2 {{
        margin: 0 0 8px;
        font-size: 19px;
      }}
      .status {{
        margin: 12px 0 0;
        color: var(--primary);
        font-size: 24px;
        font-weight: 800;
      }}
      .next {{
        margin: 8px 0 0;
        font-size: 18px;
        font-weight: 700;
      }}
      .grid {{
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 12px;
        margin-top: 14px;
      }}
      code {{
        display: block;
        border: 1px solid #d7dee6;
        background: #f0f3f6;
        padding: 9px;
        overflow-wrap: anywhere;
        font-family: "SFMono-Regular", Consolas, monospace;
      }}
      .muted {{
        color: var(--muted);
      }}
      .issue {{
        border-left: 4px solid var(--bad);
        background: #fff4f4;
        padding: 10px 12px;
      }}
      .next-card {{
        margin-top: 16px;
        border: 2px solid var(--primary);
        background: #eef8fa;
        padding: 16px;
      }}
      .next-card span {{
        display: block;
        color: var(--muted);
        font-size: 14px;
        font-weight: 700;
      }}
      .primary-action {{
        display: flex;
        align-items: center;
        justify-content: center;
        margin-top: 4px;
        min-height: 58px;
        border: 1px solid #064c58;
        background: var(--primary);
        color: #ffffff;
        padding: 12px 14px;
        text-align: center;
        font-size: 28px;
        font-weight: 900;
      }}
      .level-pass .primary-action {{ background: var(--ok); border-color: #155935; }}
      .level-warn .primary-action {{ background: var(--warn); border-color: #70470e; }}
      .level-fail .primary-action {{ background: var(--bad); border-color: #7a2929; }}
      .next-card strong {{
        display: block;
        margin-top: 12px;
        color: var(--primary);
        font-size: 15px;
      }}
      .operator-path {{
        margin-top: 8px;
        font-size: 13px;
      }}
      .operator-note {{
        margin: 12px 0 0;
        color: var(--muted);
        font-size: 13px;
      }}
      .help-box {{
        margin-top: 12px;
        border: 1px solid var(--line);
        background: #f9fbfd;
        padding: 12px;
      }}
      .support-details {{
        margin-top: 14px;
        border: 1px solid var(--line);
        background: var(--panel);
      }}
      .support-details summary {{
        cursor: pointer;
        padding: 14px 16px;
        color: var(--muted);
        font-weight: 800;
      }}
      .support-details[open] summary {{
        border-bottom: 1px solid var(--line);
      }}
      .support-body {{
        padding: 16px;
      }}
      .support-grid {{
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 12px;
      }}
      @media (max-width: 760px) {{
        .grid,
        .support-grid {{ grid-template-columns: 1fr; }}
        h1 {{ font-size: 25px; }}
        .primary-action {{ font-size: 23px; }}
      }}
    </style>
  </head>
  <body>
    <main class="install-wizard">
      <header class="operator-card level-{html.escape(operator_level)}">
        <div class="status-line">
          <span>BestPerf 安装结果</span>
          <strong>{html.escape(status_label)}</strong>
        </div>
        <h2>{html.escape(headline)}</h2>
        <h1>下一步：{html.escape(next_label)}</h1>
        <p class="next">下一步：{html.escape(next_label)}</p>
        {issue_block}
        <div class="next-card">
          <span>下一步只点这个 · 现在只需要做这一步</span>
          <div class="primary-action">{html.escape(primary_text)}</div>
          <p>{html.escape(next_click_detail)}</p>
          <strong>{html.escape(primary_path_label)}</strong>
          <code class="operator-path">{html.escape(primary_path)}</code>
          <p class="operator-note">普通用户不用看下面的报告路径。</p>
        </div>
        <div class="help-box">
          <strong>如果 GUI 打不开</strong>
          <p>{html.escape(help_detail or '把安装报告和日志发给支持人员。')}</p>
        </div>
      </header>
      <details class="support-details">
        <summary>支持人员才需要看</summary>
        <div class="support-body">
          <p class="muted">如果你不知道怎么处理，把这些文件路径发给支持人员。</p>
          <div class="support-grid">
            {''.join(guide_html)}
            {runtime_block}
            <section>
              <h2>安装报告</h2>
              <p>支持人员用它确认安装器做了什么。</p>
              <code>{html.escape(str(report.get('report_path') or '-'))}</code>
            </section>
            <section>
              <h2>安装日志</h2>
              <p>支持人员用它定位失败命令。</p>
              <code>{html.escape(str(report.get('log_path') or '-'))}</code>
            </section>
          </div>
        </div>
      </details>
    </main>
  </body>
</html>
"""


def render_log(report: dict) -> str:
    lines = [
        "BestPerf install report",
        f"status={report.get('status')}",
        f"source_dir={report.get('source_dir')}",
        f"install_dir={report.get('install_dir')}",
        f"desktop_entry={report.get('desktop_entry')}",
        f"launch_requested={report.get('launch_requested')}",
        "",
    ]
    runtime = report.get("runtime") if isinstance(report.get("runtime"), dict) else {}
    if runtime:
        lines.extend([
            f"runtime_status={runtime.get('status')}",
            f"runtime_level={runtime.get('level')}",
            f"runtime_python={runtime.get('python_entrypoint')}",
            f"runtime_detail={runtime.get('detail')}",
            "",
        ])
    for role, path in (report.get("desktop_entries") or {}).items():
        lines.append(f"desktop_entry_{role}={path}")
    if report.get("desktop_entries"):
        lines.append("")
    for step in report.get("steps", []):
        detail = step.get("path") or step.get("destination") or step.get("paths") or ""
        if isinstance(detail, dict):
            detail = ", ".join(f"{role}={path}" for role, path in detail.items())
        lines.append(f"{step.get('id')} {step.get('status')} {detail}".rstrip())
    lines.append("")
    for check in report.get("checks", []):
        lines.append(f"{check.get('id')} {check.get('status')} {check.get('detail', '')}".rstrip())
    return "\n".join(lines) + "\n"


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Install BestPerf into a stable user folder and create a desktop launcher",
        epilog="Writes install_summary.latest.txt, install_summary.latest.html, install_report.latest.json, and install.latest.log with post-install checks.",
    )
    parser.add_argument("--root", default=str(ROOT), help="Release root to install")
    parser.add_argument("--install-dir", default="", help="Destination directory, default is user-local BestPerf app directory")
    parser.add_argument("--desktop-dir", default="", help="Desktop directory for the launcher")
    parser.add_argument("--out-dir", default="", help="Directory for install_report.latest.json and install.latest.log")
    parser.add_argument("--dry-run", action="store_true", help="Write planned install report without copying files")
    parser.add_argument("--no-launch", action="store_true", help="Do not launch BestPerf after installing")
    args = parser.parse_args()
    try:
        report = install(args)
    except Exception as exc:
        print(f"install_error={exc}", file=sys.stderr)
        return 1
    print(f"install_report={report['report_path']}")
    print(f"install_summary={report['summary_path']}")
    print(f"install_summary_html={report['summary_html_path']}")
    print(f"install_log={report['log_path']}")
    print(f"install_status={report['status']}")
    print(f"desktop_entry={report['desktop_entry']}")
    for role, path in report.get("desktop_entries", {}).items():
        print(f"desktop_entry_{role}={path}")
    return 0 if report["status"] in {"installed", "dry_run"} else 2


if __name__ == "__main__":
    raise SystemExit(main())
