#!/usr/bin/python3

import dbus
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
import logging
import os
import signal
from datetime import datetime, timedelta
import time
import random
import configparser
from apscheduler.schedulers.background import BackgroundScheduler
from threading import Event 
from gettext import gettext as _
import gettext
import getpass
import subprocess
import re
import json
from typing import List, Dict
from dbus.types import Int32, Int64, String, Boolean, Double, Array,Dictionary
from dbus import service

UNATTENDED_UPGRADE_TIMESTAMP = "/var/lib/unattended-upgrades/unattended-upgrades-timestamp"
UNATTENDED_UPGRADE_POLICY_FILE_PATH="/var/lib/unattended-upgrades/unattended-upgrades-policy.conf"
ACTION_INSTALL = 1
ACTION_CHECK_RESOLVER = 3
ACTION_DOWNLOADONLY = 4
        
def signal_term_handler(signal,frame):
    # type: (int, object) -> None
    logging.warning("SIGTERM received, will stop")
    os._exit(1)

def ReadValueFromFile(file,section,option):
    config=configparser.ConfigParser(allow_no_value=True)
    config.optionxform = str
    try:
        config.read(file)
        value = config[section][option]
        return value
    except Exception as e:
        logging.error(_("read config file error:%s")%e)
        return None

def WriteValueToFile(file,section,option,value):
    config=configparser.ConfigParser(allow_no_value=True)
    config.optionxform = str
    config.add_section(section)
    config.set(section,option,value)
    config.write(open(file,"w"))   

def UpdateTimeStamp(task_id,last_run_time):
    logging.debug(_("update timestamp:%s %s")%(task_id,last_run_time))
    if os.path.exists(UNATTENDED_UPGRADE_TIMESTAMP):
        config=configparser.ConfigParser(allow_no_value=True)
        config.optionxform = str
        config.read(UNATTENDED_UPGRADE_TIMESTAMP)
        if 'TimeStamp' in config.sections():
            config.set('TimeStamp',task_id,last_run_time)  
            with open(UNATTENDED_UPGRADE_TIMESTAMP,'w') as f:
                config.write(f)


def ini_to_json(ini_path: str) -> dict:
    """
    将INI配置文件转换为JSON格式的字典对象
    
    Args:
        ini_path: INI文件路径
        
    Returns:
        dict: 转换后的JSON格式字典
        
    Raises:
        FileNotFoundError: 当INI文件不存在时抛出
        ValueError: 当文件格式错误时抛出
    """
    # 初始化配置解析器
    config = configparser.ConfigParser()
    
    # 读取INI文件
    if not config.read(ini_path):
        raise FileNotFoundError(f"文件 {ini_path} 未找到")
    
    # 构建嵌套字典
    result = {}
    for section in config.sections():
        result[section] = dict(config.items(section))  # 转换section为字典结构
        
    return result

def json_to_asv(data):
    """
    将 JSON 字典转换为 D-Bus a{sv} 类型
    参数：
        data: dict - 输入字典（需确保键为字符串）
    返回：
        dbus.Dictionary - 符合 a{sv} 签名的 D-Bus 字典
    异常：
        ValueError - 输入类型错误或数据格式不合法
        TypeError - 存在不支持的类型
    """
    def _convert_value(value):
        # 递归处理嵌套结构
        if isinstance(value, dict):
            return Dictionary(
                {k: _convert_value(v) for k, v in value.items()},
                signature=dbus.Signature('sv'),
                variant_level=1  # 隐式创建变体层
            )
        elif isinstance(value, list):
            return Array(
                [_convert_value(v) for v in value],
                signature=dbus.Signature('v'),
                variant_level=1
            )
        elif isinstance(value, bool):
            return Boolean(value)
        elif isinstance(value, int):
            return Int64(value) if abs(value) > 0x7FFFFFFF else Int32(value)
        elif isinstance(value, float):
            return Double(value)
        elif isinstance(value, str):
            return String(value)
        else:
            raise TypeError(f"不支持的类型: {type(value)}")

    try:
        # 输入类型验证
        if not isinstance(data, dict):
            raise ValueError("输入必须是字典类型")
        
        # 键类型验证（需全部为字符串）
        if not all(isinstance(k, str) for k in data.keys()):
            raise ValueError("字典键必须是字符串")
        
        return Dictionary(
            {k: _convert_value(v) for k, v in data.items()},
            signature=dbus.Signature('sv'),
            variant_level=1
        )
    except (TypeError, ValueError) as e:
        raise e
    except Exception as e:
        raise RuntimeError(f"未知错误: {str(e)}") from e
    
def process_time_intervals(data: List[Dict], max_random_minutes: int) -> List[Dict]:
    """
    合并重叠时间区间并生成随机调整后的时间段
    :param data: 输入时间段列表 [{"start":"HH:MM","end":"HH:MM"}]
    :param max_random_minutes: 最大随机偏移分钟数
    :return: 合并调整后的时间段列表
    """
    # 时间转换函数（支持跨天时间）
    def time_to_minutes(t: str) -> int:
        h, m = map(int, t.split(':'))
        return h * 60 + m

    # 分钟数转时间（支持跨天显示）
    def minutes_to_time(m: int) -> str:
        total_m = m % 1440
        return f"{total_m//60:02d}:{total_m%60:02d}"

    # 区间合并算法
    intervals = []
    for period in data:
        start = time_to_minutes(period["start"])
        end = time_to_minutes(period["end"])
        if end < start:  # 处理跨天情况
            intervals.append((start, end + 1440))
        else:
            intervals.append((start, end))

    # 排序并合并
    intervals.sort(key=lambda x: x[0])
    merged = []
    for current in intervals:
        if not merged:
            merged.append(list(current))
        else:
            last = merged[-1]
            if current[0] <= last[1]:
                last[1] = max(last[1], current[1])
            else:
                merged.append(list(current))

    # 生成随机调整后的时间段
    result = []
    for start, end in merged:
        # 计算有效时间跨度（考虑跨天）
        total_minutes = end - start
        random_offset = random.randint(0, min(max_random_minutes, total_minutes))

        # 生成新开始时间
        new_start = (start + random_offset) % 1440

        # 处理跨天显示逻辑
        original_end = end % 1440 if end > 1440 else end
        result.append({
            "start": minutes_to_time(new_start),
            "end": minutes_to_time(original_end)
        })

    return result

def process_timelist(timelist: List[Dict]) -> List[Dict]:
    """处理跨天时间段并生成标准化输出

    Args:
        timelist: 时间区间列表，示例 [{"start":"23:00","end":"11:00"},...]

    Returns:
        包含小时、分钟和超时时间的字典列表，示例 [{"hour":23,"minute":0,"timeout":720}]
    """
    def time_to_minutes(t: str) -> int:
        """将HH:MM转换为分钟数"""
        hours, minutes = map(int, t.split(':'))
        return hours * 60 + minutes

    result = []

    for period in timelist:
        # 解析起止时间
        start = time_to_minutes(period["start"])
        end = time_to_minutes(period["end"])

        # 处理跨天情况
        if end < start:
            end += 1440  # 24小时分钟数

        # 计算超时分钟数
        timeout = end - start

        # 提取小时和分钟
        start_h, start_m = divmod(start, 60)

        result.append({
            "hour": start_h,
            "minute": start_m,
            "timeout": timeout
        })

    return result

def convert_a_sv_to_json(a_sv_data):
    """
    将DBus的a{sv}类型数据转换为JSON对象
    
    Args:
        a_sv_data (dbus.Dictionary): D-Bus类型的a{sv}数据（字典格式）
    
    Returns:
        str: JSON格式字符串
    """
    def _parse_variant(value):
        # 递归解析变体类型中的嵌套数据
        if isinstance(value, dbus.Dictionary):
            return {k: _parse_variant(v) for k, v in value.items()}
        elif isinstance(value, dbus.Array):
            return [_parse_variant(v) for v in value]
        elif isinstance(value, dbus.Struct):
            return tuple(_parse_variant(v) for v in value)
        elif isinstance(value, (dbus.String, dbus.ObjectPath)):
            return str(value)
        elif isinstance(value, dbus.Boolean):
            return bool(value)
        elif isinstance(value, (dbus.Int16, dbus.Int32, dbus.Int64)):
            return int(value)
        elif isinstance(value, (dbus.Double, dbus.UInt16, dbus.UInt32, dbus.UInt64)):
            return float(value) if '.' in str(value) else int(value)
        else:
            return value

    # 转换主逻辑
    json_dict = {}
    for key, variant in a_sv_data.items():
        json_dict[key] = _parse_variant(variant)
    
    return json.dumps(json_dict, indent=2)

def parse_time(time_str: str) -> dict:
    """将HH:MM格式的时间字符串解析为字典
    
    Args:
        time_str: 时间字符串，支持中文/英文冒号，如"20:00"或"20：00"
        
    Returns:
        包含小时和分钟的字典，示例: {'hour':20, 'minute':0}
        
    Raises:
        ValueError: 当格式无效或数值越界时抛出
    """
    # 预处理字符串
    normalized_str = time_str.replace('：', ':')  # 中文冒号转英文
    parts = normalized_str.split(':')
    
    # 格式验证
    if len(parts) != 2:
        raise ValueError(f"无效时间格式: {time_str}，应为HH:MM")
    
    try:
        hour = int(parts[0])
        minute = int(parts[1])
    except ValueError:
        raise ValueError(f"非数字时间值: {time_str}")
    
    # 数值范围验证
    if not (0 <= hour <= 23):
        raise ValueError(f"小时值越界: {hour}，应为0-23")
    if not (0 <= minute <= 59):
        raise ValueError(f"分钟值越界: {minute}，应为0-59")
    
    return {'hour': hour, 'minute': minute}

def parse_time_range(time_str: str) -> dict:
    """
    解析时间字符串并生成时间范围字典
    
    参数：
    time_str - 输入时间字符串，支持三种格式：
               "HH:MM"、"HH:MM:SS"、"HH:MM-HH:MM"
    
    返回：
    {'start': 'HH:MM', 'end': 'HH:MM'}
    
    异常：
    ValueError - 当输入格式无效或时间值越界时抛出
    """
    # 格式验证正则表达式
    time_pattern = r'^([0-2][0-9]:[0-5][0-9])(?::[0-5][0-9])?(?:-([0-2][0-9]:[0-5][0-9]))?$'
    
    if match := re.match(time_pattern, time_str):
        start_time, end_time = match.groups()
        
        # 处理第三种格式（时间区间）
        if end_time:
            return validate_and_format(start_time, end_time)
        
        # 处理前两种格式（单个时间）
        base_time = time_str[:5]  # 截取HH:MM部分
        calculated_end = calculate_end_time(base_time)
        return {'start': base_time, 'end': calculated_end}
    
    raise ValueError(f"无效时间格式：{time_str}")

def validate_and_format(start: str, end: str) -> dict:
    """验证并格式化时间区间"""
    try:
        # 时间有效性验证
        datetime.strptime(start, '%H:%M')
        datetime.strptime(end, '%H:%M')
        return {'start': start, 'end': end}
    except ValueError as e:
        raise ValueError(f"无效时间值：{str(e)}")

def calculate_end_time(base: str) -> str:
    """计算3小时后时间（处理跨天）"""
    try:
        # 时间解析与计算
        base_dt = datetime.strptime(base, '%H:%M')
        end_dt = base_dt + timedelta(hours=3)
        return end_dt.strftime('%H:%M')
    except ValueError as e:
        raise ValueError(f"无效基准时间：{str(e)}")

class NotifyService(service.Object):
    def __init__(self):
        # 连接到会话总线并注册对象路径
        bus = dbus.SystemBus()
        service.Object.__init__(self,bus, '/com/kylin/notifysend')

    @dbus.service.signal(dbus_interface='com.kylin.notifysend', 
                        signature='is')  # 参数类型定义为int
    def RebootNotify(self, status_code,status_description):
        """自定义信号声明：参数为整数状态码"""
        pass  # 函数体为空，通过调用该方法触发信号

    @dbus.service.signal(dbus_interface='com.kylin.notifysend', 
                        signature='is')  # 参数类型定义为int
    def InstallFinishNotify(self, status_code,status_description):
        """自定义信号声明：参数为整数状态码"""
        pass  # 函数体为空，通过调用该方法触发信号

    @dbus.service.signal(dbus_interface='com.kylin.notifysend', 
                        signature='b')  # 参数类型定义为bool
    def ConnectDistUpgrade(self, status):
        """自定义信号声明：参数为是否连接备份和下载完成信号状态码"""
        pass  # 函数体为空，通过调用该方法触发信号

class UnattendedUpgradesShutdown():
    def __init__(self) -> None:
        DBusGMainLoop(set_as_default=True)
        self.loop = GLib.MainLoop()
        self.system_bus = dbus.SystemBus()
        self.update_utils_proxy = self.system_bus.get_object('com.kylin.systemupgrade','/com/kylin/systemupgrade/utils')
        self.update_utils_interface = dbus.Interface(self.update_utils_proxy,dbus_interface='com.kylin.systemupgrade.interface')
        self.update_proxy = self.system_bus.get_object('com.kylin.systemupgrade','/com/kylin/systemupgrade',follow_name_owner_changes=True)
        self.update_interface = dbus.Interface(self.update_proxy,dbus_interface='com.kylin.systemupgrade.interface')
        self.upgrade_strategy_proxy = self.system_bus.get_object('com.kylin.UpgradeStrategies','/com/kylin/UpgradeStrategies',follow_name_owner_changes=True)
        self.upgrade_strategy_interface = dbus.Interface(self.upgrade_strategy_proxy,dbus_interface='com.kylin.UpgradeStrategies.interface')
        self.backup_proxy = self.system_bus.get_object('com.kylin.backupserver','/',follow_name_owner_changes=True)
        self.backup_interface = dbus.Interface(self.backup_proxy,dbus_interface='com.kylin.backup.server')
        self.update_proxy.connect_to_signal('UpdateDetectFinished',self.update_detect_finished_handler)                                        
        self.update_proxy.connect_to_signal('UpdateFixBrokenStatus',self.update_fix_broken_status)
        self.update_proxy.connect_to_signal('UpdateDependResloveStatus',self.update_depend_resolve_status)                                           
        self.update_proxy.connect_to_signal('UpdateDloadAndInstStaChanged',self.update_download_install_status)
        self.update_proxy.connect_to_signal('UpdateInstallFinished',self.update_install_finished)                                        
        self.update_proxy.connect_to_signal('UpdateDownloadFinished',self.update_download_finished)
        self.update_proxy.connect_to_signal("ChangeUpgradePolicy",self.change_upgrade_policy_handler)   
        self.update_proxy.connect_to_signal("UpgradeAllNow",self.upgrade_all_now_handler)
        self.upgrade_strategy_proxy.connect_to_signal("AutocheckStatusChanged",self.auto_check_status_changed_handler)
        self.upgrade_strategy_proxy.connect_to_signal("AutoUpgradeStatusChanged",self.autoupgrade_status_changed_handler)
        self.upgrade_strategy_proxy.connect_to_signal("AutoupgradePeriodChanged",self.autoupgrade_period_changed_handler)
        self.upgrade_strategy_proxy.connect_to_signal("AutoupgradeDetailChanged",self.autoupgrade_detail_changed_handler)
        self.upgrade_strategy_proxy.connect_to_signal("UpgradeSchemeChanged",self.change_upgrade_scheme_handler)
        self.upgrade_strategy_proxy.connect_to_signal("UpgradeStrategyChanged",self.change_upgrade_strategy_handler)
        self.upgrade_strategy_proxy.connect_to_signal("PropertyChanged",self.property_changed_handler)
        self.upgrade_strategy_proxy.connect_to_signal("UpgradeAllNow",self.upgrade_all_now_handler)   
        self.backup_proxy.connect_to_signal('sendBackupResult',self.backup_result_handler)                                          
        self.backup_proxy.connect_to_signal('sendRate',self.send_rate_handler) 
        self.update_detect_status = False
        self.update_detect_event = Event()
        self.update_list = []
        self.resolve_depend_status = False
        self.resolve_depend_status_event = Event()
        self.remove_pkgs = []
        self.install_finish_status = False
        self.install_finish_status_event = Event()
        self.install_finish_group = []
        self.download_finish_status = False
        self.download_finish_status_event = Event()
        self.download_finish_group = []
        self.backup_finish_result = False
        self.backup_finish_event = Event()    
        self.need_check_backup=True
        self.notify_service = NotifyService()
        self.shutdown_install = 'True'
        self.backup_finish_status = ""

    def DataBackendCollect(self,updateinfo,json_file):
        self.update_utils_interface.DataBackendCollect(updateinfo,json_file)

    def GetConfigValue(self,section,option):
        ret1,ret2=self.update_interface.GetConfigValue(section,option)
        return ret2
    
    def GetAutoBackupStatus(self):
        return self.upgrade_strategy_interface.GetAutoBackupStatus()
    
    def SetConfigValue(self,section,option,value):
        return self.update_interface.SetConfigValue(section,option,value)
    
    def GetUpgradeStrategy(self):
        return self.upgrade_strategy_interface.GetUpgradeStrategy()
    
    def SetUpgradeStrategy(self,input):
        return self.upgrade_strategy_interface.SetUpgradeStrategy(input)
    
    def CancelDownload(self):
        return self.update_interface.CancelDownload()
    
    def GetBackendStatus(self):
        return self.update_interface.GetBackendStatus("")

    def init_dbus_connections(self):
        pass

    def init_config(self):
        pass

    def init_event_flags(self):
        pass

    def init_scheduler(self):
        pass

    def run(self):
        pass

    def prepare_upgrade(self):
        logging.info(_("prepare for upgrade"))
        self.update_detect_event.clear()
        self.update_interface.UpdateDetect()
        self.update_detect_event.wait()
        logging.debug(_("update detect finish:%s,%s")%(self.update_detect_status,",".join(self.update_list)))
        if self.update_detect_status and len(self.update_list)>0:
            pass
        elif not self.update_detect_status and 'kylin-system-updater' in self.update_list:
            logging.info(_("self update finished"))
        else:
            return False
        self.resolve_depend_status_event.clear()
        self.update_interface.DistUpgradeForAuto(ACTION_CHECK_RESOLVER)
        self.resolve_depend_status_event.wait()
        logging.debug(_("resolve dependency status:%s,%s")%(self.resolve_depend_status,",".join(self.remove_pkgs)))
        if self.resolve_depend_status and len(self.remove_pkgs)==0:
            pass
        else:
            return False
        logging.info("disconnect notifysend from downloadfinish and backup result")
        self.notify_service.ConnectDistUpgrade(False)
        needbackup=self.upgrade_strategy_interface.GetAutoBackupStatus()
        if(self.need_check_backup):
            logging.debug(_("checking if need backup:%s")%needbackup)
            if needbackup:
                if self.backup():
                    logging.debug(_("backup success"))
                    ret = self.SetConfigValue("UpdateFrontendConf","backup_exist","True")
                    logging.info(_("set config value :%s")%(ret))
                    restorable_flag="/tmp/update-backup.success"
                    logging.info("create restorable flag:%s"%(restorable_flag))
                    with open(restorable_flag, 'w') as file:
                        file.write("restore")
                else:
                    logging.debug(_("backup failed"))                                                        
                    BackupInfo.update({"errorCode":str(self.backup_finish_status),
                                    "error_string":"备份失败",
                                    "appname":",".join(self.update_list)})
                    BackupInfo.update({"status":"failed"})
                    json_file = json.dumps(BackupInfo.copy())
                    self.DataBackendCollect("BackupInfo",json_file)
                    return False
        return True

    def download(self,timeout):
        logging.debug(_("start download"))
        logging.info("download timeout:%d"%timeout)
        self.download_finish_status=False
        self.download_finish_group=[]
        self.download_finish_status_event.clear()
        errcode,errstr = self.update_interface.DistUpgradeForAuto(ACTION_DOWNLOADONLY)
        logging.info("ret code:%d,ret string:%s"%(errcode,errstr))
        if(errcode!=0):
            return False
        self.download_finish_status_event.wait(timeout=timeout)
        backend_status=self.GetBackendStatus()
        logging.info("backend status:%d"%backend_status)
        if(backend_status==ACTION_DOWNLOADONLY):
            logging.info("cancel download")
            self.CancelDownload()
            time.sleep(1)
        logging.debug(_("download finish status:%s,%s")%(self.download_finish_status,",".join(self.download_finish_group)))
        if self.download_finish_status and len(self.download_finish_group)>0:
            pass
        else:
            return False
        return True 
    
    def install_basic(self):
        logging.debug(_("start install"))   
        self.install_finish_status_event.clear()
        self.update_interface.DistUpgradeForAuto(ACTION_INSTALL)
        self.install_finish_status_event.wait()        
        logging.debug(_("install finish status:%s,%s")%(self.install_finish_status,",".join(self.install_finish_group)))
        if self.install_finish_status and len(self.install_finish_group)>0:
            pass
        else:
            return False
        return True      

    def install(self):
        pass
    
    def download_with_timeout(self,timeout):
        pass
         
    def predownload_with_timeout(self,timeout):
        pass

    def backup(self):
        logging.info(_("start backup"))
        self.backup_proxy = self.system_bus.get_object('com.kylin.backupserver','/',follow_name_owner_changes=True)
        self.backup_interface = dbus.Interface(self.backup_proxy,dbus_interface='com.kylin.backup.server')
        self.backup_proxy.connect_to_signal('sendBackupResult',self.backup_result_handler)                                          
        self.backup_proxy.connect_to_signal('sendRate',self.send_rate_handler) 
        backup_name = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        userName=getpass.getuser()
        uid=os.getuid()
        self.backup_finish_event.clear()
        self.backup_interface.autoBackUpForSystemUpdate_noreturn(backup_name,userName,uid)
        self.backup_finish_event.wait()
        return self.backup_finish_result
    
    def reboot(self):
        subprocess.run(["systemctl", "reboot"], check=True)

    def auto_check_status_changed_handler(self,status):
        pass

    def change_upgrade_strategy_handler(self,diff):
        pass
    
    def change_upgrade_scheme_handler(self,scheme):
        pass
        
    def change_upgrade_policy_handler(self):
        pass

    def autoupgrade_status_changed_handler(self,autoupgradestate):
        pass

    def autoupgrade_detail_changed_handler(self,mask,time):
        pass

    def autoupgrade_period_changed_handler(self,day):
        pass

    def property_changed_handler(self,property, value): 
        pass
        
    def upgrade_all_now_handler(self):
        pass

    def update_detect_finished_handler(self,success,updatelist,error_status,error_cause):
        logging.info(_("update detect finished:sucess:%s,updatelist:%s,error_status:%s,error_cause:%s")\
            %(success,",".join(updatelist),error_status,error_cause))
        self.update_detect_status = success
        self.update_list = updatelist
        self.update_detect_event.set()
        
    def update_fix_broken_status(self,resolver_status,remove_status,remove_pkgs,pkg_raw_description,delete_desc,error_string,error_desc):
        logging.info(_("update fix broken status:resolver_status:%s,remove_status:%s,error_string:%s,error_desc:%s")%(resolver_status,remove_status,error_string,error_desc))
        self.update_detect_status = False
        self.update_list = []
        self.update_detect_event.set()    
        
    def update_depend_resolve_status(self,resolver_status,remove_status,remove_pkgs,pkg_raw_description,delete_description,error_string,error_desc):
        logging.info(_("update depend resove status:%s,remove status:%s,remove pkgs:%s,pkg raw description:%s,delete_descrition:%s,error string:%s,error desc:%s")\
            %(resolver_status,remove_status,",".join(remove_pkgs),",".join(pkg_raw_description),",".join(delete_description),error_string,error_desc))
        self.resolve_depend_status = resolver_status
        self.remove_pkgs = remove_pkgs
        self.resolve_depend_status_event.set()
        self.shutdown_install=self.GetConfigValue("InstatllMode","shutdown_install")
        
    def update_download_install_status(self,group,progress,status,details):
        logging.debug(_("%s update progress:%d,status:%s,details:%s")%(",".join(group),progress,status,details))
        
    def update_install_finished(self,success,group,error_string,error_desc):
        logging.info(_("update install finisih success:%s,group:%s,error string:%s,error desc:%s")\
            %(success,",".join(group),error_string,error_desc))
        self.install_finish_status = success
        self.install_finish_group = group
        self.install_finish_status_event.set()
        self.download_finish_status = success
        self.download_finish_group = group
        self.download_finish_status_event.set()
        logging.info("shutdown install:%s"%(self.shutdown_install))
        if(self.shutdown_install=='True'):
            errcodelist=["#0208","#0204"]
            if(error_string in errcodelist):
                logging.info("error string is %s,do not clear the delay count and button hide status")
            else:
                logging.info("reset hideupdate and delaycount")
                strategy_data={
                    "autoupdate":{
                        "hideupdate":False,
                        "delaycount":0                   
                        }
                    } 
                self.SetUpgradeStrategy(json_to_asv(strategy_data))
        
    def update_download_finished(self,success,group,error_string,error_desc):
        logging.info(_("update download finisih success:%s,group:%s,error string:%s,error desc:%s")\
            %(success,",".join(group),error_string,error_desc))
        self.download_finish_status = success    
        self.download_finish_group = group
        self.download_finish_status_event.set()
                        
    def backup_result_handler(self,result,status):
        logging.debug(_("backup result:%s,status:%d")%(result,status))                  
        self.backup_finish_result = result 
        self.backup_finish_status = status
        self.backup_finish_event.set()
        
    def send_rate_handler(self,sta,pro):
        logging.debug(_("backup status:%d,progress:%d")%(sta,pro))    

class UnattendedUpgradeBackend(UnattendedUpgradesShutdown):
    def __init__(self):
        super().__init__()

    def init_config(self):
        updagradestrategy=self.GetUpgradeStrategy()
        configjsonstr=convert_a_sv_to_json(updagradestrategy)
        logging.info(configjsonstr)
        self.config={}
        self.config=json.loads(configjsonstr)
    
    def init_scheduler(self):
        self.background_scheduler=BackgroundScheduler()
        self.background_scheduler.start()
        self.schedule_task()
        with open("/var/log/kylin-unattended-upgrades/unattended-upgrades-shutdown.log",'a+') as f:
            self.background_scheduler.print_jobs(out=f)  
        
    def schedule_task(self):
        try:
            updatemode=self.config.get('updatemode','off')
            logging.info("upgrade mode:%s"%updatemode)
            if(updatemode=="off"):
                pass
            elif(updatemode=="autoupdate"):
                autoupdateconfig=self.config.get("autoupdate",{})
                downloadtime=autoupdateconfig.get("downloadtime",[])
                downloadrandom=autoupdateconfig.get("downloadtimerandom",120)
                period=autoupdateconfig.get("period",1)
                mode=autoupdateconfig.get("mode",1)
                timelist=process_timelist(process_time_intervals(downloadtime,downloadrandom))
                logging.info(timelist)
                downloadtimestamp=ReadValueFromFile(UNATTENDED_UPGRADE_TIMESTAMP,'TimeStamp','download')
                if not downloadtimestamp:
                    now = datetime.now() 
                    downloadtimestamp = now.strftime("%Y-%m-%d %H:%M:%S")
                dtime=datetime.strptime(downloadtimestamp, "%Y-%m-%d %H:%M:%S")
                for t in timelist:
                    taskdate=(dtime+timedelta(days=float(period))).replace(hour=t.get("hour",0),minute=t.get("minute",0))
                    task_id="download_task_%s"%(taskdate.strftime("%Y-%m-%d %H:%M:%S"))
                    self.background_scheduler.add_job(self.download_with_timeout,args=[t.get("timeout",30)*60],trigger='interval',days=period,start_date=taskdate,id=task_id,replace_existing=True)
                if(mode=="timing"):
                    installtimestamp=ReadValueFromFile(UNATTENDED_UPGRADE_TIMESTAMP,'TimeStamp','install')
                    if not installtimestamp:
                        now = datetime.now() 
                        installtimestamp = now.strftime("%Y-%m-%d %H:%M:%S")
                    itime=datetime.strptime(installtimestamp, "%Y-%m-%d %H:%M:%S")
                    installtime=autoupdateconfig.get("installtime","20:00")
                    installtimedict=parse_time(installtime)
                    logging.info(installtimedict)
                    taskdate=(itime+timedelta(days=float(period))).replace(hour=installtimedict.get("hour",0),minute=installtimedict.get("minute",0))
                    task_id="install_task_%s"%(taskdate.strftime("%Y-%m-%d %H:%M:%S"))
                    self.background_scheduler.add_job(self.install,trigger='interval',days=period,start_date=taskdate,id=task_id,replace_existing=True)
            elif(updatemode=="predownload"):
                autoupdateconfig=self.config.get("predownload",{})
                downloadtime=autoupdateconfig.get("downloadtime",[])
                downloadrandom=autoupdateconfig.get("downloadtimerandom",120)
                period=autoupdateconfig.get("period",1)
                timelist=process_timelist(process_time_intervals(downloadtime,downloadrandom))
                logging.info(timelist)
                downloadtimestamp=ReadValueFromFile(UNATTENDED_UPGRADE_TIMESTAMP,'TimeStamp','predownload')
                if not downloadtimestamp:
                    now = datetime.now() 
                    downloadtimestamp = now.strftime("%Y-%m-%d %H:%M:%S")
                dtime=datetime.strptime(downloadtimestamp, "%Y-%m-%d %H:%M:%S")
                for t in timelist:
                    taskdate=(dtime+timedelta(days=float(period))).replace(hour=t.get("hour",0),minute=t.get("minute",0))
                    task_id="predownload_task_%s"%(taskdate.strftime("%Y-%m-%d %H:%M:%S"))
                    self.background_scheduler.add_job(self.predownload_with_timeout,args=[t.get("timeout",30)*60],trigger='interval',days=period,start_date=taskdate,id=task_id,replace_existing=True)
            elif(updatemode=="updatenow"):            
                autoupdateconfig=self.config.get("updatenow",{})
                downloadrandom=autoupdateconfig.get("downloadtimerandom",120)
                delay = random.uniform(0, downloadrandom)
                delta = timedelta(minutes=delay)
                taskdate=datetime.now() + delta
                self.background_scheduler.add_job(self.install,'date',run_date=taskdate)
            else:
                pass 
        except Exception as e:
            logging.error(e) 

    def install(self):
        self.need_check_backup=True
        if(self.prepare_upgrade()):
            if(self.install_basic()):
                updatemode=self.config.get("updatemode","autoupdate")
                logging.info("updatemode:%s"%updatemode)
                if(updatemode=="autoupdate"):
                    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                    UpdateTimeStamp('install',now)
                    postaction=self.config.get("autoupdate",{}).get("postaction",'no')
                    logging.info("postaction:%s"%postaction)
                    if(postaction=="notify"):
                        self.notify_service.InstallFinishNotify(0,"install finished")
                    elif(postaction=="reboot"):
                        self.reboot()  
                elif(updatemode=="updatenow"):
                    postaction=self.config.get("autoupdate",{}).get("postaction",'no')
                    logging.info("postaction:%s"%postaction)
                    if(postaction=="notify"):
                        self.notify_service.InstallFinishNotify(0,"install finished")
                    elif(postaction=="reboot"):
                        self.reboot()  
                             
    def download_with_timeout(self,timeout):
        self.need_check_backup=False
        wait_timeout=timeout
        start_time=time.time()
        if(self.prepare_upgrade()):
            end_time=time.time()
            time_diff=end_time-start_time
            if(wait_timeout>time_diff):
                wait_timeout=wait_timeout-time_diff
                logging.info("timeout:%d,time diff:%d"%(wait_timeout,time_diff))
            if(self.download(wait_timeout)):
                now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                UpdateTimeStamp('download',now)
                mode=self.config.get("autoupdate",{}).get("mode","timing")
                logging.info("auto update install mode:%s"%mode)
                if(mode=="shutdown"):
                    imode=self.GetConfigValue("InstallMode","shutdown_install")
                    logging.info("install mode:%s"%imode)
                    if(imode=="True"):
                        logging.info("send reboot notify signal")
                        self.notify_service.RebootNotify(0,"download finish")
                        pass
         
    def predownload_with_timeout(self,timeout):
        self.need_check_backup=False
        wait_timeout=timeout
        start_time=time.time()
        if(self.prepare_upgrade()):
            end_time=time.time()
            time_diff=end_time-start_time
            logging.info("timeout:%d,time diff:%d"%(wait_timeout,time_diff))
            if(wait_timeout>time_diff):
                wait_timeout=wait_timeout-time_diff
            if(self.download(wait_timeout)):
                now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                UpdateTimeStamp('predownload',now)

    def change_upgrade_strategy_handler(self, diff):  
        logging.info("upgrade strategy changed:%s"%diff)
        json_diff=json.loads(diff)
        updagradestrategy=self.GetUpgradeStrategy()
        configjsonstr=convert_a_sv_to_json(updagradestrategy)
        logging.info(configjsonstr)
        self.config=json.loads(configjsonstr)
        updatemode=self.config.get("updatemode","off")
        logging.info("upgrade mode:%s"%updatemode)
        added=json_diff.get("added",{})
        modified=json_diff.get("modified",{})
        if(not added and not modified):      
            logging.info("no changes of strategy")
        else:
            logging.info("strategy changed reschedule tasks")
            self.background_scheduler.remove_all_jobs()
            self.schedule_task()
        with open("/var/log/kylin-unattended-upgrades/unattended-upgrades-shutdown.log",'a+') as f:
            self.background_scheduler.print_jobs(out=f)  

    def property_changed_handler(self, property, value):
        logging.info("property changed %s:%s"%(property,value))
        self.change_upgrade_policy_handler()

    def change_upgrade_policy_handler(self):
        logging.info("upgrade policy changed")
        json_data=ini_to_json(UNATTENDED_UPGRADE_POLICY_FILE_PATH)
        logging.info(json_data)
        data={}
        autoupgradepolicy=json_data.get('autoUpgradePolicy',{})
        updatedays=int(autoupgradepolicy.get('updatedays',1))
        randomrange=int(autoupgradepolicy.get('randomrange',1))
        if(autoupgradepolicy.get('autoupgradestate','off')=='on'):
            data.update({'updatemode':'autoupdate'})
            downloadmode=autoupgradepolicy.get('downloadmode','timing')
            downloadtime=parse_time_range(autoupgradepolicy.get('downloadtime','11:00-12:00'))
            installmode=autoupgradepolicy.get('installmode','timing')
            installtime=parse_time_range(autoupgradepolicy.get('installtime','10:00-11:00'))
            autoupdate={}
            if(downloadmode=='timing'):
                autoupdate.update({'downloadtime':[downloadtime]})
            if(installmode=='timing'):
                autoupdate.update({'mode':'timing'})
                autoupdate.update({'installtime':installtime.get('start',"10:00")})
            elif(installmode=='bshutdown'):
                autoupdate.update({'mode':'shutdown'})
            autoupdate.update({'downloadtimerandom': randomrange})
            autoupdate.update({'period':updatedays})
            data.update({'autoupdate':autoupdate})
        elif(autoupgradepolicy.get('predownload','off')=='on'):
            data.update({'updatemode':'predownload'})
            predownloadtime=parse_time_range(autoupgradepolicy.get('predownloadtime','10:00-11:00'))
            predownload={}
            predownload.update({"downloadtime":[predownloadtime]})
            predownload.update({'downloadtimerandom': randomrange})
            predownload.update({'period':updatedays})
            data.update({"predownload":predownload})
        else:
            data.update({'updatemode':'off'})
        self.SetUpgradeStrategy(json_to_asv(data))
    
    def change_upgrade_scheme_handler(self,scheme):
        def generate_time_output(input_data: dict) -> dict:
            """
            处理时间字段并生成指定格式的字典输出
            
            参数：
            input_data - 包含download_time和install_time的原始数据
            
            返回：
            动态生成的字典，仅包含有效数据项
            
            规则：
            1. 当download_time列表非空时，生成downloadtime字段（键名转换）
            2. 当install_time列表非空时，提取第一个元素的start_time生成installtime字段
            3. 空列表或不存在字段时自动过滤
            """
            output = {}
            
            # 处理download_time（键名转换逻辑）
            download_periods = [
                {"start": item["start_time"], "end": item["end_time"]}
                for item in input_data.get("download_time", [])
                if isinstance(item, dict) and "start_time" in item and "end_time" in item
            ]
            if download_periods:  # 仅当列表非空时生成字段
                output["downloadtime"] = download_periods
            
            # 处理install_time（首项提取逻辑）
            install_items = input_data.get("install_time", [])
            if install_items and isinstance(install_items[0], dict):  # 检查列表非空且首项有效
                first_install = install_items[0]
                if "start_time" in first_install:
                    output["installtime"] = first_install["start_time"]
            
            return output        
        configjsonstr=convert_a_sv_to_json(scheme)
        logging.info("upgrade scheme changed")
        logging.info(configjsonstr)
        json_data=json.loads(configjsonstr)
        data={}
        if(json_data.get('auto_download','true')=='true'):
            data.update({'updatemode':'autoupdate'})
            autoupdate=generate_time_output(json_data)
            period=int(json_data.get('download_period',{}).get('day',1))
            mode=json_data.get('mode','shutdown')
            autoupdate.update({"mode":mode}) 
            autoupdate.update({"period":period})
            data.update({"autoupdate":autoupdate})
        elif(json_data.get('auto_download','false')=='false'):
            data.update({'updatemode':'off'})
        self.SetUpgradeStrategy(json_to_asv(data))

    def auto_check_status_changed_handler(self, status):
        logging.info("is instance of bool:%s"%isinstance(status,bool))
        data={}
        if(status):
            data.update({"notification":bool(1)})    
        else:
            data.update({"notification":bool(0)})
        self.SetUpgradeStrategy(json_to_asv(data))

    def autoupgrade_status_changed_handler(self,autoupgradestate):
        logging.info("auto upgrade status changed:%s"%autoupgradestate)

    def autoupgrade_detail_changed_handler(self,mask,time):
        logging.info("auto upgrade detail changed:%s,%s"%(mask,time))

    def autoupgrade_period_changed_handler(self,day):
        logging.info("auto upgrade period changed:%d"%day)
        
    def upgrade_all_now_handler(self):
        logging.info("upgrade all now")
        data={}
        data.update({'updatemode':'updatenow'})
        self.SetUpgradeStrategy(json_to_asv(data))

    def run(self):
        logging.debug(_("Waiting for signal to start operation"))
        self.loop.run() 

    def init_project_config(self):
        try:
            pkg="kylin-update-desktop-config"
            logging.info("check %s version"%pkg)
            success, result = self.get_package_special_id(pkg)
            logging.info("check project info result:%s,id:%s"%(success,result))
            version="none"
            version=result.lower()
            if(version=="hg" or version=="hw.hg"):
                logging.info("set hg project json config")
                refreshjsonflag="/var/lib/hg-json-refresh-flag"        
                if(os.path.exists(refreshjsonflag)):
                    logging.info("/var/lib/hg-json-refresh-flag exists,already set kylin-update-strategy.json,pass")
                else:
                    logging.info("set hg project json config")
                    data={}
                    with open("/var/lib/unattended-upgrades/kylin-update-strategy.json", 'r') as file1:
                        data = json.load(file1)
                    data["updatemode"]="autoupdate"
                    data["autoupdate"]["mode"]="shutdown"
                    data["autoupdate"]["period"]=1
                    data["autoupdate"]["downloadtimerandom"]=120
                    data["autoupdate"]["downloadtime"]=[{"start":"11:00", "end":"14:00"}]                 
                    with open('/var/lib/unattended-upgrades/kylin-update-strategy.json', 'w') as file2:
                        json.dump(data, file2, indent=4) 
                    with open(refreshjsonflag,'w') as f:
                        f.write("kylin-update-strategy.json already refreshed") 
                    cmd="/usr/share/kylin-system-updater/SystemUpdater/Core/quitStrategiesService.py"
                    subret=subprocess.run(cmd,shell=True,capture_output=True,text=True,check=True) 
                    logging.info(subret)
        except Exception as e:
            logging.info("init project config error",e)  

    def get_package_special_id(self,package_name):
        """
        获取Ubuntu系统软件包版本号中的特殊项目标识
        
        :param package_name: 软件包名称（如"kylin-update-desktop-config"）
        :return: (状态, 结果)元组，成功返回(True, 标识符)，失败返回(False, 错误信息)
        """
        try:
            # 使用dpkg-query查询软件包版本号
            result = subprocess.run(
                ['dpkg-query', '-W', '-f=${Version}', package_name],
                capture_output=True,
                text=True,
                check=True,
                timeout=5  # 设置5秒超时
            )
            
            version = result.stdout.strip()
            if not version:
                return False, "获取的版本号为空"
            elif "hw.hg" in version:
                return True,"hg"
            elif "hg" in version:
                return True,"hg"
            # 正则表达式匹配特殊标识符
            # 规则：匹配长度≥2的字母组合，前后必须有数字或边界
            pattern = r'(?:^|[.-])\d*([a-zA-Z]{2,})(?=\d|\.|$)'
            match = re.search(pattern, version)
            
            return True, match.group(1) if match else "none"
        
        except Exception as e:
            # 处理包不存在或查询失败
            error_msg = e.stderr.strip() or "软件包查询失败"
            if "no packages found" in error_msg.lower():
                return False, f"软件包'{package_name}'未安装"
            return False, error_msg
        
        except subprocess.TimeoutExpired:
            return False, "查询超时，请检查dpkg-query是否可用"
        
        except Exception as e:
            return False, f"未知错误: {str(e)}"

if __name__ == "__main__":
    gettext.bindtextdomain("unattended-upgrades","/usr/share/locale")
    gettext.textdomain("unattended-upgrades")
    logdir = "/var/log/kylin-unattended-upgrades/"
    if not os.path.exists(logdir):
        os.makedirs(logdir)
    logfile = os.path.join(logdir, "unattended-upgrades-shutdown.log")
    logging.basicConfig(format='%(asctime)s-%(name)s-%(levelname)s-%(message)s',datefmt='%Y-%m-%d,%H:%M:%S',level=logging.DEBUG,filename=logfile)
    signal.signal(signal.SIGTERM, signal_term_handler)
    unattendedupgrade=UnattendedUpgradeBackend()
    unattendedupgrade.init_project_config()
    unattendedupgrade.init_dbus_connections()
    unattendedupgrade.init_config()
    unattendedupgrade.init_event_flags()
    unattendedupgrade.init_scheduler()
    unattendedupgrade.run()
   