# /*
#  * Copyright (C) 2020, KylinSoft Co., Ltd.
#  *
#  * This program is free software: you can redistribute it and/or modify
#  * it under the terms of the GNU General Public License as published by
#  * the Free Software Foundation, either version 3 of the License, or
#  * (at your option) any later version.
#  *
#  * This program is distributed in the hope that it will be useful,
#  * but WITHOUT ANY WARRANTY; without even the implied warranty of
#  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  * GNU General Public License for more details.
#  *
#  * You should have received a copy of the GNU General Public License
#  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
#  */

#!/usr/bin/python3
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
# DistUpgradeConfigParser.py 
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
#  USA


from configparser import NoOptionError,NoSectionError
from configparser import ConfigParser as SafeConfigParser
import os.path
import os
import logging
from typing import Dict,Any
from pathlib import Path
from copy import deepcopy
import json

class UpgradeConfig(SafeConfigParser):
    def __init__(self, datadir="/var/lib/kylin-system-updater/", name="system-updater.conf",defaults_conf=None):
        SafeConfigParser.__init__(self)
        self.datadir = datadir
        maincfg = os.path.join(datadir, name)
        # defaults are read first
        self.config_files = []
        if defaults_conf:
            self.config_files.append(defaults_conf)
        # our config file
        self.config_files += [maincfg]
        for conf in self.config_files:
            if not os.path.exists(conf):
                logging.warning("Check: Conf(%s) not exits...",conf)
        self.read(self.config_files, encoding='iso-8859-1')
        logging.info("Initialize Upgrade ConfigFile(%s) to success",str(self.config_files))

    def optionxform(self, optionstr):
        return optionstr
        
    def reRead(self):
        self.read(self.config_files, encoding='iso-8859-1')

    def _write(self):
        with open(self.config_files[-1], 'w+') as configfile:
            self.write(configfile)

    def setValue(self, section, option, value=None,is_write = True):
        logging.info("SetValue Section:%s Option:%s Value:%s",section, option, value)
        try:
            self.reRead()
            self.set(section, option,value)
        except Exception as e:
                logging.error("Error: setValue section:%s option:%s value:%s",section, option, value)
                logging.error(str(e))
                return False
        if is_write == True:
            self._write()
        return True

    def getWithDefault(self, section, option, default,re_read=False):
        try:
            if re_read == True:
                self.reRead()

            if type(default) == bool:
                return self.getboolean(section, option)
            elif type(default) == float:
                return self.getfloat(section, option)
            elif type(default) == int:
                return self.getint(section, option)
            return self.get(section, option)
        except NoOptionError as e:
            logging.info("NoOption: %s", e)
            return default
        except NoSectionError as e:
            logging.info("NoSection: %s", e)
            return default
        except Exception as e:
            logging.warning(str(e))
            return default

    def getlist(self, section, option):
        try:
            tmp = self.get(section, option)
        except Exception as e:
            logging.error(str(e))
            return []
        items = [x.strip() for x in tmp.split(" ")]
        if '' in items and len(items):
            return []
        return items

    def setListValue(self, section, option, value=None,is_write = True):
        tmp = str(value).replace('[', '').replace(']', '')
        tmp = tmp.replace("'", '').replace(',', '')
        try:
            self.set(section, option,tmp)
        except Exception as e:
            logging.error("setListValue section:%s option:%s",section, option)
            logging.error(str(e))
            return
        if is_write == True:
            self._write()

    def getListFromFile(self, section, option):
        try:
            filename = self.get(section, option)
        except NoOptionError:
            return []
        p = os.path.join(self.datadir, filename)
        if not os.path.exists(p):
            logging.error("getListFromFile: no '%s' found" % p)
        with open(p) as f:
            items = [x.strip() for x in f]
        return [s for s in items if not s.startswith("#") and not s == ""]

class UpgradeJsonManager():
    def __init__(self,filelist=["/var/lib/unattended-upgrades/kylin-update-strategy.json"]):
        initial_data=self.merge_files(filelist)
        self._data = deepcopy(initial_data) if initial_data else {}

    def reload_config(self,filelist=["/usr/share/kylin-system-updater/kylin-update-strategy.json"]):
        initial_data=self.merge_files(filelist)
        self._data = deepcopy(initial_data) if initial_data else {}
        
    def merge_files(self,file_list: list) -> dict:
        """
        合并多个JSON文件内容，遇到异常时返回空字典

        参数:
            file_list: 需要合并的文件路径列表

        返回:
            dict: 合并后的字典数据，异常时返回{}
        """
        merged_data = {}

        for file_path in file_list:
            try:
                # 检查文件存在性
                if not os.path.exists(file_path):
                    raise FileNotFoundError(f"文件 {file_path} 不存在")

                # 检查文件可读性
                if not os.access(file_path, os.R_OK):
                    raise PermissionError(f"无权限读取文件 {file_path}")

                # 读取并解析文件
                with open(file_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    merged_data.update(data)

            except (FileNotFoundError, PermissionError) as e:
                (f"[错误] {str(e)}")
                return {}
            except json.JSONDecodeError:
                logging.error(f"[错误] 文件 {file_path} 不是有效的JSON格式")
                return {}
            except Exception as e:
                logging.error(f"[未知错误] 处理文件 {file_path} 时发生意外错误: {str(e)}")
                return {}

        return merged_data
    
        # 直接返回当前JSON对象（深拷贝安全模式）
    
    def get_json(self, indent: int = None) -> str:
        """
        输出内部JSON对象的字符串形式
        
        参数:
            indent (int): 缩进空格数，None表示紧凑格式
            
        返回:
            str: JSON格式字符串
        """
        return json.dumps(
            self.data,  # 使用@property的深拷贝
            indent=indent,
            ensure_ascii=False,
            default=str  # 处理无法序列化的对象
        )
    
    def get_json_object(self) -> dict:
        """
        返回内部JSON对象的深拷贝副本
        
        返回:
            dict: 原始数据结构，包含当前所有键值对
        """
        return self.data  # 复用已存在的安全机制
    
    def update_and_diff(self, new_data: Dict[str, Any]) -> Dict[str, Dict]:
        """
        更新数据并输出差异
        返回结构示例：
        {
            "added": {"new_key": "value"},
            "modified": {"existing_key": {"old": "value", "new": "updated"}}
        }
        """
        diff_result = {"added": {}, "modified": {}}
        self._recursive_merge(self._data, new_data, diff_result)
        return diff_result
    
    def _recursive_merge(self, internal: Dict, external: Dict, diff: Dict, path: str = "") -> None:
        """深度递归合并并记录差异"""
        for key, ext_value in external.items():
            current_path = f"{path}.{key}" if path else key
            
            # 处理新增键
            if key not in internal:
                internal[key] = deepcopy(ext_value)
                diff["added"][current_path] = ext_value
                continue
                
            int_value = internal[key]
            # 处理嵌套字典
            if isinstance(int_value, dict) and isinstance(ext_value, dict):
                self._recursive_merge(int_value, ext_value, diff, current_path)
            # 处理值差异
            elif int_value != ext_value:
                diff["modified"][current_path] = {
                    "old": deepcopy(int_value),
                    "new": deepcopy(ext_value)
                }
                internal[key] = deepcopy(ext_value)

    def save_to_file(self, file_path: str = "/var/lib/unattended-upgrades/kylin-update-strategy.json", indent: int = None, ensure_ascii: bool = False) -> bool:
        """
        将内部JSON对象保存到指定文件
        
        参数:
            file_path: 目标文件路径（支持绝对/相对路径）
            indent: 缩进空格数（None为紧凑格式）
            ensure_ascii: 是否转义非ASCII字符）
            
        返回:
            bool: 保存是否成功
        """
        try:
            # 创建目录
            directory = os.path.dirname(file_path)
            if directory and not os.path.exists(directory):
                os.makedirs(directory, exist_ok=True)
                
            # 深拷贝后写入
            with open(file_path, 'w', encoding='utf-8') as f:
                json.dump(
                    self.data,  # 使用@property的深拷贝
                    f,
                    indent=indent,
                    ensure_ascii=ensure_ascii,
                    separators=(',', ': '),
                    default=str  # 处理不可序列化对象
                )
            return True
        except (IOError, PermissionError) as e:
            logging.error(f"文件保存失败: {str(e)}")
            return False
        except Exception as e:
            logging.error(f"未知错误: {str(e)}")
            return False

    @property
    def data(self) -> Dict[str, Any]:
        """获取数据的深拷贝副本"""
        return deepcopy(self._data)

    def __str__(self) -> str:
        """美化输出"""
        return json.dumps(self._data, indent=2, ensure_ascii=False)
    
if __name__ == "__main__":
    pass
