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

#include <map>
#include <sstream>
#include <unistd.h>

#include "common/KMConfigData.h"
#include "common/KMCrun.h"
#include "common/KMLogger.h"
#include "common/KMUtil.h"

REGISTER_SUBCOMMAND_DYNCREATE(enter, KMEnter)

using namespace std;
/**
 * @brief 安装子命令的参数解析
 */
class KMEnter::Options : public KMOption::Options
{
public:
    Options() = default;
    ~Options() override = default;

protected:
    /**
    * @brief 命令行参数解析后操作
    */
    void preParseHook() override;

    /**
    * @brief 命令行参数解析后操作
    */
    void postParseHook() override;

private:
    /**
    * @brief 命令行参数添加描述选项
    */
    void addOptions();

public:
    bool m_bHelp;
    string commandArgs;
    string ref;
    vector<string> args;
    std::string m_splitOfArgs;
};

void KMEnter::Options::preParseHook()
{
    addOptions();
}

/**
 * @brief 命令行参数解析后操作
 */
void KMEnter::Options::postParseHook()
{
    if (m_bHelp)
    {
        showUsage();
        exit(EXIT_SUCCESS);
    }
    args.clear();
    int posRef = getPositionalOptionIndex("ref");
    int steps = m_splitOfArgs.empty() ? 1 : 2;
    if (posRef > 0 && m_originalArgs.size() > posRef + steps)
    {
        for (int i = posRef + steps; i < m_originalArgs.size(); i++)
        {
            args.push_back(m_originalArgs.at(i));
        }
    }
}
/**
 * @brief 命令行参数添加描述选项
 */
void KMEnter::Options::addOptions()
{
    setDescription(_("\nUsage:\n \tkaiming enter [options...] containerid/pid/appid [--] commands - run commands in a running container\n"));

    //帮助选项
    addOption("help", "h", KMOption::value(&m_bHelp)->defaultValue(false), _("display Help Options"));

    //位置选项
    addPositionOption("ref", KMOption::value<std::string>(&ref), 1, "");
    addPositionOption("--", KMOption::value(&m_splitOfArgs), 1, _("The start flag of command's arguments(not kaiming's options)"));
    addPositionOption("args", KMOption::value<std::vector<std::string>>(&args), -1, "");
}

class KMEnter::Private
{
public:
    shared_ptr<Options> m_options;
    vector<string> commandArg;
};

KMEnter::KMEnter()
    : m_dPtr(make_shared<Private>())
{
    init();
}

KMEnter::~KMEnter() = default;

/**
 * @brief enter
 * 在正在运行的容器中运行命令
 * @param argc 参数个数
 * @param argv 参数列表
 * @return int 0:成功,非0:失败
 */
int KMEnter::dispose(int argc, char **argv)
{
    KMDebug("KMEnter::dispose invoke begin");
    if (!parseArgs(argc, argv))
    {
        return false;
    }

    return start() ? EXIT_SUCCESS : -1;
}

/**
 * @brief 解析命令行参数
 * @param argc 参数个数
 * @param argv 参数列表
 * @return true 成功 false 失败
 */
bool KMEnter::parseArgs(int argc, char **argv)
{
    // 参数解析
    m_dPtr->m_options->parseCommandLine(argc, argv);

    for (auto &arg : m_dPtr->m_options->args)
    {
        m_dPtr->commandArg.emplace_back(arg);
    }
    
    if (m_dPtr->commandArg.empty())
    {
        m_dPtr->commandArg.emplace_back("bash");
    }

    return true;
}

/**
 * @brief 开始运行
 * @return true 成功 false 失败
 */
bool KMEnter::start()
{
    string enterArgs = m_dPtr->m_options->ref;

    int uid = getuid();
    gid_t gid = getgid();
    bool argsFind = false;
    vector<KMEnterInfoList> enterInfos = getInfos();
    for (const auto &enterInfo : enterInfos)
    {
        if (enterArgs == enterInfo.containerid || enterArgs == enterInfo.appid || enterArgs == to_string(enterInfo.pid))
        {
            std::string uid_gid_stream = std::string("--user=") + to_string(uid) + std::string(":") + to_string(gid);
            vector<std::string> args_enter = { "exec", "--tty", uid_gid_stream, enterInfo.containerid };
            vector<string> cmdArgs = assembleCommand();
            for (const auto &arg : cmdArgs)
            {
                args_enter.push_back(arg);
            }
            if (!KMCrun::exec(args_enter))
            {
                KMError(string("can not exec ") + enterArgs);
                return EXIT_FAILURE;
            }
            argsFind = true;
            break;
        }
    }

    if (!argsFind)
    {
        KMError(string("can not find ") + enterArgs);
        return EXIT_FAILURE;
    }

    return true;
}

/**
 * @brief 组装command运行命令
 */
vector<string> KMEnter::assembleCommand() const
{
    string command;
    if (!m_dPtr->commandArg.empty())
    {
        for (auto const &arg : m_dPtr->commandArg)
        {
            command.append(" ").append(arg);
        }
    }
    if (command.empty())
    {
        KMError("command is empty");
    }

    std::vector<string> args = { "/bin/bash", "--login", "-e", "-c" };
    args.emplace_back(command);
    args.emplace_back("; wait");
    return args;
}

/**
 * @brief 获取信息
 */
vector<KMEnterInfoList> KMEnter::getInfos() const
{
    vector<ContainerListItem> item;
    vector<KMEnterInfoList> enterInfos;
    string enterArgs = m_dPtr->m_options->ref;
    if (vector<string> listArgs = { "list", "--format=json" }; !KMCrun::exec<vector<ContainerListItem>>(listArgs, item))
    {
        KMError(string("can not find ") + enterArgs);
    }

    for (auto const &i : item)
    {
        KMEnterInfoList enterInfo;
        enterInfo.containerid = i.id;
        enterInfo.pid = i.pid;
        enterInfo.path = i.bundle;
        State itemAppid;
        vector<string> stateArgs = { "state", enterInfo.containerid };

        if (!KMCrun::exec<State>(stateArgs, itemAppid))
        {
            continue;
        }

        enterInfo.appid = itemAppid.annotations.value_or(map<string, string>{})["kaiming.app.id"];
        enterInfos.emplace_back(enterInfo);
    }
    return enterInfos;
}

void KMEnter::init()
{
    m_dPtr->m_options = make_shared<Options>();
}