/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*-
 * -*- coding: utf-8 -*-
 *
 * Copyright (C) 2024 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
 * 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 <http://www.gnu.org/licenses/>.
 */

#include "save-security-config.h"
#include "usdqjsonconf.h"

SaveSecurityConfig::SaveSecurityConfig(QObject *parent):
    QObject(parent)
{
    initSecurityConf();
}

QStringList SaveSecurityConfig::getLoggedInUsers()
{
    QStringList users;
    QProcess process;

    // 方法1：使用who命令
    process.start("who", QStringList() << "-u");
    if (process.waitForFinished()) {
        QString output = process.readAllStandardOutput();
        QStringList lines = output.split("\n", QString::SkipEmptyParts);
        for (const QString &line : lines) {
            QString user = line.split(" ", QString::SkipEmptyParts).first();
            if (!user.isEmpty() && !users.contains(user)) {
                users.append(user);
            }
        }
    }
    return users;
}

void SaveSecurityConfig::updateKcm42Config(QString user, QStringList updateList)
{
    QStringList userList;
    if (user.isEmpty()) {
        userList = getLoggedInUsers();
    } else {
        userList.append(user);
    }

    for (auto userName : userList) {
        QString configDir = QString(SSC_CUSTOMIZED_CONFIG_DIR).arg(userName);
        createSecurityDir(configDir);
        for (auto updateNode : updateList) {
            QString updateFile = QString("%1%2.json").
                    arg(configDir).
                    arg(updateNode);
            if (QFile(updateFile).exists()) {
                QFile::remove(updateFile);
            }
            QString kcm43File = getConfigPath(userName, updateNode);
            if (QFile::exists(kcm43File) && !QFile::copy(kcm43File, updateFile)) {
                SYS_LOG(LOG_WARNING, "Copy kcm43 file %s to %s ERROR!",kcm43File.toLatin1().data(), updateFile.toLatin1().data());
            } else {
                Q_EMIT configChanged(userName, updateNode, updateFile);
            }
        }
    }
}

void SaveSecurityConfig::deleteKcm42Config()
{
    QStringList users = getLoggedInUsers();
    for (auto user : users) {
        QString configDir = QString(SSC_CUSTOMIZED_CONFIG_DIR).arg(user);
        QDir dir(configDir);
        if (!dir.exists()) {
            return;
        }

        QFileInfoList fileInfoList = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
        for (const QFileInfo &fileInfo : fileInfoList) {
            if (fileInfo.suffix() == "json") {
                if (!fileInfo.dir().remove(fileInfo.fileName())) {
                    SYS_LOG(LOG_WARNING, "Failed to delete file:%s",fileInfo.absoluteFilePath().toLatin1().data());
                } else {
                    Q_EMIT configChanged(user, fileInfo.completeBaseName(), fileInfo.absoluteFilePath());
                }
            }
        }
    }
}

bool SaveSecurityConfig::exitSecurity()
{
    if (!deleteAllAppConfig()) {
        return false;
    }

    Q_EMIT SecurityExit();

    return true;
}

void SaveSecurityConfig::initSecurityConf()
{
    //删除客户端不存在的本地用户配置
    QStringList clientUserList = getDirList(CLIENT_SECURITYFILE_PATH);
    QStringList localUserList = getDirList(APP_SECURITYFILE_PATH);
    for (const auto localUser : localUserList) {
        if (!clientUserList.contains(localUser)) {
            deleteSecurityConfig(localUser);
        }
    }

    //更新全局域控配置，若不存在则删除组件全局配置
    QString globalConfig = getClientGlobalSecurityFile();
    if (globalConfig.isEmpty()) {
        SYS_LOG(LOG_INFO, "Client don't have global security config file, delete local file!");
        deleteSecurityConfig("");
    } else {
        //初始化更新全局配置时不更新用户配置，下面会更新用户配置
        SYS_LOG(LOG_INFO, "Init client global security config file, file:%s", globalConfig.toLatin1().data());
        updateGlobalConfig(globalConfig, false);
    }

    //更新客户端存在的本地用户配置
    QStringList clinetUserFileList = getClientUserSecurityFileList();
    for (const auto clientUserFile : clinetUserFileList) {
        QFileInfo fileInfo(clientUserFile);
        SYS_LOG(LOG_INFO, "Init client user security config file, file:%s", clientUserFile.toLatin1().data());
        updateUserConfig(clientUserFile, fileInfo.dir().dirName());
    }
}

bool SaveSecurityConfig::UpdateConfig(QString configPath, QString userName)
{
    if (configPath.isEmpty()) {
        SYS_LOG(LOG_WARNING,"UpdateConfig Error! ConfigPath arg is empty!");
        return false;
    }

    if (!QFile(configPath).exists()) {
        SYS_LOG(LOG_WARNING,"UpdateConfig Error! File %s is not exists!", configPath.toLatin1().data());
        return false;
    }

    return updateConfigBegin(configPath, userName);
}

bool SaveSecurityConfig::ClearConfig(QString userName)
{
    return deleteSecurityConfig(userName);
}

bool SaveSecurityConfig::ExitSecurity()
{
    deleteKcm42Config();
    return exitSecurity();
}

QString SaveSecurityConfig::GetConfigPath(QString userName, QString fileName)
{
    if (fileName.isEmpty()) {
        return QString();
    }

    return getConfigPath(userName, fileName);
}

QStringList SaveSecurityConfig::getMethodPermitedAppList(const QString &key)
{
    if (key.isEmpty()) {
        return QStringList();
    }
    QStringList appList;
    appList = getConfigFileList(METHODSPERMITED_GRUOP, key);
    if (key == SECURITYCONFIG_KEY_CLIENT) {
        addBasicApp(appList);
    }
    return appList;
}

void SaveSecurityConfig::addBasicApp(QStringList& applist)
{
    if (!applist.contains(SECURITYCLIENT_NAME)) {
        applist.append(SECURITYCLIENT_NAME);
    }
}

bool SaveSecurityConfig::deleteAllAppConfig()
{
    QDir dir(APP_SECURITYFILE_PATH);
    // 检查目录是否存在
    if (!dir.exists()) {
        SYS_LOG(LOG_NOTICE, "Security config dir is not exists!");
        return true;
    }

    if (dir.removeRecursively()) {
        SYS_LOG(LOG_NOTICE, "All security config delete!");
        return true;
    } else {
        SYS_LOG(LOG_WARNING, "Delete security config fail!");
        return false;
    }
}

bool SaveSecurityConfig::deleteSecurityConfig(QString userName)
{
    if (userName.isEmpty()) {
        SYS_LOG(LOG_NOTICE, "Delete global security config");
        deleteKcm42Config();
        return deleteGlobalSecurityConfig();
    } else {
        SYS_LOG(LOG_NOTICE, "Delete user %s security config", userName.toLatin1().data());
        return deleteUserSecurityConfig(userName);
    }

    return false;
}

bool SaveSecurityConfig::deleteUserSecurityConfig(const QString &userName)
{
    if (userName.isEmpty()) {
        SYS_LOG(LOG_DEBUG, "User name is empty");
        return false;
    }

    QString configDir = QString("%1/%2").arg(APP_SECURITYFILE_PATH, userName);
    QDir dir(configDir);
    if (!dir.exists()) {
        return true;
    }

    QStringList updateConfig;
    QFileInfoList fileInfoList = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
    for (const QFileInfo &fileInfo : fileInfoList) {
        if (fileInfo.suffix() == "json") {
            if (!fileInfo.dir().remove(fileInfo.fileName())) {
                SYS_LOG(LOG_WARNING, "Failed to delete file:%s",fileInfo.absoluteFilePath().toLatin1().data());
            } else {
                updateConfig.append(fileInfo.completeBaseName());
            }
        }
    }
    // 尝试删除目录（非空也要删除）
    if (!dir.removeRecursively()) {
        SYS_LOG(LOG_WARNING, "Failed to delete directory, it may not be empty:%s", configDir.toLatin1().data());
    }
    Q_EMIT ConfigChanged(userName, updateConfig);
    return true;
}

bool SaveSecurityConfig::deleteGlobalSecurityConfig()
{
    QString configDir = QString(APP_SECURITYFILE_PATH);
    QDir appDir(configDir);
    if (!appDir.exists()) {
        return true;
    }

    QStringList updateConfigList = getAllConfigNamefromDir(configDir);
    for (const auto updateConfig : updateConfigList) {
        QFile file(getAppFilePathStr(APP_SECURITYFILE_PATH, updateConfig));
        file.remove();
    }

    QStringList clientUserFileList = getClientUserSecurityFileList();
    for (const auto clientUserFile : clientUserFileList) {
        QFileInfo info(clientUserFile);
        QString userName = info.dir().dirName();

        QString updatePath = QString("%1/%2").arg(APP_SECURITYFILE_PATH, userName);
        QStringList updateAppList = getUpdateAppList(clientUserFile, updatePath);
        if (updateAppList.isEmpty()) {
            continue;
        }
        writeUserConf(clientUserFile, updateAppList, userName);
    }

    Q_EMIT ConfigChanged("", updateConfigList);
    return true;
}

QString SaveSecurityConfig::getAppFilePathStr(const QString &path, const QString &file, QString user)
{
    if (!user.isEmpty()) {
        return QString("%1/%2/%3.json").arg(path, user, file);
    }
    return QString("%1/%2.json").arg(path, file);
}

QString SaveSecurityConfig::getConfigPath(const QString &userName, const QString &appName)
{
    if (appName.isEmpty()) {
        return QString();
    }

    QString filePath;
    if (!userName.isEmpty()) {
        filePath = getAppFilePathStr(APP_SECURITYFILE_PATH, appName, userName);
        if (QFile(filePath).exists()) {
            return filePath;
        }
    }

    filePath = getAppFilePathStr(APP_SECURITYFILE_PATH, appName);
    if (QFile(filePath).exists()) {
        return filePath;
    } else {
        return QString();
    }
}

QStringList SaveSecurityConfig::getAllConfigNamefromDir(const QString &dirName)
{
    QDir dir(dirName);
    if (!dir.exists()) {
        return QStringList();
    }
    QStringList configNameList;
    QFileInfoList fileInfoList = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
    for (const QFileInfo &fileInfo : fileInfoList) {
        if (fileInfo.suffix() == "json") {
            configNameList.append(fileInfo.completeBaseName());
        }
    }
    return configNameList;
}

bool SaveSecurityConfig::updateConfigBegin(const QString &updatefile, const QString &userName)
{
    if (!m_updateTimerMap.keys().contains(updatefile)) {
        updateTimerInit(updatefile, userName);
    }
    updateTimerStart(updatefile);

    return true;
}

bool SaveSecurityConfig::updateConfig(const QString& configPath, const QString& userName)
{
    if (!QFile(configPath).exists()) {
        return false;
    }

    if (userName.isEmpty()) {
        updateGlobalConfig(configPath);
    } else {
        updateUserConfig(configPath, userName);
    }
    return true;
}

QStringList SaveSecurityConfig::getUpdateAppList(const QString &clientFile, const QString &updatePath)
{
    QStringList changeAppList;
    if (!QFile(clientFile).exists()) {
        return changeAppList;
    }
    createSecurityDir(updatePath);
    QJsonObject clientAllObj = UsdQJsonConf::loadJsonObject(clientFile);
    if (clientAllObj.keys().contains("config")) {
        clientAllObj.remove("config");
    }

    QStringList clientAppList = clientAllObj.keys();
    QStringList appList = getAllConfigNamefromDir(updatePath);
    QString appFile;

    for (const QString &app : appList) {
        if (!clientAppList.contains(app)) {
            appFile = getAppFilePathStr(updatePath, app);
            if (QFile(appFile).exists()) {
                QFile(appFile).remove();
                changeAppList.append(app);
            }
        }
    }

    QJsonObject clientObj;
    for (const QString &clientApp : clientAppList) {
        appFile = getAppFilePathStr(updatePath, clientApp);
        if (!QFile(appFile).exists()) {
            changeAppList.append(clientApp);
            continue;
        }

        if (updatePath != APP_SECURITYFILE_PATH) {
            QString globalFile = getAppFilePathStr(APP_SECURITYFILE_PATH, clientApp);
            if (!QFile(globalFile).exists()) {
                continue;
            }
            clientObj = UsdQJsonConf::loadJsonObject(globalFile).value(clientApp).toObject();
            UsdQJsonConf::mergeJsonObjects(clientObj, clientAllObj[clientApp].toObject());
        } else {
            clientObj = clientAllObj[clientApp].toObject();
        }

        QJsonObject appObj = UsdQJsonConf::loadJsonObject(appFile);
        if (clientObj != appObj.value(clientApp).toObject()) {
            changeAppList.append(clientApp);
        }
    }

    return changeAppList;
}

void SaveSecurityConfig::updateUserConfig(const QString &clientFile, const QString &userName)
{
    QString updatePath = QString("%1/%2").arg(APP_SECURITYFILE_PATH, userName);
    QStringList updateAppList = getUpdateAppList(clientFile, updatePath);
    if (updateAppList.isEmpty()) {
        SYS_LOG(LOG_NOTICE, "There is no application security config file changed, don't send signal!");
        return;
    }

    writeUserConf(clientFile, updateAppList, userName);
    updateKcm42Config(userName, updateAppList);

    Q_EMIT ConfigChanged(userName, updateAppList);
}

void SaveSecurityConfig::updateGlobalConfig(const QString& clientFile, bool updateUser)
{
    QStringList updateAppList = getUpdateAppList(clientFile, APP_SECURITYFILE_PATH);
    if (updateAppList.isEmpty()) {
        SYS_LOG(LOG_NOTICE, "There is no application security config file changed, don't send signal!");
        return;
    }

    writeGlobalConf(clientFile, updateAppList);
    if (updateUser) {
        writeGlobalConfToUserConf(clientFile, updateAppList);
    }
    updateKcm42Config(QString(), updateAppList);

    Q_EMIT ConfigChanged("", updateAppList);
}

void SaveSecurityConfig::writeGlobalConf(const QString &clientFile, const QStringList &updateAppList)
{
    QJsonObject clientGlobalObj = UsdQJsonConf::loadJsonObject(clientFile);

    for (const auto updateApp : updateAppList) {
        if (!clientGlobalObj.keys().contains(updateApp)) {
            continue;
        }
        QJsonObject appObj = clientGlobalObj.value(updateApp).toObject();
        QJsonObject writeObj;
        writeObj.insert(updateApp, appObj);
        QString appFile = getAppFilePathStr(APP_SECURITYFILE_PATH, updateApp);
        UsdQJsonConf::saveJsonObject(appFile, writeObj);
    }
}

void SaveSecurityConfig::writeUserConf(const QString &clientFile, const QStringList &updateAppList, const QString& userName)
{
    QJsonObject clientObj = UsdQJsonConf::loadJsonObject(clientFile);
    for (const auto updateApp : updateAppList) {
        if (!clientObj.keys().contains(updateApp)) {
            continue;
        }
        QJsonObject clientGlobalObj;
        QString globalFile = getAppFilePathStr(APP_SECURITYFILE_PATH, updateApp);
        if (QFile(globalFile).exists()) {
            clientGlobalObj = UsdQJsonConf::loadJsonObject(globalFile).value(updateApp).toObject();
        }
        UsdQJsonConf::mergeJsonObjects(clientGlobalObj, clientObj[updateApp].toObject());
        QJsonObject writeObj;
        writeObj.insert(updateApp, clientGlobalObj);
        QString appFile = getAppFilePathStr(APP_SECURITYFILE_PATH, updateApp, userName);
        UsdQJsonConf::saveJsonObject(appFile, writeObj);
    }
}

void SaveSecurityConfig::writeGlobalConfToUserConf(const QString& clientFile, const QStringList& updateAppList)
{
    QJsonObject clientGlobalObj = UsdQJsonConf::loadJsonObject(clientFile);

    QStringList clientUserFileList = getClientUserSecurityFileList();
    for (const auto clientUserFile : clientUserFileList) {
        QFileInfo info(clientUserFile);
        QString userName = info.dir().dirName();

        QJsonObject clientUserObj = UsdQJsonConf::loadJsonObject(clientUserFile);
        for (const auto updateApp : updateAppList) {
            if (clientUserObj.keys().contains(updateApp)) {
                QJsonObject appObj = clientGlobalObj.value(updateApp).toObject();
                UsdQJsonConf::mergeJsonObjects(appObj, clientUserObj.value(updateApp).toObject());
                QJsonObject writeObj;
                writeObj.insert(updateApp, appObj);
                QString appFile = getAppFilePathStr(APP_SECURITYFILE_PATH, updateApp, userName);
                if(!QFile(appFile).exists()) {
                    QFileInfo info(appFile);
                    createSecurityDir(info.absolutePath());
                }
                UsdQJsonConf::saveJsonObject(appFile, writeObj);
            } else {
                QString appFile = getAppFilePathStr(APP_SECURITYFILE_PATH, updateApp, userName);
                if (QFile(appFile).exists()) {
                    QFile(appFile).remove();
                }
            }
        }
    }
}

void SaveSecurityConfig::updateTimerInit(const QString &updatefile, const QString &userName)
{
    QSharedPointer<QTimer> timer(new QTimer());
    timer.data()->setSingleShot(true);
    m_updateTimerMap.insert(updatefile, timer);
    connect(timer.data(), &QTimer::timeout, this, [this, updatefile, userName] () {
        SYS_LOG(LOG_NOTICE, "On update config, user:%s, config file:%s", userName.toLatin1().data(), updatefile.toLatin1().data());
        updateConfig(updatefile, userName);
        m_updateTimerMap.remove(updatefile);
    });
}

void SaveSecurityConfig::createSecurityDir(const QString &dirPath)
{
    // 检查目录是否存在
    if (!QDir(dirPath).exists()) {
        UsdBaseClass::peekDir(dirPath, QFileDevice::Permission(0x655));
    }
}

void SaveSecurityConfig::updateTimerStart(const QString &updatefile)
{
    auto timer = m_updateTimerMap.value(updatefile);
    if (!timer.isNull()) {
        SYS_LOG(LOG_NOTICE, "config file:%s start timer", updatefile.toLatin1().data());
        timer->start(UPDATE_TIME);
    }
}

QVariant SaveSecurityConfig::getConfigFromQSettingsFile(const QString& file, const QString &group, const QString &key)
{
    QVariant value;
    if (file.isEmpty() || group.isEmpty() || key.isEmpty()) {
        SYS_LOG(LOG_WARNING, "Arguments is empty");
        return value;
    }

    if(!QFile(file).exists()) {
        SYS_LOG(LOG_WARNING, "Config file %s does not exists", file.toLatin1().data());
        return value;
    }

    QSettings usdSettings(file, QSettings::IniFormat);
    usdSettings.beginGroup(group);
    if (!usdSettings.allKeys().contains(key)) {
        SYS_LOG(LOG_NOTICE, "Config file %s don't have key &s", key.toLatin1().data());
        return value;
    }
    value = usdSettings.value(key);
    usdSettings.endGroup();
    usdSettings.sync();

    return value;
}

QStringList SaveSecurityConfig::getConfigFileList(const QString &group, const QString& key)
{
    QVariant value = getConfigFromQSettingsFile(SECURITYCONFIGFILE_PATH, group, key);

    switch (value.type()) {
    case QVariant::StringList:
    case QVariant::String:
        return value.toStringList();
        break;

    default:
        SYS_LOG(LOG_WARNING, "QVariant value type is %d", value.type());
        break;
    }

    return QStringList();
}

QString SaveSecurityConfig::getClientGlobalSecurityFile()
{
    QString globalFile = getAppFilePathStr(CLIENT_SECURITYFILE_PATH, CLIENT_SECURITYFILE_GLOBAL);
    return QFile(globalFile).exists()? globalFile : QString();
}

QStringList SaveSecurityConfig::getDirList(const QString &path)
{
    QDir dir(path);
    dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
    return dir.entryList();
}

QStringList SaveSecurityConfig::getClientUserSecurityFileList()
{
    QStringList userFileList;
    QStringList userDirList = getDirList(CLIENT_SECURITYFILE_PATH);
    for (const auto userDir : userDirList) {
        QString userFilePath = getAppFilePathStr(CLIENT_SECURITYFILE_PATH, CLIENT_SECURITYFILE_USER, userDir);
        if (QFile(userFilePath).exists()) {
            userFileList.append(userFilePath);
        }
    }

    return userFileList;
}

void SaveSecurityConfig::setQSettingsFile(const QString &filePath, const QString &group, const QString &key, const QVariant &value)
{
    if(!QFile(filePath).exists()) {
        QFileInfo info(filePath);
        createSecurityDir(info.absolutePath());
        QFile file(filePath);
        file.setPermissions(QFileDevice::Permission(0x644));
        file.close();
    }

    QSettings usdSettings(filePath, QSettings::IniFormat);
    usdSettings.beginGroup(group);
    if (value.isNull()) {
        usdSettings.remove(key);
    } else {
        usdSettings.setValue(key, value);
    }
    usdSettings.endGroup();
    usdSettings.sync();
}
