#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Rada OTA 升级进度显示程序
在 dpkg 升级 rada-frontend 期间显示全屏进度界面，
主服务恢复后自动退出。
"""

import os
import sys
import warnings
import time
import argparse
import subprocess

# 屏蔽 GTK 的 DeprecationWarning（不影响功能，避免日志污染）
warnings.filterwarnings("ignore", category=DeprecationWarning)

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GLib', '2.0')
from gi.repository import Gtk, GLib, Pango, Gdk


# ---------- 配置 ----------

LOG_FILE_DEFAULT = "/home/jbn/Rada/records/ota-upgrade.log"
SERVICE_DEFAULT = "radar-frontend"
TIMEOUT_DEFAULT = 600  # 10 分钟超时
POLL_INTERVAL = 500    # 轮询间隔（毫秒）

# 阶段定义：(阶段名, 关键字列表, 显示文案, 进度起始%, 进度结束%)
STAGES = [
    ("preparing", [],                          "正在准备更新...",     0,   5),
    ("download",  ["Get:", "获取:"],           "正在下载更新包...",   5,   35),
    ("unpack",    ["Unpacking", "解包"],       "正在解压文件...",     35,  65),
    ("install",   ["Setting up", "正在设置"],  "正在安装新版本...",   65,  90),
    ("starting",  [],                          "正在启动服务...",     90,  100),
]

# 错误关键字（只匹配 dpkg 级别的致命错误，不匹配 apt 临时错误）
# 注意：不包含 "E: "，因为 apt 中间可能有临时错误但最终会成功（如依赖问题重试）
ERROR_KEYWORDS = [
    "dpkg: error",
    "Errors were encountered while processing",
    "subprocess installed post-installation script returned error",
    "subprocess new pre-removal script returned error",
]


class UpdaterWindow(Gtk.Window):
    """升级进度显示窗口"""

    def __init__(self, log_file, service, timeout):
        super().__init__(title="系统更新")
        self.log_file = log_file
        self.service = service
        self.timeout = timeout
        self.start_time = time.time()

        # 状态
        self.current_stage = 0          # 阶段索引
        self.progress = 0.0             # 当前进度百分比
        self.is_error = False
        self.error_message = ""
        self.log_pos = self._init_log_pos()  # 日志读取位置（启动时定位到文件末尾）

        # 服务生命周期状态机：
        #   "waiting_stop"  → 等待主服务被停止（prerm 阶段）
        #   "waiting_start" → 等待主服务重新启动（postinst 之后）
        #   "starting"      → 服务已启动，显示"启动中"后退出
        self.service_phase = "waiting_stop"
        self.service_inactive_since = None   # 服务停止的时间
        self.service_active_since = None     # 服务重新活跃的时间
        self.starting_stage_start = None     # "启动中"阶段开始时间

        # 窗口设置
        self.set_decorated(False)        # 无边框
        self.set_resizable(False)
        self.set_position(Gtk.WindowPosition.CENTER)

        # 全屏
        screen = self.get_screen()
        monitor = screen.get_primary_monitor()
        geom = screen.get_monitor_geometry(monitor)
        self.set_size_request(geom.width, geom.height)
        self.move(geom.x, geom.y)
        self.fullscreen()

        # 背景色（深色）
        self.override_background_color(
            Gtk.StateType.NORMAL,
            Gdk.RGBA(0.10, 0.10, 0.18, 1.0)
        )

        # 构建 UI
        self._build_ui()

        # 启动轮询定时器
        GLib.timeout_add(POLL_INTERVAL, self._on_poll)

    def _init_log_pos(self):
        """初始化日志读取位置——从文件末尾开始，只读取启动后新增的内容
        避免历史日志（之前的升级记录）导致阶段误判。
        """
        try:
            if os.path.exists(self.log_file):
                with open(self.log_file, "r", errors="replace") as f:
                    f.seek(0, os.SEEK_END)
                    return f.tell()
        except Exception:
            pass
        return 0

    def _build_ui(self):
        """构建界面布局"""
        # 外层垂直盒子
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        vbox.set_homogeneous(False)
        self.add(vbox)

        # 顶部弹性空间
        vbox.pack_start(Gtk.Box(), True, True, 0)

        # 主标题
        self.title_label = Gtk.Label()
        self.title_label.set_markup(
            '<span font="36" weight="bold" foreground="#ffffff">'
            '系统正在更新，请稍候...'
            '</span>'
        )
        self.title_label.set_alignment(0.5, 0.5)
        vbox.pack_start(self.title_label, False, False, 20)

        # 标题与进度条之间的间距
        vbox.pack_start(Gtk.Box(), False, False, 60)

        # 进度条容器（水平居中）
        progress_hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        progress_hbox.set_homogeneous(False)

        # 左侧弹性空间
        progress_hbox.pack_start(Gtk.Box(), True, True, 0)

        # 进度条
        self.progress_bar = Gtk.ProgressBar()
        self.progress_bar.set_size_request(700, 30)
        self.progress_bar.set_fraction(0.0)
        self.progress_bar.set_show_text(True)
        self.progress_bar.set_text("0%")
        self.progress_bar.override_font(Pango.FontDescription("18"))

        # 进度条样式（通过 CSS）
        css_provider = Gtk.CssProvider()
        css = b"""
        progressbar {
            background-color: #2d2d44;
            border-radius: 4px;
        }
        progressbar trough {
            background-color: #2d2d44;
            border-radius: 4px;
            border: none;
        }
        progressbar progress {
            background-color: #4a9eff;
            border-radius: 4px;
            min-height: 30px;
        }
        """
        css_provider.load_from_data(css)
        style_context = self.progress_bar.get_style_context()
        style_context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

        # 保存样式 provider 引用，错误状态时需要追加
        self._progress_normal_css = css_provider

        progress_hbox.pack_start(self.progress_bar, False, False, 0)

        # 右侧弹性空间
        progress_hbox.pack_start(Gtk.Box(), True, True, 0)

        vbox.pack_start(progress_hbox, False, False, 0)

        # 进度条与阶段文字之间的间距
        vbox.pack_start(Gtk.Box(), False, False, 40)

        # 阶段说明文字
        self.stage_label = Gtk.Label()
        self.stage_label.set_markup(
            '<span font="24" foreground="#cccccc">'
            '正在准备更新...'
            '</span>'
        )
        self.stage_label.set_alignment(0.5, 0.5)
        vbox.pack_start(self.stage_label, False, False, 0)

        # 中间弹性空间
        vbox.pack_start(Gtk.Box(), True, True, 0)

        # 底部警告
        self.warn_label = Gtk.Label()
        self.warn_label.set_markup(
            '<span font="16" foreground="#ffaa00">'
            '⚠  请勿断电或关闭设备，升级完成后将自动重启'
            '</span>'
        )
        self.warn_label.set_alignment(0.5, 0.5)
        vbox.pack_start(self.warn_label, False, False, 40)

        # 底部弹性空间
        vbox.pack_start(Gtk.Box(), True, True, 0)

    def _on_poll(self):
        """定时器回调：检查日志 + 检查服务状态

        服务生命周期状态机：
        waiting_stop → 等待 prerm 停服务
        waiting_start → 等待 postinst 启动新服务
        starting → 服务已启动，显示 1 秒后退出
        """
        # 检查服务状态
        service_active = self._check_service_active()

        if self.service_phase == "waiting_stop":
            # 等待服务被停止（prerm 阶段）
            if not service_active:
                if self.service_inactive_since is None:
                    self.service_inactive_since = time.time()
                # 服务停了 1 秒以上，确认真的停了
                elif time.time() - self.service_inactive_since >= 1:
                    self.service_phase = "waiting_start"
                    self.service_active_since = None
            else:
                # 服务还在跑，重置
                self.service_inactive_since = None

        elif self.service_phase == "waiting_start":
            # 等待服务重新启动（postinst 之后）
            if service_active:
                if self.service_active_since is None:
                    self.service_active_since = time.time()
                else:
                    # 服务稳定运行 2 秒 → 进入启动中阶段
                    if time.time() - self.service_active_since >= 2:
                        self._enter_starting_stage()
                        self.service_phase = "starting"
            else:
                self.service_active_since = None

        elif self.service_phase == "starting":
            # 启动中阶段显示 1 秒 → 退出（保证主界面先唤醒）
            if self.starting_stage_start is not None:
                if time.time() - self.starting_stage_start >= 1.0:
                    Gtk.main_quit()
                    return False

        # 错误状态下不再推进阶段
        if self.is_error:
            return True

        # 读取日志更新阶段
        self._update_stage_from_log()

        # 更新进度条动画
        self._update_progress()

        return True  # 继续轮询

    def _check_service_active(self):
        """检查主服务是否活跃"""
        try:
            result = subprocess.run(
                ["systemctl", "is-active", "--quiet", self.service],
                capture_output=True,
                timeout=2
            )
            return result.returncode == 0
        except Exception:
            return False

    def _enter_starting_stage(self):
        """进入启动中阶段"""
        self.current_stage = 4
        self.starting_stage_start = time.time()
        stage_name, _, text, start_pct, end_pct = STAGES[4]
        self._update_stage_label(text)
        self.progress = start_pct

    def _update_stage_from_log(self):
        """从日志文件更新阶段"""
        try:
            if not os.path.exists(self.log_file):
                return

            with open(self.log_file, "r", errors="replace") as f:
                # 移到上次读取位置
                f.seek(self.log_pos)
                new_lines = f.readlines()
                self.log_pos = f.tell()

            if not new_lines:
                return

            # 检查错误
            for line in new_lines:
                for kw in ERROR_KEYWORDS:
                    if kw in line:
                        self._set_error("升级遇到问题，正在自动修复...")
                        return

            # 从后往前匹配阶段（匹配到最高阶段就停）
            # 阶段顺序：preparing(0) → download(1) → unpack(2) → install(3)
            for i in range(len(STAGES) - 2, 0, -1):  # 跳过 preparing 和 starting
                stage_name, keywords, text, start_pct, end_pct = STAGES[i]
                if not keywords:
                    continue
                for line in new_lines:
                    for kw in keywords:
                        if kw in line:
                            if i > self.current_stage:
                                self.current_stage = i
                                self._update_stage_label(text)
                                # 进度跳到新阶段起始值
                                if self.progress < start_pct:
                                    self.progress = start_pct
                            return  # 找到最高阶段就返回
        except Exception:
            pass

    def _update_progress(self):
        """更新进度条显示（带动画）"""
        if self.is_error:
            return

        stage = STAGES[self.current_stage]
        stage_name, keywords, text, start_pct, end_pct = stage

        # 每轮推进一点点（阶段内渐进填充）
        if self.progress < end_pct:
            # 不同阶段推进速度不同
            if stage_name in ("download", "unpack"):
                increment = 0.15
            else:
                increment = 0.3
            self.progress = min(self.progress + increment, end_pct)

        self.progress_bar.set_fraction(self.progress / 100.0)
        self.progress_bar.set_text("{}%".format(int(self.progress)))

    def _update_stage_label(self, text):
        """更新阶段文字"""
        self.stage_label.set_markup(
            '<span font="24" foreground="#cccccc">{}</span>'.format(text)
        )

    def _set_error(self, message):
        """设置错误状态"""
        self.is_error = True
        self.error_message = message
        self.stage_label.set_markup(
            '<span font="24" foreground="#ff6b6b">{}</span>'.format(message)
        )
        # 进度条变红（通过 CSS）
        css_provider = Gtk.CssProvider()
        css = b"""
        progressbar progress {
            background-color: #ff4444;
            border-radius: 4px;
            min-height: 30px;
        }
        """
        css_provider.load_from_data(css)
        style_context = self.progress_bar.get_style_context()
        style_context.add_provider(
            css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 1
        )


def main():
    parser = argparse.ArgumentParser(description="Rada OTA 升级进度显示")
    parser.add_argument("--log-file", default=LOG_FILE_DEFAULT,
                        help="apt 升级日志路径")
    parser.add_argument("--service", default=SERVICE_DEFAULT,
                        help="监控的主服务名")
    parser.add_argument("--timeout", type=int, default=TIMEOUT_DEFAULT,
                        help="超时秒数")
    args = parser.parse_args()

    # 创建窗口
    win = UpdaterWindow(args.log_file, args.service, args.timeout)
    win.connect("destroy", Gtk.main_quit)
    win.show_all()

    # 运行
    Gtk.main()


if __name__ == "__main__":
    main()
