#include "KMBuilderInfo.h"

#include <fstream>
#include <iostream>
#include <chrono>
#include <boost/process.hpp>
#include <boost/foreach.hpp>

#include <gio/gio.h>

#include "KMBuilderUtils.h"
#include "KMBuilderConfig.h"
#include "KMBuilderContext.h"
#include "KMBuilderCache.h"
#include "KMBuilderGlobal.h"
#include "common/KMLogger.h"
#include "common/KMFile.h"
#include "common/KMSummaryUtils.h"

namespace fs = std::filesystem;
namespace bp = boost::process;

KMBuilderInfo::KMBuilderInfo()
{}

KMBuilderInfo::~KMBuilderInfo()
{
    // 清除临时文件夹
    if (!m_tmpPath.empty())
    {
        std::string result;
        KMBuilderUtils::spawn("rm", { "-rf", m_tmpPath }, result);
    }
}

// 初始化函数
bool KMBuilderInfo::writeInfo()
{
    if (builderContext.isPackage() && !builderContext.isBuildOnly() && !builderContext.isFinishOnly() && !builderContext.isExportOnly())
    {
        KMDebug("Generate offline package 'repo.tar' from --repo");

        initialize();

        if (m_repoDir.empty())
        {
            KMError("Must set --repo option");
            return false;
        }

        if (!fs::exists(m_repoDir))
        {
            KMError("The path of --repo not exists");
            return false;
        }

        m_arch = builderContext.getArch();

        KMBuilderGlobal global;
        m_branch = global.configGetBranch();
        m_source = global.appId();

        if (!parseAppdataXml())
        {
            return false;
        }

        if (!generateRepoMd5sum())
        {
            KMError("Failed to md5sum the path of --repo");
            return false;
        }

        if (!parseRepoSummary())
        {
            return false;
        }

        setDependsValue("");
        setMaintainerValue("Kylin Team <team+kylin@tracker.debian.org>");
        setPriorityValue("optional");
        setFileValue("repo.tar");
        setCertOuValue("");
        setCertOValue("");
        setCertCnValue("");
        setReliabilityValue("normal");
        setnormalValue("");

        // 将数据写入TOML文件
        if (writeKMinfoFile())
        {
            convertToOkPackage();
        }
    }

    return true;
}

void KMBuilderInfo::initialize()
{
    // 初始化打包路径
    m_appDir = builderContext.getAppDir();
    m_runDir = builderContext.getRunDir();
    m_repoDir = builderContext.getRepo();
}

std::string KMBuilderInfo::getAppdataXmlFile(const std::string &path)
{
    // 正常只有一个文件
    std::string result;
    if (!KMBuilderUtils::spawn("ls", { path }, result))
    {
        KMInfo(result);
        return "";
    }

    return KMBuilderUtils::buildFilename(path, result);
}

bool KMBuilderInfo::parseAppdataXml()
{
    std::string xmlFile = getAppdataXmlFile(KMBuilderUtils::buildFilename(m_appDir, "files/share/appdata"));
    if (xmlFile.empty())
    {
        xmlFile = getAppdataXmlFile(KMBuilderUtils::buildFilename(m_appDir, "files/share/metainfo"));
        if (xmlFile.empty())
        {
            KMError("Not found *.appdata.xml or *.metadata.xml in files/share/appdata or files/share/metainfo");
            return false;
        }
    }

    try
    {
        pt::read_xml(xmlFile, m_treeXml);
    }
    catch (const pt::xml_parser_error &e)
    {
        KMError(e.what());
        return false;
    }

    m_description = m_treeXml.get<std::string>("component.summary", "");
    m_homepage = m_treeXml.get<std::string>("component.url", "");
    // auto iter = m_treeXml.find("component.releases");
    // if (iter != m_treeXml.not_found())
    {
        for (const auto &node : m_treeXml.get_child("component.releases"))
        {
            std::string version = node.second.get<std::string>("<xmlattr>.version");
            if (!version.empty())
            {
                m_version = version;
                break;
            }
        }
    }

    // 没有设置version则不允许封包
    if (m_version.empty())
    {
        KMError("Please set component.release's <xmlattr>.version in file *.appdata.xml or *.metadata.xml");
        return false;
    }

    return true;
}

bool KMBuilderInfo::generateRepoMd5sum()
{
    // 压缩到文件/tmp/xxxxxx/repo.tar
    const auto now = std::chrono::system_clock::now();
    std::string seconds = std::to_string(std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count());
    m_tmpPath = KMBuilderUtils::buildFilename("/tmp", seconds, m_source);
    KMBuilderUtils::mkpath(m_tmpPath);
    std::string workingDir = fs::path(m_repoDir).parent_path();
    std::string baseRepo = fs::path(m_repoDir).filename();
    std::string tmpFile = KMBuilderUtils::buildFilename(m_tmpPath, "repo.tar");
    KMDebug("mkdir for tar : " + m_tmpPath);

    // 压缩打包
    std::string result;
    if (!KMBuilderUtils::spawn("tar", workingDir, { "-czvf", tmpFile, baseRepo }, result))
    {
        KMError(result);
        return false;
    }
    else
    {
        KMDebug(result);
    }

    // 求md5sum
    std::string md5sumResult;
    if (!KMBuilderUtils::spawn("md5sum", { tmpFile }, md5sumResult))
    {
        KMError(result);
        return false;
    }
    else
    {
        KMDebug(md5sumResult);
    }

    // 解析result，形如：3fe0b7c6becb816e21da5973e9481506  repo.tar
    size_t startPos = 0;
    size_t len = md5sumResult.length();
    for (; startPos < len; ++startPos)
    {
        if (std::isspace(md5sumResult.at(startPos)))
        {
            break;
        }
    }
    m_md5 = md5sumResult.substr(0, startPos);

    return true;
}

/**
 * @brief : 读取repo仓库中的summary文件的内容
 * @return: KMVariant 
 */
bool KMBuilderInfo::parseRepoSummary()
{
    std::string result;
    if (KMBuilderUtils::spawn("ostree", { "summary", "--raw", "--repo", m_repoDir }, result))
    {
        KMDebug(result);
        GVariant *summary_v = g_variant_parse(G_VARIANT_TYPE(VAR_SUMMARY_TYPESTRING), result.c_str(), NULL, NULL, NULL);
        if (summary_v != NULL)
        {
            KMVariant summaryGuard = summary_v;

            VarSummaryRef summaryRef = var_summary_from_gvariant(summary_v);
            VarMetadataRef meta = var_summary_get_metadata(summaryRef);

            VarVariantRef cache_vv;
            if (!var_metadata_lookup(meta, "xa.cache", NULL, &cache_vv))
            {
                KMError("summary信息中未找到xa.cache");
                return false;
            }

            VarCacheRef cache = var_cache_from_variant(cache_vv);
            int lengthOfCache = var_cache_get_length(cache);
            for (int i = 0; i < lengthOfCache; ++i)
            {
                VarCacheEntryRef cacheEntryRef = var_cache_get_at(cache, i);
                std::string pref = var_cache_entry_get_key(cacheEntryRef);
                VarCacheDataRef cacheDataRef = var_cache_entry_get_value(cacheEntryRef);
                guint64 installedSize = varCacheDataGetInstalledSize(cacheDataRef);
                guint64 downloadSize = varCacheDataGetDownloadSize(cacheDataRef);

                // pref形如：runtime/top.openkylin.Music.Debug/x86_64/stable 或 app/top.openkylin.Music/x86_64/stable
                std::vector<std::string> sections = KMBuilderUtils::splitString(pref, "/");
                if (4 == sections.size())
                {
                    m_type.push_back(sections.at(0));

                    const std::string& appId = sections.at(1);
                    m_path.push_back(appId);

                    std::string name = appId;
                    std::size_t pos = appId.find(".", appId.find(".") + 1);
                    if (pos != std::string::npos)
                    {
                        name = appId.substr(pos + 1);
                    }
                    m_name.push_back(name);

                    m_size.push_back(std::to_string(installedSize));
                }
                
            }

            return true;
        }
    }
    else
    {
        KMError(result);
    }

    return false;
}

void KMBuilderInfo::setArchValue(const std::string &value)
{
    m_arch = value;
}

void KMBuilderInfo::setBranchValue(const std::string &value)
{
    m_branch = value;
}

void KMBuilderInfo::setDependsValue(const std::string &value)
{
    m_depends = value;
}

void KMBuilderInfo::setMaintainerValue(const std::string &value)
{
    m_maintainer = value;
}

void KMBuilderInfo::setPriorityValue(const std::string &value)
{
    m_priority = value;
}

void KMBuilderInfo::setFileValue(const std::string &value)
{
    m_file = value;
}

void KMBuilderInfo::setCertOuValue(const std::string &value)
{
    m_certOu = value;
}

void KMBuilderInfo::setCertOValue(const std::string &value)
{
    m_certO = value;
}

void KMBuilderInfo::setCertCnValue(const std::string &value)
{
    m_certCn = value;
}

void KMBuilderInfo::setReliabilityValue(const std::string &value)
{
    m_reliability = value;
}

void KMBuilderInfo::setnormalValue(const std::string &value)
{
    m_normal = value;
}

// 将数据写入TOML文件的接口
bool KMBuilderInfo::writeKMinfoFile()
{
    std::string filename = KMBuilderUtils::buildFilename(m_tmpPath, ".okinfo");
    std::ofstream KMinfoFile(filename);
    if (!KMinfoFile.is_open())
    {
        KMError("Failed to open TOML file for writing.");
        return false;
    }

    KMinfoFile << "[Generic]" << std::endl;
    KMinfoFile << "Arch = \'" << m_arch << "\'" << std::endl;
    KMinfoFile << "Branch = \'" << m_branch << "\'" << std::endl;
    KMinfoFile << "Depends = [" << m_depends << "]" << std::endl;
    KMinfoFile << "Description = \'" << m_description << "\'" << std::endl;
    KMinfoFile << "MD5 = \'" << m_md5 << "\'" << std::endl;
    KMinfoFile << "Homepage = \'" << m_homepage << "\'" << std::endl;
    KMinfoFile << "Maintainer = \'" << m_maintainer << "\'" << std::endl;
    KMinfoFile << "Priority = \'" << m_priority << "\'" << std::endl;
    KMinfoFile << "Source = \'" << m_source << "\'" << std::endl;
    KMinfoFile << "Version = \'" << m_version << "\'" << std::endl;
    KMinfoFile << "File = \'" << m_file << "\'" << std::endl;
    KMinfoFile << std::endl;

    int count = m_type.size();
    for (int i = 0; i < count; ++i)
    {
        KMinfoFile << "[[Refs]]" << std::endl;
        KMinfoFile << "Type = \'" << m_type[i] << "\'" << std::endl;
        KMinfoFile << "Name = \'" << m_name[i] << "\'" << std::endl;
        KMinfoFile << "Path = \'" << m_path[i] << "\'" << std::endl;
        KMinfoFile << "Installed-Size = \'" << m_size[i] << "\'" << std::endl;
        KMinfoFile << std::endl;
    }

    KMinfoFile << "[Sign]" << std::endl;
    KMinfoFile << "CertOu = [" << m_certOu << "]" << std::endl;
    KMinfoFile << "CertO = [" << m_certO << "]" << std::endl;
    KMinfoFile << "CertCn = [" << m_certCn << "]" << std::endl;
    KMinfoFile << "Reliability = \'" << m_reliability << "\'" << std::endl;
    KMinfoFile << "#normal/limit" << m_normal << "" << std::endl;

    KMinfoFile.close();
    return true;
}

std::string KMBuilderInfo::convertArch()
{
    if (m_arch == "x86_64")
    {
        return "amd64";
    }
    else if (m_arch == "aarch64")
    {
        return "arm64";
    }
    else if (m_arch.compare(0, 9, "loongarch") == 0 || m_arch == "loongarch")
    {
        return "loongarch64";
    }
    else if (m_arch == "mips64")
    {
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
        return "mips64el";
#endif
    }
    else if (m_arch == "sw" || m_arch == "sw64")
    {
        return "sw64";
    }

    // i386，其他的架构不支持
    return m_arch;
}

void KMBuilderInfo::convertToOkPackage()
{
    std::string destDir = m_runDir;
    std::string ok = KMBuilderUtils::buildFilename(destDir, m_source + "_" + m_version + "_" + convertArch() + ".ok");
    std::string workingDir = fs::path(m_tmpPath).parent_path();

    std::string result;
    if (KMBuilderUtils::spawn("tar", workingDir, { "-czvf", ok, m_source }, result))
    {
        KMDebug(result);
    }
    else
    {
        KMError(result);
    }
}

