#include "KMInstance.h"

#include "common/KMDirFd.h"
#include "common/KMFileGuard.h"
#include "common/KMFolder.h"
#include "common/KMLogger.h"
#include "json.hpp"

using namespace nlohmann;

class KMInstance::Private
{
public:
    int pid;
    int childpid;
    string id;
    string dir;
    string info;
    string app;
    string arch;
    string branch;
    string commit;
    string runtime;
    string runtimeCommit;
};

KMInstance::KMInstance(const string &appId)
    : m_appId(appId)
    , m_appLockFd(-1)
    , m_instanceLockFd(-1)
    , d(make_shared<Private>())
{
    init();
    initPrivate();
}

void KMInstance::init()
{
    m_baseDir = KMFolder::getInstancePath();
    m_appDir = m_baseDir / m_appId;
    m_appLockFile = m_appDir / ".ref";
    m_appXdgRuntimeDir = m_appDir / "xdg-run";
    m_appSharedTmp = m_appDir / "tmp";
}

/**
 * initPrivate:
 * @brief : 初始化私有成员
 */
void KMInstance::initPrivate()
{
    d->dir = m_appDir.string();
    d->id = m_appId;
    d->info = fs::path(m_appDir / "info").string();
    d->pid = getPidFromFile();
    d->childpid = getChildPidFromFile();
    (void)getInfoFromFile();
}

/**
 * @brief : 获取所有实例
 * @return: vector<KMInstance> 返回
 */
vector<shared_ptr<KMInstance>> KMInstance::getAllInstances()
{
    fs::path dir = KMFolder::getInstancePath();
    if (!fs::exists(dir))
    {
        return vector<shared_ptr<KMInstance>>();
    }

    vector<shared_ptr<KMInstance>> instances;
    for (auto &dir : fs::directory_iterator(dir))
    {
        if (!dir.is_directory())
        {
            continue;
        }

        string filename = dir.path().filename().string();
        bool isDigit = true;
        for (char const &c : filename)
        {
            if (std::isdigit(c) == 0)
            {
                isDigit = false;
                break;
            }
        }

        if (!isDigit)
        {
            continue;
        }

        if (isValidIntance(filename))
        {
            instances.push_back(make_shared<KMInstance>(filename));
        }
    }

    return instances;
}

/**
 * @brief : 判断实例是否有效并删除无效id
 * @param: instance id
 * @return: true 有效 false 无效
 */
bool KMInstance::isValidIntance(const string &id)
{
    bool isValid = true;
    struct stat statbuf;
    struct flock l = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0 };
    string idDir = KMFolder::getInstancePath() / id;
    string ref = idDir + "/.ref";
    int lockfd = open(ref.c_str(), O_RDWR | O_CLOEXEC);

    do
    {
        if (lockfd < 0)
        {
            break;
        }

        if (fstat(lockfd, &statbuf) != 0)
        {
            break;
        }

        /* Only gc if created at least 3 secs ago */
        if (statbuf.st_mtime + 3 > time(nullptr))
        {
            break;
        }

        if (fcntl(lockfd, F_GETLK, &l) != 0)
        {
            break;
        }

        if (l.l_type != F_UNLCK)
        {
            break;
        }

        isValid = false;
        std::error_code error;
        string info = idDir + "/info";
        string appname;
        g_autoptr(GKeyFile) pKeyFile = g_key_file_new();
        if (fs::exists(fs::path(info)) && g_key_file_load_from_file(pKeyFile, info.c_str(), G_KEY_FILE_NONE, nullptr))
        {
            if (g_key_file_has_group(pKeyFile, METADATA_GROUP_APPLICATION))
            {
                appname = g_key_file_get_string(pKeyFile, METADATA_GROUP_APPLICATION, METADATA_KEY_NAME, nullptr);
            }
            else if (g_key_file_has_group(pKeyFile, METADATA_GROUP_RUNTIME))
            {
                appname = g_key_file_get_string(pKeyFile, METADATA_GROUP_RUNTIME, METADATA_KEY_RUNTIME, nullptr);
            }
        }

        fs::remove_all(fs::path(idDir), error);

        if (appname.empty())
        {
            break;
        }

        string appDir = KMFolder::getInstancePath() / appname;
        if (!fs::exists(appDir))
        {
            break;
        }

        struct flock lock = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0 };
        string appref = appDir + "/.ref";
        int fd = open(appref.c_str(), O_RDWR | O_CLOEXEC);
        if (fd != -1 && fcntl(fd, F_SETLK, &lock) == 0)
        {
            for (auto &it : fs::directory_iterator(fs::path("/dev/shm")))
            {
                if (it.path().filename().string().find("/dev/shm/kaiming-" + appname) != string::npos)
                {
                    fs::remove_all(it.path(), error);
                    break;
                }
            }
            fs::remove_all(appDir, error);
        }
        close(fd);

    } while (0);

    close(lockfd);
    return isValid;
}

/**
 * getPidFromFile:
 * @brief : 获取子进程pid
 * @return: int 返回
 */
int KMInstance::getChildPidFromFile()
{
    fs::path file = m_appDir / "boxinfo.json";
    if (!fs::exists(file))
    {
        return 0;
    }

    string content;
    if (!KMUtil::readFile(file, content))
    {
        return 0;
    }

    json j;
    try
    {
        j = json::parse(content);
    }
    catch (...)
    {
        KMError("Parse policy data failed ");
        return 0;
    }

    return j.contains("child-pid") ? j["child-pid"].get<int>() : 0;
}

/**
 * getPidFromFile:
 * @brief : 获取进程pid
 * @return: int 返回
 */
int KMInstance::getPidFromFile()
{
    fs::path file = m_appDir / "pid";
    if (!fs::exists(file))
    {
        return 0;
    }

    string content;
    if (!KMUtil::readFile(file, content))
    {
        return 0;
    }

    return (int)g_ascii_strtoll(content.c_str(), nullptr, 10);
}

/**
 * getInfoFromFile:
 * @brief : 获取info信息
 * @return: true 成功 false 失败
 */
bool KMInstance::getInfoFromFile()
{
    if (!fs::exists(d->info))
    {
        return false;
    }

    g_autoptr(GKeyFile) pKeyFile = g_key_file_new();
    if (!g_key_file_load_from_file(pKeyFile, d->info.c_str(), G_KEY_FILE_NONE, nullptr))
    {
        KMError("Failed to load instance info file " + d->info);
        return false;
    }

    GKeyFile *info = KMUtil::stealPointer(&pKeyFile);
    if (info == nullptr)
    {
        return false;
    }

    if (g_key_file_has_group(info, METADATA_GROUP_APPLICATION))
    {
        d->app = g_key_file_get_string(info, METADATA_GROUP_APPLICATION, METADATA_KEY_NAME, nullptr);
        d->runtime = g_key_file_get_string(info, METADATA_GROUP_APPLICATION, METADATA_KEY_RUNTIME, nullptr);
    }
    else if (g_key_file_has_group(info, METADATA_GROUP_RUNTIME))
    {
        d->runtime = g_key_file_get_string(info, METADATA_GROUP_RUNTIME, METADATA_KEY_RUNTIME, nullptr);
    }

    if (g_key_file_has_group(info, METADATA_GROUP_INSTANCE))
    {
        d->arch = g_key_file_get_string(info, METADATA_GROUP_INSTANCE, METADATA_KEY_ARCH, nullptr);
        d->branch = g_key_file_get_string(info, METADATA_GROUP_INSTANCE, METADATA_KEY_BRANCH, nullptr);
        d->commit = g_key_file_get_string(info, METADATA_GROUP_INSTANCE, METADATA_KEY_APP_COMMIT, nullptr);
        d->runtimeCommit = g_key_file_get_string(info, METADATA_GROUP_INSTANCE, METADATA_KEY_RUNTIME_COMMIT, nullptr);
    }

    return true;
}

/**
 * @brief : 获取进程pid
 */
int KMInstance::getPid()
{
    return d->pid;
}

/**
 * @brief : 获取子进程pid
 */
int KMInstance::getChildPid()
{
    return d->childpid;
}

/**
 * @brief : 获取instance id
 */
string KMInstance::getInstanceId()
{
    return d->id;
}

/**
 * @brief : 获取app名称
 */
string KMInstance::getApp()
{
    return d->app;
}

/**
 * @brief : 获取架构
 */
string KMInstance::getArch()
{
    return d->arch;
}

/**
 * @brief : 获取分支
 */
string KMInstance::getBranch()
{
    return d->branch;
}

/**
 * @brief : 获取commit
 */
string KMInstance::getCommit()
{
    return d->commit;
}

/**
 * @brief : 获取runtime
 */
string KMInstance::getRuntime()
{
    return d->runtime;
}

void KMInstance::clearUnusedInstances()
{
    KMDirFdIterator iter;
    if (!iter.initAt(AT_FDCWD, m_baseDir.string().c_str(), false))
    {
        return;
    }

    struct dirent *dent;
    while (true)
    {
        if (!iter.nextDentEnsureDtype(&dent))
        {
            break;
        }

        if (dent == NULL)
        {
            break;
        }

        if (!KMUtil::isInteger(dent->d_name))
        {
            continue;
        }

        if (dent->d_type == DT_DIR)
        {
            struct stat statbuf;
            struct flock l = { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0 };
            std::string ref = std::string(dent->d_name) + "/.ref";
            KMFileGuard fg(::openat(iter.fd(), ref.c_str(), O_RDWR | O_CLOEXEC));
            if (fg.fd() > 0 && ::fstat(fg.fd(), &statbuf) == 0 && statbuf.st_mtime + 3 < ::time(NULL) && ::fcntl(fg.fd(), F_GETLK, &l) == 0 && l.l_type == F_UNLCK)
            {
                // 此实例已经不再使用，移除
                KMDebug(std::string("Cleaning up unused container id ") + dent->d_name);

                std::string perAppDir = KMUtil::buildFilename(m_baseDir.string(), dent->d_name);
                KMFileGuard perAppDirFg(::openat(AT_FDCWD, perAppDir.c_str(), O_PATH | O_DIRECTORY | O_CLOEXEC));
                if (perAppDirFg.fd() < 0)
                {
                    KMWarn("Failed to open " + perAppDir);
                    return;
                }

                KMFileGuard perAppDirLockFg(::openat(perAppDirFg.fd(), ".ref", O_RDWR | O_CREAT | O_CLOEXEC, 0600));
                if (perAppDirLockFg.fd() < 0)
                {
                    KMWarn("Failed to open " + perAppDir + "/.ref");
                    return;
                }

                struct flock exclusiveLock = {
                    .l_type = F_WRLCK,
                    .l_whence = SEEK_SET,
                    .l_start = 0,
                    .l_len = 0,
                };
                if (::fcntl(perAppDirLockFg.fd(), F_SETLK, &exclusiveLock) < 0)
                {
                    KMWarn("Failed to lock " + perAppDir + "/.ref");
                    return;
                }

                struct stat sbuf;
                if (::fstat(perAppDirLockFg.fd(), &sbuf) < 0)
                {
                    KMWarn("Failed to fstat " + perAppDir + "/.ref");
                    return;
                }

                if (sbuf.st_mtime + 3 >= time(NULL))
                {
                    KMWarn("lock file too recent, avoiding race condition");
                    return;
                }

                KMDebug(std::string("Cleaning up per-app-ID state for ") + dent->d_name);

                /*
                    /dev/shm被卸载到主机的/dev/shm上，以获得一致的可用空间行为，并确保它实际上在RAM中。它可能包含相对较大的文件，所以对其进行清理。
                    原则上，这可以用于其他目录，如/tmp，在一个路径数组上的循环中（因此进行了缩进），但现在只对/dev/shm这样做。
                */
                {
                    // TODO: /dev/shm中被tmpfs占用的内存空间会随着进程退出自动释放，暂不卸载到主机/dev/shm上
                }

                /*
                    目前将应用程序的/tmp直接分配到主机XDG_RUNTIME_DIR上的每个应用程序目录中，而不是以类似于/dev/shm的方式将其卸载到/tmp中，
                    因此希望tmp是一个目录，而不是符号链接。如果它是符号链接，将取消它的链接。
                */
                if (!KMDirFd::rmRfAt(perAppDirFg.fd(), "tmp"))
                {
                    KMWarn("Failed to rmRfAt " + perAppDir + "/tmp");
                }

                /*
                    故意不清理.ref锁定文件或目录本身。如果这样做，锁定方案将失败，因为并发进程可能会在取消链接之前打开.ref文件。
                */
            }
        }
    }
}

bool KMInstance::ensurePerIdPath()
{
    if (!fs::exists(m_baseDir) && !fs::create_directories(m_baseDir))
    {
        return false;
    }

    for (int count = 0; count < 1000; count++)
    {
        m_instanceId = to_string(g_random_int());
        m_instancePath = m_baseDir / m_instanceId;

        // 清理无用实例
        clearUnusedInstances();

        if (fs::create_directories(m_instancePath))
        {
            fs::path lockFile = m_instancePath / ".ref";
            struct flock l = { .l_type = F_RDLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0 };

            m_instanceLockFd = open(lockFile.string().c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0644);
            if (m_instanceLockFd < 0)
            {
                kmlogger.error("open instance lock file error.");
                return false;
            }
            else if (fcntl(m_instanceLockFd, F_SETLK, &l) != 0)
            {
                kmlogger.error("lock instance file error.");
                return false;
            }
            return true;
        }
    }
    return false;
}

bool KMInstance::ensurePerAppPath()
{
    if (!fs::exists(m_appDir) && !fs::create_directories(m_appDir))
    {
        kmlogger.error("can't create per app dir %s\n", m_appDir.string().c_str());
        return false;
    }

    struct flock nonExclusiveLock = { .l_type = F_RDLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0 };
    m_appLockFd = open(m_appLockFile.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
    if (m_appLockFd < 0 || fcntl(m_appLockFd, F_SETLKW, &nonExclusiveLock) != 0)
    {
        kmlogger.error("unable lock file %s\n", m_appLockFile.c_str());
        return false;
    }

    return true;
}

bool KMInstance::ensurePerAppShareTmp()
{
    if (m_appLockFd < 0)
    {
        return false;
    }

    if (!fs::exists(m_appSharedTmp) && !fs::create_directories(m_appSharedTmp))
    {
        return false;
    }

    return true;
}

bool KMInstance::ensurePerAppXdgRuntimePath()
{
    if (fs::exists(m_appXdgRuntimeDir))
    {
        return true;
    }
    return fs::create_directories(m_appXdgRuntimeDir);
}

bool KMInstance::ensurePerIdPidFile()
{
    fs::path pidFile = m_instancePath / "pid";
    return KMUtil::syncWriteFile(pidFile.string(), to_string(getpid()));
}

bool KMInstance::ensurePerIdInfoFile()
{
    fs::path info = m_instancePath / "info";
    return KMUtil::syncWriteFile(info.string(), "");
}

bool KMInstance::ensurePerAppDevShm(const std::string &appid, int perAppDirLockFd, std::string &sharedDevShmOut, std::string &errMsg)
{
    // TODO : 暂时未用到，后续使用时再实现
    errMsg = "暂时未用到，后续使用时再实现";
    return false;
}

bool KMInstance::ensurePerAppTmp(const std::string &appid, int perAppDirLockFd, std::string &sharedTmpOut, std::string &errMsg)
{
    // TODO : 暂时未用到，后续使用时再实现；也可以使用成员函数bool ensurePerAppShareTmp();
    errMsg = "暂时未用到，后续使用时再实现；也可以使用成员函数bool ensurePerAppShareTmp();";
    return false;
}
