/*
 * 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 "KMBrowserUtils.h"

#include <fstream>
#include <set>
#include <array>

/**
 * @brief 获取系统中所有已注册的浏览器可执行文件路径
 * @return 浏览器可执行文件路径列表
 */
std::vector<std::string> KMBrowserUtils::getAllBrowserExecutables()
{
    auto desktopNames = getRegisteredDesktopFiles();
    auto desktopPaths = locateDesktopFiles(desktopNames);
    auto executables = extractExecutables(desktopPaths);
    writeExecutablesToFile(executables);
    return executables;
}

/**
 * @brief 从配置文件中读取已保存的浏览器路径
 * @return 浏览器可执行文件路径列表
 */
std::vector<std::string> KMBrowserUtils::loadExecutablesFromFile()
{
    fs::path filePath = "/opt/kaiming-tools/config/browsers.conf";
    std::vector<std::string> executables;

    // 检查文件是否存在
    if (!fs::exists(filePath))
    {
        KMDebug("配置文件不存在: " + filePath.string());
        return executables;
    }

    std::ifstream ifs(filePath);
    if (!ifs.is_open())
    {
        KMDebug("无法打开文件: " + filePath.string());
        return executables;
    }

    std::string line;
    while (std::getline(ifs, line))
    {
        if (!line.empty())
        {
            executables.push_back(line);
        }
    }

    ifs.close();
    return executables;
}

/**
 * @brief 执行系统命令并获取输出结果
 * @param cmd 要执行的命令
 * @return 包含命令输出和错误码的pair
 */
std::pair<std::string, std::error_code> KMBrowserUtils::execCommand(const std::string &command)
{
    if (command.empty())
    {
        return { "", std::make_error_code(std::errc::invalid_argument) };
    }

    std::array<char, 256> buffer{};
    std::ostringstream result;

    // 重置 errno
    errno = 0;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(command.c_str(), "r"), pclose);

    if (!pipe)
    {
        return { "", std::make_error_code(static_cast<std::errc>(errno)) };
    }

    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
    {
        result << buffer.data();
    }

    return { result.str(), std::error_code{} };
}

/**
 * @brief 获取已注册的浏览器桌面文件列表
 * @return 浏览器桌面文件名列表
 */
std::vector<std::string> KMBrowserUtils::getRegisteredDesktopFiles()
{
    std::vector<std::string> desktopFiles;
    auto [output, ec] = execCommand("gio mime x-scheme-handler/http");
    if (ec)
    {
        KMDebug("执行命令失败: gio mime x-scheme-handler/http, 错误: " + ec.message());
        return desktopFiles;
    }

    std::istringstream iss(output);
    std::string line;
    bool parseApps = false;

    while (std::getline(iss, line))
    {
        if (line.find("Registered applications") != std::string::npos || line.find("已注册的应用程序") != std::string::npos)
        {
            parseApps = true;
            continue;
        }
        if (parseApps)
        {
            size_t pos = line.find(".desktop");
            if (pos != std::string::npos)
            {
                std::string file = line.substr(0, pos + 8);
                file.erase(0, file.find_first_not_of(" \t"));
                // 去除尾部空白字符
                file.erase(file.find_last_not_of(" \t") + 1);
                if (!file.empty())
                {
                    desktopFiles.push_back(file);
                }
            }
        }
    }

    return desktopFiles;
}

/**
 * @brief 根据桌面文件名定位完整的桌面文件路径
 * @param desktopNames 桌面文件名列表
 * @return 完整的桌面文件路径列表
 */
std::vector<fs::path> KMBrowserUtils::locateDesktopFiles(const std::vector<std::string> &desktopNames)
{
    std::vector<fs::path> result;

    // 获取XDG数据目录
    const char *xdgDirsEnv = std::getenv("XDG_DATA_DIRS");
    std::string xdgDirs = xdgDirsEnv ? xdgDirsEnv : "/usr/local/share:/usr/share";

    // 获取当前登录用户
    std::string loginUser = KMStorageDir::getLoginUser();

    // 添加用户本地目录（如果能获取到用户信息）
    if (!loginUser.empty())
    {
        std::string userLocalDir = "/home/" + loginUser + "/.local/share";
        // 将用户目录添加到搜索路径的开头，具有更高优先级
        xdgDirs = userLocalDir + ":" + xdgDirs;
    }

    // 添加kaiming默认目录
    xdgDirs += ":/opt/kaiming/share";

    std::istringstream ss(xdgDirs);
    std::string dataDir;

    while (std::getline(ss, dataDir, ':'))
    {
        // 清理路径字符串
        dataDir.erase(0, dataDir.find_first_not_of(" \t"));
        dataDir.erase(dataDir.find_last_not_of(" \t") + 1);

        if (dataDir.empty())
            continue;

        KMDebug("正在搜索目录: " + dataDir);

        fs::path applicationsDir = fs::path(dataDir) / "applications";
        if (!fs::exists(applicationsDir))
        {
            KMDebug("目录不存在: " + applicationsDir.string());
            continue;
        }

        for (const auto &desktopName : desktopNames)
        {
            fs::path fullPath = applicationsDir / desktopName;
            if (fs::exists(fullPath))
            {
                result.push_back(fullPath);
            }
        }
    }
    return result;
}

/**
 * @brief 从桌面文件中提取可执行文件路径
 * @param desktopPaths 桌面文件路径列表
 * @return 可执行文件路径列表
 */
std::vector<std::string> KMBrowserUtils::extractExecutables(const std::vector<fs::path> &desktopPaths)
{
    std::set<std::string> uniqueExecutables;

    for (const auto &desktopPath : desktopPaths)
    {
        std::ifstream file(desktopPath);
        if (!file.is_open())
        {
            KMDebug("无法打开.desktop文件: " + desktopPath.string());
            continue;
        }

        std::string line;
        while (std::getline(file, line))
        {
            if (line.rfind("Exec=", 0) == 0)
            {
                std::string exec = line.substr(5);

                // 处理引号包围的可执行文件路径
                if (!exec.empty() && exec.front() == '"' && exec.back() == '"')
                {
                    exec = exec.substr(1, exec.length() - 2);
                }

                // 移除参数，只保留可执行文件路径
                size_t spacePos = exec.find(' ');
                if (spacePos != std::string::npos)
                {
                    exec = exec.substr(0, spacePos);
                }

                // 移除可能的环境变量前缀
                size_t envPos = exec.find_first_of("%");
                if (envPos != std::string::npos)
                {
                    exec = exec.substr(0, envPos);
                }

                // 清理可执行文件路径
                exec.erase(0, exec.find_first_not_of(" \t"));
                exec.erase(exec.find_last_not_of(" \t") + 1);

                if (!exec.empty())
                {
                    uniqueExecutables.insert(exec);
                }
                break;
            }
        }
    }
    return { uniqueExecutables.begin(), uniqueExecutables.end() };
}

/**
 * @brief 将浏览器可执行文件路径写入配置文件
 * @param executables 可执行文件路径列表
 */
void KMBrowserUtils::writeExecutablesToFile(const std::vector<std::string> &executables)
{
    fs::path configDir = "/opt/kaiming-tools/config";
    fs::path filePath = configDir / "browsers.conf";

    // 安全检查并创建目录
    std::error_code ec;
    if (!fs::exists(configDir))
    {
        if (!fs::create_directories(configDir, ec))
        {
            KMDebug("无法创建配置目录: " + ec.message());
            return;
        }
    }

    // 检查内容是否发生变化
    std::set<std::string> newContent(executables.begin(), executables.end());
    std::set<std::string> oldContent;

    if (fs::exists(filePath))
    {
        std::ifstream ifs(filePath);
        if (ifs.is_open())
        {
            std::string line;
            while (std::getline(ifs, line))
            {
                oldContent.insert(line);
            }
            ifs.close();
        }
    }

    // 内容无变化，直接返回
    if (oldContent == newContent)
    {
        return;
    }

    // 写入新内容
    std::ofstream ofs(filePath, std::ios::trunc);
    if (!ofs.is_open())
    {
        KMDebug("无法写入配置文件: " + filePath.string());
        return;
    }

    for (const auto &executable : newContent)
    {
        ofs << executable << "\n";
    }
    ofs.close();

    if (ofs.fail())
    {
        KMDebug("写入配置文件时出错: " + filePath.string());
    }
    else
    {
        KMInfo("已更新浏览器配置文件: " + filePath.string());
    }
}