#include "KMBuilderCleanup.h"

KMBuilderCleanup::KMBuilderCleanup(shared_ptr<KMBuilderCache> cache)
{
    m_pConfigData = builderContext.getBuilderConfig()->getConfigData();
    m_pKMBuilderCache = cache;
    m_builderCache = m_pKMBuilderCache->getBuilderCache();
}

KMBuilderCleanup::~KMBuilderCleanup()
{
}

bool KMBuilderCleanup::configCleanup()
{
    KMBuilderGlobal global;
    string appRoot = "";
    vector<string> env;
    string appdataFile = "";
    string appdataSource = "";

    string configRenameDesktopFile = std::any_cast<string>(m_pConfigData->attributeMap["rename-desktop-file"]);
    string configRenameIcon = std::any_cast<string>(m_pConfigData->attributeMap["rename-icon"]);
    string configDesktopFileNamePrefix = std::any_cast<string>(m_pConfigData->attributeMap["desktop-file-name-prefix"]);
    string configDesktopFileNameSuffix = std::any_cast<string>(m_pConfigData->attributeMap["desktop-file-name-suffix"]);
    string configID = std::any_cast<string>(m_pConfigData->attributeMap["id"]);
    string configAppdataLicense = std::any_cast<string>(m_pConfigData->attributeMap["appdata-license"]);

    bool configAppstreamCompose = std::any_cast<bool>(m_pConfigData->attributeMap["appstream-compose"]);
    bool configBuildRuntime = std::any_cast<bool>(m_pConfigData->attributeMap["build-runtime"]);
    configChecksumForCleanup();
    if (!m_pKMBuilderCache->find("cleanup"))
    {
        std::unordered_map<std::string, bool> toRemoveHT;
        string appDir = m_builderCache.appDir;

        //通过清理指令做第一步清理
        vector<string> cleanupCommands = m_pConfigData->cleanupCommands;
        if (cleanupCommands.size())
        {
            vector<string> buildArgs = global.builderOptionsGetBuildArgs();
            if (buildArgs.empty())
            {
                return false;
            }

            env = global.builderOptionsGetEnv();
            for (auto cleanupCommand : cleanupCommands)
            {
                if (!KMBuilderUtils::command(appDir, env, buildArgs, cleanupCommand))
                {
                    return false;
                }
            }
        }

        //通过清理相应文件做第二步清理
        //收集需要清理的路径
        shared_ptr<KMBuilderConfig> pBuilderConfig = builderContext.getBuilderConfig();
        vector<shared_ptr<KMBuilderModule>> vBuilderModule = pBuilderConfig->getModules();
        for (auto pBuilderModule : vBuilderModule)
        {
            global.builderModuleCleanupCollect(pBuilderModule, false, toRemoveHT);
        }

        //执行删除清理阶段
        vector<string> keys = KMBuilderUtils::getKeysAsVector(toRemoveHT);
        // 使用std::sort和一个自定义的比较lambda函数
        std::sort(keys.begin(), keys.end(), [](const std::string &a, const std::string &b) {
            return a > b; // 默认按升序排序,可以用于先删除子节点
        });

        for (auto key : keys)
        {
            string filePath = appDir + "/" + key;
            km_logger.info("Removing:%s !", filePath.c_str());
            // 删除一个文件
            if (fs::exists(filePath))
            {
                try
                {
                    if (fs::remove(filePath))
                    {
                        std::cout << filePath << " deleted successfully.\n";
                    }
                    else
                    {
                        std::cerr << "Failed to delete " << filePath << ".\n";
                    }
                }
                catch (const fs::filesystem_error &e)
                {
                    std::cerr << "Failed to delete: " << e.what() << '\n';
                }
            }
            else
            {
                std::cerr << filePath << " does not exist or is not a regular file.\n";
            }
        }

        //处理appdata.xml
        appRoot = KMBuilderUtils::getChildPath(appDir, "files");
        appdataSource = configFindAppdataFile(appRoot);
        if (!appdataSource.empty())
        {
            string appdataDir = appRoot + "/" + "share/appdata";
            string appdataBasename = KMBuilderUtils::argSplice(configID, ".appdata.xml");
            appdataFile = KMBuilderUtils::getChildPath(appdataDir, appdataBasename);

            if (appdataSource != appdataFile)
            {
                string srcBasename = KMBuilderUtils::getBasename(appdataSource);
                km_logger.info("Renaming %s to share/appdata/%s", srcBasename.c_str(), appdataBasename.c_str());

                try
                {
                    fs::create_directories(appdataDir);
                    std::cout << "Directories created successfully.\n";
                }
                catch (const fs::filesystem_error &e)
                {
                    std::cerr << "Failed to create directories: " << e.what() << '\n';
                    return false;
                }
                try
                {
                    fs::rename(appdataSource, appdataFile);
                    std::cout << "Renamed successfully.\n";
                }
                catch (const fs::filesystem_error &e)
                {
                    std::cerr << "Failed to rename: " << e.what() << '\n';
                    return false;
                }
            }

            if (!configAppdataLicense.empty())
            {
                if (!reWriteAppdata(appdataFile, configAppdataLicense))
                {
                    return false;
                }
            }
        }

        //处理renameDesktopFile
        if (!configRenameDesktopFile.empty())
        {
            string applicationsDir = appRoot + "/" + "share/applications";
            string src = KMBuilderUtils::getChildPath(applicationsDir, configRenameDesktopFile);
            string desktopBasename = KMBuilderUtils::argSplice(configID, ".desktop");
            string dest = KMBuilderUtils::getChildPath(applicationsDir, desktopBasename);

            km_logger.info("Renaming %s to %s", configRenameDesktopFile.c_str(), desktopBasename.c_str());
            try
            {
                fs::rename(src, dest);
                std::cout << "Renamed successfully.\n";
            }
            catch (const fs::filesystem_error &e)
            {
                std::cerr << "Failed to rename: " << e.what() << '\n';
                return false;
            }

            if (!appdataFile.empty())
            {
                if (!reWriteAppdataByRenameDesktopFile(appdataFile, configRenameDesktopFile, desktopBasename))
                {
                    return false;
                }
            }
        }

        //查找icon图标,并将所有图标改名
        if (!configRenameIcon.empty())
        {
            bool foundIcon = false;
            string iconsDir = appRoot + "/" + "share/icons";

            if (!foreachFileHelper(iconsDir, iconsDir, "", renameIconCb, &foundIcon, 0))
            {
                return false;
            }

            if (!foundIcon)
            {
                km_logger.error("icon %s not found below %s", configRenameIcon.c_str(), iconsDir.c_str());
                return false;
            }
        }

        //处理desktop 文件
        if (!configRenameIcon.empty() || !configDesktopFileNamePrefix.empty() || !configDesktopFileNameSuffix.empty() || !configRenameDesktopFile.empty())
        {
            string applicationsDir = appRoot + "/" + "share/applications";
            string desktopBasename = KMBuilderUtils::argSplice(configID, ".desktop");
            string desktop = KMBuilderUtils::getChildPath(applicationsDir, desktopBasename);
            string desktopContents = "";

            km_logger.info("Rewriting contents of %s", desktopBasename.c_str());

            desktopContents = KMBuilderUtils::fileGetContents(desktop);

            if (desktopContents.empty())
            {
                km_logger.error("Can't load desktop file %s: ", desktop.c_str());
                return false;
            }

            //处理INI参数
            pt::ptree ptKeyFile;
            if (!KMBuilderUtils::loadIniData(desktopContents, ptKeyFile))
            {
                km_logger.error("Can't load desktopContents file:%s", desktopContents.c_str());
                return false;
            }

            if (!configRenameDesktopFile.empty())
            {
                vector<string> oldRenames = KMBuilderUtils::getStringListFromPt(ptKeyFile, "Desktop Entry", "X-Kaiming-RenamedFrom");
                int oldRenameLen = 0;
                int newRenameLen = 0;

                oldRenameLen = oldRenames.size();

                vector<string> newRenames(oldRenameLen + 1); // 分配存储configRenameDesktopFile的空间
                for (int i = 0; i < oldRenameLen; i++)
                {
                    newRenames[newRenameLen++] = oldRenames[i];
                }

                newRenames[newRenameLen++] = configRenameDesktopFile;

                KMBuilderUtils::writeStringListToPt(ptKeyFile, "Desktop Entry", "X-Kaiming-RenamedFrom", newRenames);
            }

            vector<string> desktopKeys = KMBuilderUtils::getKeys(ptKeyFile, "Desktop Entry");
            if (!configRenameIcon.empty())
            {
                string originalIconName = KMBuilderUtils::getValue(ptKeyFile, "Desktop Entry", "Icon");

                KMBuilderUtils::setValue(ptKeyFile, "Desktop Entry", "Icon", configID);

                for (auto desktopKey : desktopKeys)
                {
                    if (desktopKey.find("Icon[") == 0)
                    {
                        string iconName = KMBuilderUtils::getValue(ptKeyFile, "Desktop Entry", desktopKey);

                        if (iconName == originalIconName)
                        {
                            KMBuilderUtils::setValue(ptKeyFile, "Desktop Entry", desktopKey, configID);
                        }
                    }
                }
            }

            if (!configDesktopFileNameSuffix.empty() || !configDesktopFileNamePrefix.empty())
            {
                for (auto desktopKey : desktopKeys)
                {
                    if (desktopKey == "Name" || desktopKey.find("Name[") == 0)
                    {
                        string name = KMBuilderUtils::getValue(ptKeyFile, "Desktop Entry", desktopKey);
                        if (!name.empty())
                        {
                            string newName = KMBuilderUtils::argSplice(!configDesktopFileNamePrefix.empty() ? configDesktopFileNamePrefix : "", name,
                                                                       !configDesktopFileNameSuffix.empty() ? configDesktopFileNameSuffix : "");
                            KMBuilderUtils::setValue(ptKeyFile, "Desktop Entry", desktopKey, newName);
                        }
                    }
                }
            }

            desktopContents = "";
            desktopContents = KMBuilderUtils::ptreeToIniString(ptKeyFile);
            if (desktopContents.empty())
            {
                return false;
            }

            if (!KMBuilderUtils::fileReSetContents(desktop, desktopContents))
            {
                return false;
            }
        }

        //执行appstream-compose
        if (configAppstreamCompose && !appdataFile.empty())
        {
            string basenameArg = KMBuilderUtils::argSplice("--basename=", configID);
            km_logger.info("Running appstream-compose");
            vector<string> exaArgs;
            exaArgs.push_back(configBuildRuntime ? "--prefix=/usr" : "--prefix=/app");
            exaArgs.push_back("--origin=kaiming");
            exaArgs.push_back(basenameArg);
            exaArgs.push_back(configID);
            if (!appstreamCompose(appDir, exaArgs))
            {
                return false;
            }
        }

        //提交commit！
        if (!m_pKMBuilderCache->commit("Cleanup"))
        {
            return false;
        }
    }
    else
    {
        g_print("Cache hit for cleanup, skipping\n");
    }

    return true;
}

bool KMBuilderCleanup::appstreamCompose(string appDir, vector<string> exaArgs)
{
    vector<string> args;

    args.push_back("kaiming");
    args.push_back("build");
    args.push_back("--die-with-parent");
    args.push_back("--nofilesystem=host:reset");
    args.push_back(appDir);
    args.push_back("appstream-compose");

    for (auto exaArg : exaArgs)
    {
        args.push_back(exaArg);
    }

    if (KMBuilderUtils::isInSanbox())
    {
        if (!KMBuilderUtils::hostSpawn("", 0, args))
        {
            //km_logger.info("Error hostSpawn");
            return false;
        }
    }
    else
    {
        if (!KMBuilderUtils::spawn("", 0, args))
        {
            //km_logger.info("Error spawnv");
            return false;
        }
    }

    return true;
}

void KMBuilderCleanup::configChecksumForCleanup()
{
    m_pKMBuilderCache->updateChecksumcleanup();
}

string KMBuilderCleanup::configFindAppdataFile(string appRoot)
{
    std::vector<std::string> extensions = {
        ".appdata.xml",
        ".metainfo.xml",
    };

    std::vector<std::string> dirs = {
        "share/appdata",
        "share/metainfo",
    };
    string source = "";

    for (auto dir : dirs)
    {
        string appdataDir = appRoot + "/" + dir;
        for (auto extension : extensions)
        {
            string baseName = "";

            string configRenameAppdataFile = std::any_cast<string>(m_pConfigData->attributeMap["rename-appdata-file"]);
            string configID = std::any_cast<string>(m_pConfigData->attributeMap["id"]);
            if (!configRenameAppdataFile.empty())
            {
                baseName = configRenameAppdataFile;
            }
            else
            {
                baseName = configID + extension;
            }

            source = KMBuilderUtils::getChildPath(appdataDir, baseName);
            if (fs::exists(source))
            {
                return source;
            }
        }
    }
    return "";
}

bool KMBuilderCleanup::reWriteAppdata(const std::string &filePath, const std::string &license)
{
    try
    {
        // 读取文件内容
        std::ifstream file(filePath, std::ios::binary | std::ios::ate);
        if (!file)
        {
            throw std::runtime_error("Failed to open file: " + filePath);
        }

        std::streamsize size = file.tellg();
        std::string data(size, '\0');
        file.seekg(0, std::ios::beg);
        if (!file.read(data.data(), size))
        {
            throw std::runtime_error("Failed to read file contents.");
        }

        // 解析XML
        tinyxml2::XMLDocument doc;
        tinyxml2::XMLError eResult = doc.Parse(data.c_str());
        if (eResult != tinyxml2::XML_SUCCESS)
        {
            throw std::runtime_error("Error parsing XML.");
        }

        // 假设XML的根元素是"component"
        tinyxml2::XMLElement *root = doc.RootElement();
        if (root == nullptr || strcmp(root->Name(), "component") != 0)
        {
            throw std::runtime_error("Invalid root element.");
        }

        // 查找或创建"project_license"子元素
        tinyxml2::XMLElement *licenseElem = root->FirstChildElement("project_license");
        if (licenseElem == nullptr)
        {
            licenseElem = doc.NewElement("project_license");
            root->InsertEndChild(licenseElem);

            // 设置"project_license"子元素的文本内容
            licenseElem->SetText(license.c_str());
        }

        // 将修改后的XML写入字符串
        tinyxml2::XMLPrinter printer;
        doc.Print(&printer);
        std::string newData = printer.CStr();

        // 将修改后的XML写回文件
        std::ofstream outfile(filePath, std::ios::binary);
        if (!outfile.write(newData.c_str(), newData.size()))
        {
            throw std::runtime_error("Failed to write file contents.");
        }

        return true;
    }
    catch (const std::exception &e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return false;
    }
}

bool KMBuilderCleanup::reWriteAppdataByRenameDesktopFile(const std::string &filePath, const std::string &renameDesktopFile, const std::string &desktopBasename)
{
    try
    {
        // 读取文件内容
        std::ifstream file(filePath, std::ios::binary | std::ios::ate);
        if (!file)
        {
            throw std::runtime_error("Failed to open file: " + filePath);
        }

        std::streamsize size = file.tellg();
        std::string data(size, '\0');
        file.seekg(0, std::ios::beg);
        if (!file.read(data.data(), size))
        {
            throw std::runtime_error("Failed to read file contents.");
        }

        // 解析XML
        tinyxml2::XMLDocument doc;
        tinyxml2::XMLError eResult = doc.Parse(data.c_str());
        if (eResult != tinyxml2::XML_SUCCESS)
        {
            throw std::runtime_error("Error parsing XML.");
        }

        // XML的根元素
        tinyxml2::XMLElement *nRoot = doc.RootElement();
        if (nRoot == nullptr || (strcmp(nRoot->Name(), "component") != 0 && strcmp(nRoot->Name(), "application") != 0))
        {
            throw std::runtime_error("Invalid root element.");
        }

        tinyxml2::XMLElement *nId;
        nId = nRoot->FirstChildElement("id");
        if (nId != nullptr)
        {
            string id = nId->GetText();
            if (id == renameDesktopFile)
            {
                // 设置"id"子元素的文本内容
                string configId = std::any_cast<string>(builderContext.getBuilderConfig()->getConfigData()->attributeMap["id"]);
                nId->SetText(configId.c_str());
            }
        }

        tinyxml2::XMLElement *nLaunchable;
        nLaunchable = nRoot->FirstChildElement("launchable");
        if (nLaunchable != nullptr)
        {
            string launchable = nLaunchable->GetText();
            if (launchable == renameDesktopFile)
            {
                // 设置"id"子元素的文本内容
                nId->SetText(desktopBasename.c_str());
            }
        }

        // 将修改后的XML写入字符串
        tinyxml2::XMLPrinter printer;
        doc.Print(&printer);
        std::string newData = printer.CStr();

        // 将修改后的XML写回文件
        std::ofstream outfile(filePath, std::ios::binary);
        if (!outfile.write(newData.c_str(), newData.size()))
        {
            throw std::runtime_error("Failed to write file contents.");
        }

        return true;
    }
    catch (const std::exception &e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return false;
    }
}

bool KMBuilderCleanup::renameIconCb(string sourceName, string fullDir, string relDir, bool *found, int depth)
{
    string configRenameIcon = std::any_cast<string>(builderContext.getBuilderConfig()->getConfigData()->attributeMap["rename-icon"]);
    if (sourceName.find(configRenameIcon) == 0)
    {
        // 检查文件名（从configRenameIcon之后）是否以"."或"-symbolic."开头
        bool bDotPrefix = sourceName.substr(configRenameIcon.length()).find(".") == 0 ? true : false;
        bool bSymbolicPrefix = sourceName.substr(configRenameIcon.length()).find("-symbolic.") == 0 ? true : false;
        if (depth == 3 && (bDotPrefix || bSymbolicPrefix))
        {
            string extension = sourceName.substr(configRenameIcon.length());
            string configId = std::any_cast<string>(builderContext.getBuilderConfig()->getConfigData()->attributeMap["id"]);
            string newName = KMBuilderUtils::argSplice(configId, extension);
            int res;
            *found = true;

            bool configCopyIcon = std::any_cast<bool>(builderContext.getBuilderConfig()->getConfigData()->attributeMap["copy-icon"]);
            km_logger.info("%s icon %s/%s to %s/%s", configCopyIcon ? "Copying" : "Renaming", relDir.c_str(), sourceName.c_str(), relDir.c_str(), newName.c_str());

            // 检查路径是否为绝对路径
            if (!fs::path(sourceName).is_absolute())
            {
                sourceName = fullDir + "/" + sourceName;
            }
            // 检查路径是否为绝对路径
            if (!fs::path(newName).is_absolute())
            {
                newName = fullDir + "/" + newName;
            }

            if (configCopyIcon)
            {
                try
                {
                    std::filesystem::create_symlink(sourceName, newName);
                }
                catch (const std::filesystem::filesystem_error &e)
                {
                    std::cerr << "Failed to create symlink: " << e.what() << std::endl;
                    return false;
                }
            }
            else
            {
                try
                {
                    std::filesystem::rename(sourceName, newName);
                }
                catch (const std::filesystem::filesystem_error &e)
                {
                    std::cerr << "Failed to rename file: " << e.what() << std::endl;
                    return false;
                }
            }
        }
        else
        {
            if (std::filesystem::is_regular_file(sourceName))
            {
                km_logger.info("%s/%s matches 'rename-icon', but not a regular file", fullDir.c_str(), sourceName.c_str());
            }
            else if (depth != 3)
            {
                km_logger.info("%s/%s matches 'rename-icon', but not at depth 3", fullDir.c_str(), sourceName.c_str());
            }
            else
            {
                km_logger.info("%s/%s matches 'rename-icon', but name does not continue with '.' or '-symbolic.'", fullDir.c_str(), sourceName.c_str());
            }
        }
    }
    return true;
}

// 递归遍历目录的函数
bool KMBuilderCleanup::foreachFileHelper(string sourceName, string fullDir, string relDir, FileHandler handler, bool *found, int depth)
{
    if (!fs::exists(sourceName))
    {
        return false;
    }

    fs::path dirPath(sourceName);

    try
    {
        for (const auto &entry : std::filesystem::directory_iterator(dirPath))
        {
            // 如果是目录，则递归遍历
            if (entry.is_directory())
            {
                fs::path child_path = dirPath / entry.path().filename();

                if (!foreachFileHelper(child_path, child_path, relDir, handler, found, depth + 1))
                {
                    return false;
                }
            }
            // 调用回调函数处理文件
            else
            {
                if (!handler(entry.path().filename(), fullDir, relDir, found, depth))
                {
                    return false; // 如果回调函数返回false，则停止遍历
                }
            }
        }
    }
    catch (const std::exception &e)
    {
        std::cerr << "Error occurred while traversing directory: " << e.what() << std::endl;
        return false;
    }
    return true;
}