#include "KMReference.h"

#include <regex>

#include "common/KMUtil.h"
#include "common/KMLogger.h"
#include "common/KMInstalledAppQuery.h"
#include "common/KMInfoJson.h"

#define TOPDIR "layers"
#define DEVEL "devel"

using namespace std;

enum RefIdentify
{
    REFIDENTIFY_UNKNOWN,
    REFIDENTIFY_ID,
    REFIDENTIFY_VERSION,
    REFIDENTIFY_MODULE,
    REFIDENTIFY_CHANNEL,
    REFIDENTIFY_MAX
};

class KMReference::Private
{
public:
    string id;
    string channel;
    string version;
    string arch;
    string module;
    string dir;
    string baseDir;
    string runtimeDir;
    std::vector<string> commands;
    std::map<std::string, std::string> envs;
};

KMReference::KMReference()
    : d_ptr(make_shared<Private>())
{
}

/**
 * @brief 解析包名
 * @param id 包名
 * @return KMReference
 */
bool KMReference::parse(const std::string &id)
{
    regex pattern(R"(^([^\/]*)(?:\/([^\/]*))?(?:\/([^\/]*))?(?:\/([^\/]*))?$)");
    smatch matches{};
    if (!regex_match(id, matches, pattern))
        return false;

    if (matches.size() < REFIDENTIFY_MAX)
        return false;

    if (!regex_match(id, matches, pattern))
    {
        KMError("appid does not match the rule");
        return false;
    }
    d_ptr->id = matches[REFIDENTIFY_ID].str();
    d_ptr->version = matches[REFIDENTIFY_VERSION].str();
    d_ptr->module = matches[REFIDENTIFY_MODULE].str();
    d_ptr->channel = matches[REFIDENTIFY_CHANNEL].str();

    KMDebug("id: " + d_ptr->id + " version: " + d_ptr->version + " module:" + d_ptr->module + " channel:" + d_ptr->channel);

    return true;
}

/**
 * @brief 获取目录
 * @param id 包名
 */
bool KMReference::getLayerDir(const std::string &id, bool isDevel)
{
    if (!parse(id))
    {
        KMError("can't parse id " + id);
        return false;
    }

    if (!getPackageDir(isDevel))
    {
        KMError("can't get package or depend dir " + id);
        return false;
    }

    return true;
}

/**
 * @brief 获取app, base, runtime的目录
 * @return true 成功，false 失败
 */
bool KMReference::getPackageDir(bool isDevel)
{
    KMInstalledAppQuery appQuery;
    appQuery.id(d_ptr->id).channel(d_ptr->channel).version(d_ptr->version).module(d_ptr->module);
    std::vector<KMRef> refs = appQuery.query();
    if (refs.empty())
    {
        KMError("can't find app " + d_ptr->id);
        return false;
    }

    KMRef ref = refs.front();
    KMFolder sysFolder(KMFolder::getDefaultSysBaseLocationPath(), false);
    d_ptr->dir = sysFolder.getPath() / string("layers") / ref.channel / ref.arch / ref.kind / ref.id / ref.module / ref.version;

    if (!fs::exists(d_ptr->dir))
    {
        KMError("can't find app dir " + d_ptr->dir);
        return false;
    }

    string infofile = d_ptr->dir + string("/info.json");
    if (!fs::exists(infofile))
    {
        KMError("can't find app info file " + infofile);
        return false;
    }

    // 若获取失败会抛出异常
    KMInfoJson info;
    info.loadFile(infofile);
    KMRef baseRef = info.baseRef();

    // 获取命令列表
    d_ptr->commands = info.commands;
    // 获取环境变量app列表
    std::map<string, string> appEnvs = info.envs;

    // 获取base目录
    std::string baseId = baseRef.id;
    appQuery.ref(baseRef);
    refs.clear();
    refs = appQuery.query();
    if (refs.empty())
    {
        KMError("can't find base ref : " + baseId);
        return false;
    }
    baseRef = refs.front();
    if (isDevel)
    {
        baseRef.module = DEVEL;
    }
    d_ptr->baseDir = sysFolder.getPath() / string(TOPDIR) / baseRef.channel / baseRef.arch / baseRef.kind / baseRef.id / baseRef.module / baseRef.version;

    string baseInfoFile = d_ptr->baseDir + string("/info.json");
    if (!fs::exists(baseInfoFile))
    {
        KMError("can't find base info file " + baseInfoFile);
        return false;
    }
    KMInfoJson baseInfo;
    baseInfo.loadFile(baseInfoFile);
    std::map<string, string> baseEnvs = baseInfo.envs;

    // 获取runtime目录
    KMRef runtimeRef = info.runtimeRef();
    std::map<string, string> runtimeEnvs;
    if (!runtimeRef.id.empty())
    {
        string runtimeId = runtimeRef.id;
        appQuery.ref(runtimeRef);
        refs.clear();
        refs = appQuery.query();
        if (refs.empty())
        {
            KMInfo("can't find runtime ref: " + runtimeId);
        }
        runtimeRef = refs.front();
        if (isDevel)
        {
            runtimeRef.module = DEVEL;
        }
        d_ptr->runtimeDir = sysFolder.getPath() / string(TOPDIR) / runtimeRef.channel / runtimeRef.arch / runtimeRef.kind / runtimeRef.id / runtimeRef.module / runtimeRef.version;

        string runtimeInfoFile = d_ptr->runtimeDir + string("/info.json");
        if (!fs::exists(runtimeInfoFile))
        {
            KMError("can't find runtime info file " + runtimeInfoFile);
            return false;
        }
        KMInfoJson runtimeInfo;
        runtimeInfo.loadFile(runtimeInfoFile);
        runtimeEnvs = runtimeInfo.envs;
    }

    // 按优先级设置环境变量
    appendEnvs(d_ptr->envs, baseEnvs);
    appendEnvs(d_ptr->envs, runtimeEnvs);
    appendEnvs(d_ptr->envs, appEnvs);

    return true;
}

/**
 * @brief 环境变量合并，环境变量优先级：base > runtime > app且处理追加环境变量例如$PATH=/usr:$PATH
 * @param envs 环境变量列表
 * @param newEnvs 追加环境变量列表
 */
void KMReference::appendEnvs(std::map<std::string, std::string> &envs, const std::map<std::string, std::string> &newEnvs)
{
    for (auto [k, v] : newEnvs)
    {
        if (v.find(string("$") + k) == string::npos)
        {
            envs[k] = v;
            continue;
        }
        auto it = envs.find(k);
        if (it == envs.end())
        {
            envs[k] = v;
        }
        else
        {
            envs[k] = it->second + string(":") + v;
        }
    }
}

/**
 * @brief 获取app目录
 * @param appDir app目录
 * @return app目录
 */
bool KMReference::getAppDir(std::string &appDir)
{
    if (!fs::exists(d_ptr->dir))
    {
        KMError("can't find app dir " + d_ptr->dir);
        return false;
    }

    appDir = d_ptr->dir;
    return true;
}

/**
 * @brief 获取base目录
 * @param baseDir base目录
 * @return base目录
 */
bool KMReference::getBaseDir(std::string &baseDir)
{
    if (!fs::exists(d_ptr->baseDir))
    {
        KMError("can't find base dir " + d_ptr->baseDir);
        return false;
    }

    baseDir = d_ptr->baseDir;
    return true;
}

/**
 * @brief 获取runtime目录
 * @param runtimeDir runtime目录
 * @return runtime目录
 */
bool KMReference::getRuntimeDir(std::string &runtimeDir)
{
    if (!fs::exists(d_ptr->runtimeDir))
    {
        KMInfo("can't find runtime dir " + d_ptr->runtimeDir);
    }

    runtimeDir = d_ptr->runtimeDir;
    return true;
}

/**
 * @brief 获取Commands命令
 * @param commands 命令列表
 * @return true 成功，false 失败
 */
bool KMReference::getCommands(std::vector<std::string> &commands)
{
    commands = d_ptr->commands;
    return true;
}

/**
 * @brief 获取环境变量列表
 * @param envs 环境变量列表
 * @return true 成功，false 失败
 */
bool KMReference::getEnvs(std::map<std::string, std::string> &envs)
{
    envs = d_ptr->envs;
    return true;
}