/*
 * Copyright (c) KylinSoft  Co., Ltd. 2024. All rights reserved.
 *
 * kaiming is licensed under the GPL v2.0+.
 * 
 * See the LICENSE file for more details.
 */

#include "KMHostAppDeploy.h"
#include "common/KMLogger.h"

#include <iostream>
#include <sstream>
#include <fstream>
#include <stdexcept>

class KMHostAppDeploy::Private
{
public:
    fs::path m_versionDataPath;
    std::string m_downloadRefId;
    std::set<std::string> m_SynchronizedFiles;
    std::vector<std::string> m_vecService;
};

KMHostAppDeploy::KMHostAppDeploy()
    : d(std::make_unique<Private>())
{
}

KMHostAppDeploy::~KMHostAppDeploy()
{
}

bool KMHostAppDeploy::loadHostAppInfo()
{
    fs::path infoFile = d->m_versionDataPath / "info.json";
    if (!fs::exists(infoFile))
    {
        KMError("can't find app info file: " + infoFile.string());
        return false;
    }

    KMInfoJson infoJson;
    infoJson.loadFile(infoFile);
    d->m_vecService = infoJson.getAnnotations().service;
    return infoJson.getAnnotations().hostapp;
}

void KMHostAppDeploy::setHostAppConfig(const fs::path &versionPath, const std::string &refID)
{
    d->m_versionDataPath = versionPath;
    d->m_downloadRefId = refID;
}

void KMHostAppDeploy::collectSynchronizedFiles()
{
    fs::path m_extractDir = d->m_versionDataPath / "files";
    for (const auto &entry : fs::recursive_directory_iterator(m_extractDir))
    {
        if (fs::is_regular_file(entry) && entry.path().parent_path() != m_extractDir)
        {
            std::string relativePath = entry.path().string().substr(m_extractDir.string().length());
            d->m_SynchronizedFiles.insert(relativePath);
        }
    }
}

bool KMHostAppDeploy::checkFileConflicts()
{
    static const std::set<std::string> excludedDirs = {"/etc", "/var", "/usr/local/etc"};
    bool conflictFound = false;

    for (const auto &file : d->m_SynchronizedFiles)
    {
        if (shouldSkipFile(file, excludedDirs))
        {
            KMInfo("Skipping conflict check for: " + file);
            continue;
        }

        if (fs::exists(file))
        {
            KMError("File conflict already exists on system: " + file);
            conflictFound = true;
            //break;
        }
    }
    return conflictFound;
}

bool KMHostAppDeploy::shouldSkipFile(const std::string &file, const std::set<std::string> &excludedDirs)
{
    for (const auto &excludedDir : excludedDirs)
    {
        if (file.find(excludedDir) == 0)
        {
            return true;
        }
    }
    return false;
}

void KMHostAppDeploy::synchronizeFiles()
{
    fs::path m_sourcePath = d->m_versionDataPath / "files";
    std::set<std::string> excludeDirs = {"lib", "usr", "bin"};

    // 遍历源目录下的顶级目录
    for (const auto &entry : fs::directory_iterator(m_sourcePath))
    {
        if (!entry.is_directory())
        {
            continue;
        }

        std::string dirName = entry.path().filename().string();

        if (excludeDirs.find(dirName) != excludeDirs.end())
        {
            KMInfo("Skipping excluded directory: " + dirName);
            continue;
        }

        fs::path targetPath = fs::path("/") / dirName;
        std::ostringstream command;
        command << "rsync -av"
                << " " << entry.path() << "/ "
                << targetPath << "/";

        KMInfo("Syncing directory: " + entry.path().string());
        int result = system(command.str().c_str());
        if (result != 0)
        {
            KMError("Failed to sync directory: " + targetPath.string());
        }
    }

    writeSynchronizedFileList();
    fs::remove_all(m_sourcePath); // delete files

    handleSchemaCompilation();
    startServices();
}

void KMHostAppDeploy::handleSchemaCompilation()
{
    fs::path schemaPath("/opt/system/resource/glib-2.0/schemas/");
    if (!fs::exists(schemaPath))
    {
        fs::create_directories(schemaPath);
    }

    std::ostringstream schemaCommand;
    schemaCommand << "glib-compile-schemas /opt/system/resource/glib-2.0/schemas/";
    system(schemaCommand.str().c_str());
}

void KMHostAppDeploy::startServices()
{
    for (const auto &service : d->m_vecService)
    {
        if (service.empty())
        {
            KMDebug("Empty service name found, skipping...");
            continue;
        }
        std::ostringstream serviceCommand;
        serviceCommand << "systemctl enable --now " << service;

        int result = system(serviceCommand.str().c_str());

        if (result != 0)
        {
            KMError("Failed to enable and start service: " + service +
                    " (exit code: " + std::to_string(result) + ")");
        }
        else
        {
            KMInfo("Successfully enabled and started service: " + service);
        }
    }
}

void KMHostAppDeploy::writeSynchronizedFileList()
{
    fs::path synchronizedPath = d->m_versionDataPath / (d->m_downloadRefId + ".list");
    std::ofstream listFile(synchronizedPath, std::ios::out | std::ios::trunc);
    if (!listFile)
    {
        KMError("Failed to open file " + synchronizedPath.string());
        return;
    }

    for (const auto &file : d->m_SynchronizedFiles)
    {
        listFile << file << "\n";
    }
}

void KMHostAppDeploy::uninstall()
{
    fs::path synchronizedPath = d->m_versionDataPath / (d->m_downloadRefId + ".list");

    // 读取已安装文件列表
    std::set<std::string> installedFiles = readSynchronizedFileList();

    if (installedFiles.empty())
    {
        KMInfo("No installed files found or file list is empty.");
        return;
    }

    static const std::set<std::string> excludedDirs = {"/etc/", "/usr/", "/var/", "/lib"};

    for (const auto &file : installedFiles)
    {
        fs::path filePath = file;
        std::string filePathStr = filePath.string();

        // 检查文件是否在排除目录中
        if (shouldSkipFile(filePathStr, excludedDirs))
        {
            KMInfo("Skipping protected directory file: " + filePathStr);
            continue;
        }

        try
        {
            if (fs::exists(filePath))
            {
                fs::remove(filePath);
                KMInfo("Deleted file: " + filePath.string());
            }
        }
        catch (const std::exception &e)
        {
            KMError("Failed to delete file: " + filePath.string() + " - " + std::string(e.what()));
        }
    }

    removeEmptyDirs(installedFiles);

    // 删除 synchronizedPath 文件
    try
    {
        if (fs::exists(synchronizedPath))
        {
            fs::remove(synchronizedPath);
            KMInfo("Deleted synchronized file list: " + synchronizedPath.string());
        }
    }
    catch (const std::exception &e)
    {
        KMError("Failed to delete synchronized file list: " + std::string(e.what()));
    }
}

void KMHostAppDeploy::removeEmptyDirs(const std::set<std::string> &files)
{
    for (const auto &file : files)
    {
        fs::path dir = fs::path(file).parent_path();
        while (dir != "/" && fs::exists(dir) && fs::is_empty(dir))
        {
            fs::remove(dir);
            KMDebug("Removed empty dir: " + dir.string());
            dir = dir.parent_path();
        }
    }
}

std::set<std::string> KMHostAppDeploy::readSynchronizedFileList()
{
    fs::path synchronizedPath = d->m_versionDataPath / (d->m_downloadRefId + ".list");
    std::set<std::string> fileSet;

    std::ifstream listFile(synchronizedPath);
    if (!listFile)
    {
        KMInfo("Failed to open synchronized file list: " + synchronizedPath.string());
        return fileSet;
    }

    std::string line;
    while (std::getline(listFile, line))
    {
        fileSet.insert(line);
    }

    return fileSet;
}
