#include "sandbox_manager.h"

#include <robot/library/oxygen/base/builtin_fs/builtin_fs.h>
#include <robot/library/oxygen/base/generic/retry.h>
#include <kernel/yt/logging/log.h>
#include <robot/library/oxygen/base/system/fs_utils.h>
#include <robot/library/oxygen/base/system/rsync.h>

#include <library/cpp/json/json_reader.h>

#include <library/cpp/xmlrpc/client/client.h>

#include <functional>
#include <util/stream/file.h>

using namespace NOxygen;
using namespace NSaas;
using namespace NXmlRPC;
using namespace NJson;

struct TConfigEntry {
    TString ResourceType;
    ui32 ResourceId;
};

class TConfigReaderCallbacks : public TJsonCallbacks {
public:
    TVector<TConfigEntry> GetConfigEntries() const {
        return Entries;
    }

protected:
    bool OnOpenMap() override {
        if (!IsInMainMap) {
            IsInMainMap = true;
        } else if(!IsParsingEntry) {
            IsParsingEntry = true;
        }
        return true;
    }

    bool OnCloseMap() override {
        if (IsParsingEntry) {
            IsParsingEntry = false;
        } else if (IsInMainMap) {
            IsInMainMap = false;
        }
        return true;
    }

    bool OnInteger(long long id) override {
        Entries.back().ResourceId = id;
        return true;
    }
    bool OnUInteger(unsigned long long id) override {
        Entries.back().ResourceId = id;
        return true;
    }
    bool OnMapKey(const TStringBuf& resType) override {
        if (IsParsingEntry) {
            return true;
        } else {
            Entries.push_back(TConfigEntry());
            Entries.back().ResourceType = TString{resType};
        }
        return true;
    }

private:
    bool IsInMainMap = false;
    bool IsParsingEntry = false;
    TVector<TConfigEntry> Entries;
};

TSandboxManager::TSandboxManager(const TFsPath& workingDir)
    : WorkingDir(workingDir)
    , UseCustomJsonPath(false)
    , Client(new NXmlRPC::TEndPoint("https://sandbox.yandex-team.ru/sandbox/xmlrpc"))
{
}

TSandboxManager::~TSandboxManager() {
}

TVector<TSandboxResourceInfo> TSandboxManager::GetAvailableSandboxResources() {
    if (UseCustomJsonPath) {
        return GetTorrentsFromJsonConfig(CustomJsonPath);
    } else {
        return GetTorrentsFromSandboxJsonConfig();
    }
}

TVector<TSandboxResourceInfo> TSandboxManager::GetTorrentsFromSandboxJsonConfig() {
    TStruct params;
    params["resource_type"] = "RESOURCES_CONF";
    params["limit"] = 10;
    params["include_broken"] = false;

    std::function<TValue()> f = [&]() { return Client->AsyncCall("list_releases", params)->Wait(); };
    const TValue result = *NOxygen::DoWithRetry<TValue>("SandboxListReleasesResourcesConf", f, TRetryOptions::Default(), /*throwLast*/ true);

    for (size_t i = 0; i < result.Size(); ++i) {
        const TStruct& resource = result[i].Struct();
        if (!resource.contains("resources")) {
            continue;
        }

        const TArray& resList = resource.Find("resources").Array();
        for (size_t j = 0; j < resList.size(); ++j) {
            const TStruct& subResource = resList[j].Struct();
            if (subResource.contains("rsync")) {
                const TString rsyncPath = Cast<TString>(subResource.Find("rsync"));
                const TString targetPath = WorkingDir / "resources_conf.json";
                try {
                    DoRsync(rsyncPath, targetPath);
                } catch (const yexception& e) {
                    L_INFO << "Failed to download resources_conf.json with rsync (rsyncPath=" << rsyncPath << ", targetPath=" << targetPath << "): " << e.what();
                    continue;
                }
                return GetTorrentsFromJsonConfig(targetPath);
            }
        }
    }

    return TVector<TSandboxResourceInfo>();
}

TVector<TSandboxResourceInfo> TSandboxManager::GetTorrentsFromJsonConfig(const TFsPath& configPath) {
    THolder<IInputStream> input = NBuiltin::CreateInput(configPath);
    TConfigReaderCallbacks callbacks;
    ReadJson(input.Get(), &callbacks);
    TVector<TConfigEntry> entries = callbacks.GetConfigEntries();
    TVector<TSandboxResourceInfo> results;
    for (const auto& entry : entries) {
        TSandboxResourceInfo info = GetInfoFromResourceId(entry);
        L_INFO << "Got information for sandbox resource: " << info.RbTorrent << " " << info.RsyncPath << " " << info.ResourceType << Endl;
        if (info.RbTorrent.size() || info.RsyncPath.size()) {
            results.push_back(info);
        }
    }
    return results;
}

TSandboxResourceInfo TSandboxManager::GetInfoFromResourceId(const TConfigEntry& entry) {
    const TValue result = Client->AsyncCall("get_resource", entry.ResourceId)->Wait();
    const TStruct& resultMap = result.Struct();
    TSandboxResourceInfo info;
    info.ResourceType = entry.ResourceType;
    if (resultMap.contains("skynet_id")) {
        info.RbTorrent = Cast<TString>(resultMap.Find("skynet_id"));
    }
    if (resultMap.contains("rsync")) {
        info.RsyncPath = Cast<TString>(resultMap.Find("rsync"));
    }
    if (resultMap.contains("file_name")) {
        info.FileName = Cast<TString>(resultMap.Find("file_name"));
    }
    return info;
}
