#!/usr/bin/python3
import yaml, sqlite3
import os, re, shutil
import logging, traceback

import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib

# 创建一个DBus服务类
class KYSettingsDBusService(dbus.service.Object):
    __read_only_view = {}
    __root_view = {}

    def __init__(self, bus_name, object_path):
        dbus.service.Object.__init__(self, bus_name, object_path)

        os.makedirs('/etc/kylin-config', exist_ok=True)
        #初始化日志模块
        logging.basicConfig(filename='/etc/kylin-config/systembus.log', level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s \n %(message)s')
        self.logger = logging.getLogger('/etc/kylin-config/systembus.log')

        self.__creat_db_file__()
        

    #读配置文件，创建只读视图数据库和root用户的数据库
    def __creat_db_file__(self):
        try:
            #加载只读配置，写入数据库
            self.__load_read_only_view__()
            self.__dict2db__(self.__read_only_view, '/etc/kylin-config/user.db')

            #copy文件到root用户目录
            if os.path.exists('/etc/kylin-config/user.db'):
                if not os.path.exists('/root/.config/kylin-config/user.db'):
                    os.makedirs(os.path.dirname('/root/.config/kylin-config/'), exist_ok=True)
                    shutil.copyfile('/etc/kylin-config/user.db', '/root/.config/kylin-config/user.db')
                    self.__root_view = self.__format_db_file('/root/.config/kylin-config/user.db')
                else:
                    self.__root_view = self.__format_db_file('/root/.config/kylin-config/user.db')
                    self.__del_discarded_key(self.__root_view, self.__read_only_view)
                    self.__merge_dict(self.__root_view, self.__read_only_view)
                    self.__dict2db__(self.__root_view, '/root/.config/kylin-config/user.db')
        except Exception as e:
            self.logger.error(traceback.format_exc())

#统一视图写入数据库
    #递归寻找枚举
    def __recursive_search__(self, dictionary, key):
        if key in dictionary:
            return dictionary[key]
        else:
            for value in dictionary.values():
                if isinstance(value, dict):
                    result = self.__recursive_search__(value, key)
                    if result is not None:
                        return result
        return None

    #递归写入配置数据到配置表
    def __write_element_in_table__(self, version_data:dict ,data: dict, cursor: sqlite3.Cursor, version_id, parent_id):
        #key写入到数据库
        if 'keys' in data:
            keys = data['keys']
            for key in keys:
                property = key

                user_value = keys[key].get('value')
                # if user_value is None:
                #     user_value = keys[key].get('default')

                type = keys[key].get('_type')

                default_value = keys[key].get('default')
                if isinstance(default_value, str) is False:
                    default_value = str(default_value)

                description = keys[key].get('description')

                summary = keys[key].get('summary')

                permission = keys[key].get('permission')
                if permission is None:
                    permission = 'public'

                #TODO: 计算range取值范围
                range = keys[key].get('range')
                # reload时，没有更新的枚举数据的range应该是枚举参数的字符串列表
                # 更新后或初始化时的枚举数据的range是@[枚举名]
                if type == 'enum' and isinstance(range, str) and range.startswith('@'):
                    enum_range = {}
                    element_list = self.__recursive_search__(version_data,keys[key]['range'][1:])
                    for element in element_list:
                        enum_range[element['nick']] = int(element['value'])
                    range = str(enum_range)
                    
                cursor.execute('''INSERT INTO configures (version_id, property, user_value, data_type, default_value, permission, range, parent, description, summary) 
                            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',(version_id, property, user_value, type, default_value, permission, range, parent_id, description, summary))
        if 'children' in data:
            children = data['children']
            for child in children:
                if child.startswith('enum_'):
                    continue
                cursor.execute("INSERT INTO configures (version_id,group_name,parent) VALUES (?, ?, ?)",(version_id,child,parent_id))
                group_id = cursor.lastrowid
                self.__write_element_in_table__(version_data,children[child],cursor,version_id,group_id)

    #创建app表
    def __covert_app_table__(self,data: dict, cursor: sqlite3.Cursor):
        # 创建app表
        cursor.execute('''CREATE TABLE IF NOT EXISTS app
                        (id INTEGER PRIMARY KEY AUTOINCREMENT,
                        app_name TEXT,
                        defualt_version TEXT)''')
        #将app信息写入app表
        for app in data.keys():
            default_version = data[app].get('default_version')
            # 如果没有设置默认版本，将默认版本设置为最大版本
            if default_version is None:
                for version in data[app]:
                    if default_version is None:
                        default_version = version
                    #比较版本 版本目前认为是2.0.0-0k0格式
                    default_list = re.findall(r'\d+', default_version)
                    list = re.findall(r'\d+', version)
                    for i in range(0,6):
                        if len(default_list) == i:
                            default_version = version
                            break
                        if len(list) == i:
                            break
                        if int(default_list[i]) < int(list[i]):
                            default_version = version
                            break
                        if int(default_list[i]) > int(list[i]):
                            break
            cursor.execute("INSERT INTO app (app_name, defualt_version) VALUES (?,?)",(app, default_version))

    #创建版本表
    def __covert_version_table__(self,data: dict, cursor: sqlite3.Cursor):
        # 创建version表
        cursor.execute('''CREATE TABLE IF NOT EXISTS version
                       (id INTEGER PRIMARY KEY AUTOINCREMENT,
                       app_id INTEGER,
                       version TEXT,
                       compatible TEXT,
                       FOREIGN KEY (app_id) REFERENCES app(id))''')
        for app in data.keys():
            cursor.execute("SELECT id FROM app WHERE app_name = ?", (app,))
            appId = cursor.fetchone()
            for version in data[app].keys():
                if(version == 'default_version'):
                    continue
                cursor.execute("INSERT INTO version (app_id,version, compatible) VALUES (?, ?, ?)",(appId[0],version, data[app][version].get('compatible')))

    #创建配置表
    def __covert_configure_table__(self, data: dict, cursor: sqlite3.Cursor):
        # 创建version表
        cursor.execute('''CREATE TABLE IF NOT EXISTS configures
                        (id INTEGER PRIMARY KEY AUTOINCREMENT,
                        version_id INTEGER,
                        property TEXT,
                        group_name TEXT,
                        user_value TEXT,
                        data_type TEXT,
                        default_value TEXT,
                        permission TEXT,
                        range TEXT,
                        parent INTEGER,
                        description TEXT,
                        summary TEXT,
                        FOREIGN KEY (version_id) REFERENCES version(id),
                        FOREIGN KEY (parent) REFERENCES configures(id))''')
        cursor.execute("SELECT * FROM app")
        apps = cursor.fetchall()
        for app in apps:
            cursor.execute("SELECT * FROM version WHERE app_id = ?", (app[0],))
            versions = cursor.fetchall()
            for version in versions:
                version_data = data[app[1]][version[2]]
                #keys写入数据库
                self.__write_element_in_table__(version_data,version_data,cursor,version[0],0)

    #将字典中的配置数据写入数据库
    def __dict2db__(self, dict, path):
        try:
            # 连接到数据库（如果不存在则创建）
            db_file_path =  path.rsplit('/',maxsplit=1)[0]
            if not os.path.exists(db_file_path):
                os.makedirs(db_file_path)
            conn = sqlite3.connect(path)

            # 创建游标对象
            cursor = conn.cursor()
            conn.execute('BEGIN TRANSACTION')

            # 获取数据库中的所有表名
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
            tables = cursor.fetchall()

            # 遍历所有表名，对每个表执行DELETE语句以删除所有行
            for table in tables:
                cursor.execute(f"DELETE FROM {table[0]}")
                # cursor.execute(f"DELETE FROM sqlite_sequence WHERE name={table[0]}")

            self.__covert_app_table__(dict,cursor)
            self.__covert_version_table__(dict,cursor)
            self.__covert_configure_table__(dict,cursor)

            # 提交更改
            conn.commit()
            conn.close()
            return 0
        
        except sqlite3.Error as e:
            conn.rollback()
            conn.close()
            self.logger.error(traceback.format_exc())
            return -1

#读取数据库数据到内存
    #递归读取数据库数据到字典
    def __format_element(self, parent: dict, cursor: sqlite3.Cursor, version_id, parent_id):
        keys = {}
        children = {}
        cursor.execute('SELECT * FROM configures WHERE version_id = ? AND parent = ?', (version_id,parent_id))
        configure_data = cursor.fetchall()
        for configure in configure_data:
            #读取子分组
            if configure[3] is not None:
                children[configure[3]] = {}
                self.__format_element(children[configure[3]],cursor,version_id,configure[0])
            #读取key
            else:
                keys[configure[2]] = {}
                if configure[4] is not None:
                    keys[configure[2]]['value'] = configure[4]
                if configure[5] is not None:
                    keys[configure[2]]['_type'] = configure[5]
                if configure[6] is not None:
                    keys[configure[2]]['default'] = configure[6]
                if configure[7] is not None:
                    keys[configure[2]]['permission'] = configure[7]
                if configure[8] is not None:
                    keys[configure[2]]['range'] = configure[8]
                if configure[10] is not None:
                    keys[configure[2]]['description'] = configure[10]
                if configure[11] is not None:
                    keys[configure[2]]['summary'] = configure[11]
        if keys:
            parent['keys'] = keys
        if children:
            parent['children'] = children

    #将数据库中的内容读取到数据结构中 
    def __format_db_file(self, db_file: str):
        try:
            data = {}
            # 连接到数据库（如果不存在则创建）
            conn = sqlite3.connect(db_file)
            if conn is None:
                return data
            # 创建游标对象
            cursor = conn.cursor()
            conn.execute('BEGIN TRANSACTION')

            #遍历app表
            cursor.execute('SELECT * FROM app')
            app_data = cursor.fetchall()
            for app in app_data:
                data[app[1]] = {}
                cursor.execute('SELECT * FROM version WHERE app_id = ?', (app[0],))
                version_data = cursor.fetchall()
                for version in version_data:
                    data[app[1]][version[2]] = {}
                    self.__format_element(data[app[1]][version[2]],cursor,version[0],0)
            conn.close()
            return data
        except Exception as e:
            conn.rollback()
            conn.close()
            self.logger.error(traceback.format_exc())
            return {}
    
    #读取yaml配置文件到字典
    def __load_config__(self, yaml_dir):
        if not os.path.exists(yaml_dir):
            return {}
        yaml_data = {}
        for root,dirs,files in os.walk(yaml_dir):
            for file in files:
                if file.endswith('.yaml'):
                    with open(f'{yaml_dir}/{file}','r') as tmp_file:
                        yaml_str = tmp_file.read()
                    tmp_data = yaml.safe_load(yaml_str)
                    yaml_data.update(tmp_data)
        return yaml_data
    
    #合并、覆盖两个字典
    def __merge_dict(self, dest:dict , src:dict):
        if src is None:
            return dest
        for key, value in src.items():
            if key in dest and isinstance(dest[key], dict) and isinstance(src[key], dict):
                self.__merge_dict(dest[key], src[key])
            else:
                dest[key] = value
        return dest
    
    def __del_discarded_key(self, dest:dict, src:dict):
        keys_to_delete = []
        for key in dest:
            if not isinstance(dest[key], dict):
                return
            if key not in src:
                keys_to_delete.append(key)
            else:
                self.__del_discarded_key(dest[key], src[key])

        for key in keys_to_delete:
            dest.pop(key)
    
    #统一配置文件内容覆盖
    def __common_override(self, dest:dict, src:dict):
        if 'children' not in dest:
            return
        for group in src:
            if group in dest['children']:
                for key in src[group]['keys']:
                    if key in dest['children'][group]['keys']:
                        dest['children'][group]['keys'][key].update(src[group]['keys'][key])
                # self.__merge_dict(dest['children'][group], src[group])
        for child in dest['children']:
            if child != group:
                self.__common_override(dest['children'][child], src)

    # 加载只读配置，更新只读视图数据库文件
    def __load_read_only_view__(self):
        self.__read_only_view.clear()
        # 只读文件目录
        self.__read_only_view.update(self.__load_config__('/etc/kylin-config/basic'))

        data = self.__load_config__('/etc/kylin-config/production')
        self.__merge_dict(self.__read_only_view, data)

        data = self.__load_config__('/etc/kylin-config/vendor')
        self.__merge_dict(self.__read_only_view, data)

        data = self.__load_config__('/etc/kylin-config/industry')
        self.__merge_dict(self.__read_only_view, data)

        data = self.__load_config__('/etc/kylin-config/project')
        self.__merge_dict(self.__read_only_view, data)

        data = self.__load_config__('/etc/kylin-config/control')
        self.__merge_dict(self.__read_only_view, data)

        data = self.__load_config__('/etc/kylin-config/common')
        if data:
            for app in self.__read_only_view:
                for version in self.__read_only_view[app]:
                    self.__common_override(self.__read_only_view[app][version], data)

    #将字典中的配置数据写入数据库
    def __dict2db__(self, dict, path):
        try:
            # 连接到数据库（如果不存在则创建）
            db_file_path =  path.rsplit('/',maxsplit=1)[0]
            if not os.path.exists(db_file_path):
                os.makedirs(db_file_path)
            conn = sqlite3.connect(path)

            # 创建游标对象
            cursor = conn.cursor()
            conn.execute('BEGIN TRANSACTION')

            # 获取数据库中的所有表名
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
            tables = cursor.fetchall()

            # 遍历所有表名，对每个表执行DELETE语句以删除所有行
            for table in tables:
                cursor.execute(f"DELETE FROM {table[0]}")
                # cursor.execute(f"DELETE FROM sqlite_sequence WHERE name={table[0]}")

            self.__covert_app_table__(dict,cursor)
            self.__covert_version_table__(dict,cursor)
            self.__covert_configure_table__(dict,cursor)

            # 提交更改
            conn.commit()
            conn.close()
            return 0
        
        except Exception as e:
            conn.rollback()
            conn.close()
            self.logger.error(traceback.format_exc())
            return -1

    # key值改变信号
    @dbus.service.signal("com.kylin.kysdk.conf2", signature='ssss')
    def key_changed(self, id, version, key, user):
        pass

    # 配置信息update信号
    @dbus.service.signal("com.kylin.kysdk.conf2", signature='')
    def updated(self):
        pass

    # 设置一个配置的值
    @dbus.service.method("com.kylin.kysdk.conf2", in_signature='sssss', out_signature='i')
    def set(self, user, id, version, key, value):
        try:
            # 修改用户配置文件，写入到数据库
            group_list = id.split('.')
            sql_path = ''
            if user == 'root':
                sql_path = '/root/.config/kylin-config/user.db'
            else:
                sql_path = f'/home/{user}/.config/kylin-config/user.db'

            conn = sqlite3.connect(sql_path)
            # 创建游标对象
            cursor = conn.cursor()

            # 开始写入事务
            conn.execute('BEGIN TRANSACTION')
            cursor.execute("SELECT id FROM app WHERE app_name = ?", (group_list[0],))
            app_id = cursor.fetchone()[0]
            cursor.execute("SELECT id FROM version WHERE app_id = ? AND version = ?", (app_id, version))
            version_id = cursor.fetchone()[0]
            parent_id = 0
            # 查找组
            for group_name in group_list[1:]:
                cursor.execute("SELECT id FROM configures WHERE version_id = ? AND parent = ? AND group_name = ?", (version_id, parent_id, group_name))
                parent_id = cursor.fetchone()[0]
            # cursor.execute("SELECT * FROM configures WHERE version_id = ? AND parent = ? AND property = ?", (version_id, parent_id, key))
            # key_data = cursor.fetchone()

            cursor.execute("UPDATE configures SET user_value = ? WHERE version_id = ? AND parent = ? AND property = ?", (value,version_id, parent_id, key))
            conn.commit()
            conn.close()
            if user == 'root':
                self.__merge_dict(self.__root_view, self.__format_db_file('/root/.config/kylin-config/user.db'))
            self.key_changed(id, version, key, user)
            return 1
        except Exception as e:
            conn.rollback()
            conn.close()
            self.logger.error(traceback.format_exc())
            return 0
    
    # 恢复单个默认值
    @dbus.service.method("com.kylin.kysdk.conf2", in_signature='ssss', out_signature='i')
    def reset(self,user, id, version, key):
        try:
            # 修改用户配置文件，写入到数据库
            group_list = id.split('.')
            sql_path = ''
            if user == 'root':
                sql_path = '/root/.config/kylin-config/user.db'
            else:
                sql_path = f'/home/{user}/.config/kylin-config/user.db'

            conn = sqlite3.connect(sql_path)
            # 创建游标对象
            cursor = conn.cursor()

            # 开始写入事务
            conn.execute('BEGIN TRANSACTION')
            cursor.execute("SELECT id FROM app WHERE app_name = ?", (group_list[0],))
            app_id = cursor.fetchone()[0]
            cursor.execute("SELECT id FROM version WHERE app_id = ? AND version = ?", (app_id,version))
            version_id = cursor.fetchone()[0]
            parent_id = 0
            # 查找组
            for group_name in group_list[1:]:
                cursor.execute("SELECT id FROM configures WHERE version_id = ? AND parent = ? AND group_name = ?", (version_id, parent_id, group_name))
                parent_id = cursor.fetchone()[0]
            # cursor.execute("SELECT * FROM configures WHERE version_id = ? AND parent = ? AND property = ?", (version_id, parent_id, key))
            cursor.execute("UPDATE configures SET user_value = default_value WHERE version_id = ? AND parent = ? AND property = ?", (version_id, parent_id, key))
            conn.commit()
            conn.close()
            if user == 'root':
                self.__merge_dict(self.__root_view, self.__format_db_file('/root/.config/kylin-config/user.db'))
            self.key_changed(id,version,key, user)
            return 0
        except Exception as e:
            conn.rollback()
            conn.close()
            self.logger.error(traceback.format_exc())
            return -1
    
    # 恢复整个id的默认值
    # @dbus.service.method("com.kylin.kysdk.conf2", in_signature='sss', out_signature='i')
    # def reset_recursively(self, user, id, version):
    #     try:
    #         # 修改用户配置文件，写入到数据库
    #         group_list = id.split('.')
    #         if user == 'root':
    #             sql_path = '/root/.config/kylin-config/user.db'
    #         else:
    #             sql_path = f'/home/{user}/.config/kylin-config/user.db'

    #         conn = sqlite3.connect(sql_path)
    #         # 创建游标对象
    #         cursor = conn.cursor()

    #         # 开始写入事务
    #         conn.execute('BEGIN TRANSACTION')
    #         cursor.execute("SELECT id FROM app WHERE app_name = ?", (group_list[0],))
    #         app_id = cursor.fetchone()[0]
    #         cursor.execute("SELECT id FROM version WHERE app_id = ? AND version = ?", (app_id,version))
    #         version_id = cursor.fetchone()[0]
    #         parent_id = 0
    #         # 查找组
    #         for group_name in group_list[1:]:
    #             cursor.execute("SELECT id FROM configures WHERE version_id = ? AND parent = ? AND group_name = ?", (version_id, parent_id, group_name))
    #             parent_id = cursor.fetchone()[0]
    #         cursor.execute("UPDATE configures SET value = default_value WHERE version_id = ? AND parent = ?", (version_id, parent_id))
    #         conn.commit()
    #         conn.close()
    #         if user == 'root':
    #             self.__merge_dict(self.__root_view, self.__format_db_file('/root/.config/kylin-config/user.db'))
    #         self.key_changed(id,version,None)
    #         return 0
    #     except sqlite3.Error as e:
    #         conn.rollback()
    #         conn.close()
    #         return -1

    # 恢复整个id的默认值
    @dbus.service.method("com.kylin.kysdk.conf2", in_signature='', out_signature='i')
    def reload(self):
        try:
            self.__load_read_only_view__()
            self.__dict2db__(self.__read_only_view, '/etc/kylin-config/user.db')

            #删除配置文件中删除的键
            self.__del_discarded_key(self.__root_view, self.__read_only_view)
            #合并配置新增和修改
            self.__merge_dict(self.__root_view, self.__read_only_view)
            self.__dict2db__(self.__root_view, '/root/.config/kylin-config/user.db')

            self.updated()
            return 1
        except Exception as e:
            self.logger.error(traceback.format_exc())
            return 0
    
    def __value_override_default(self, dest:dict, src:dict):
        for key, value in src.items():
            if key not in dest:
                continue
            if isinstance(src[key], dict):
                self.__value_override_default(dest[key], src[key])
            else:
                if src['value'] is not None:
                    dest['default'] = src['value']
                return


    @dbus.service.method("com.kylin.kysdk.conf2", in_signature='ss', out_signature='b')
    def save(self, user, path):
        if not os.path.exists(path):
            return False
        data = {}
        if 'root' == user:
            data = self.__format_db_file('/root/.config/kylin-config/user.db')
        else:
            data = self.__format_db_file(f'/home/{user}/.config/kylin-config/user.db')
        if data == {} or data is None:
            return False
        
        tmp = {}
        tmp.update(self.__read_only_view)
        if tmp == {} or tmp is None:
            return False
        #将用户配置写入到复制的只读视图里
        self.__value_override_default(tmp, data)
        for key in tmp:
            data = {}
            data[key] = tmp[key]
            yaml_data = yaml.safe_dump(data,allow_unicode = True)
            if yaml_data is None:
                continue
            with open(f'{path}/{key}.yaml', 'w') as yaml_file:
                yaml_file.write(yaml_data)
        return True

if __name__ == "__main__":
    # 初始化DBus主循环
    DBusGMainLoop(set_as_default=True)

    # 连接到DBus系统总线
    bus = dbus.SystemBus()

    # 注册一个DBus服务
    bus_name = dbus.service.BusName("com.kylin.kysdk.conf2", bus=bus)
    object_path = "/com/kylin/kysdk/conf2"
    my_dbus_service = KYSettingsDBusService(bus_name, object_path)

    # 开始DBus主循环
    mainloop = GLib.MainLoop()
    mainloop.run()