#include "nanny_request.h"
#include <saas/deploy_manager/config/config.h>
#include <saas/util/cluster/datacenter.h>
#include <saas/util/external/dc.h>
#include <library/cpp/logger/global/global.h>
#include <library/cpp/json/json_reader.h>
#include <util/stream/file.h>
#include <util/string/subst.h>
#include <util/system/file.h>


IRequest::IRequest(const TDeployManagerConfig::TNannyConfig& nannyConfig)
    : NannyConfig(nannyConfig)
{
}

TGetSlotsRequest::TGetSlotsRequest(
    const TDeployManagerConfig::TNannyConfig& nannyConfig,
    const TString& tag,
    bool useContainerNames
)
    : IRequest(nannyConfig)
    , Request()
    , SlotsResult()
    , Tag(tag)
    , UseContainerNames(useContainerNames)
{
    Request = nannyConfig.RequestSlotsByTagTemplate;
    SubstGlobal(Request, "%tag_name%", tag);
}

const TString TGetSlotsRequest::SASMarker = "a_geo_sas";
const TString TGetSlotsRequest::MSKMarker = "a_geo_msk";
const TString TGetSlotsRequest::MANMarker = "a_geo_man";
const TString TGetSlotsRequest::VLAMarker = "a_geo_vla";

bool TGetSlotsRequest::Execute(NUtil::THttpReply& reply) const {
    reply = NUtil::THttpRequest(Request).SetAttemptionsMax(1).SetTimeout(10000).SetIsHttps().Execute(NannyConfig.Host, NannyConfig.Port);
    return true;
}

bool TGetSlotsRequest::ParseText(const TString& replyText) {
    NJson::TJsonValue replyJson;
    if (!NJson::ReadJsonFastTree(replyText, &replyJson) || !replyJson.Has("result") || !replyJson["result"].IsArray()) {
        ERROR_LOG << "Can't parse json reply from nanny: " << replyText << Endl;
        return false;
    }
    SlotsResult.clear();
    const NJson::TJsonValue::TArray& arr = replyJson["result"].GetArraySafe();
    for (auto&& i : arr) {
        const NJson::TJsonValue& hostName = i["hostname"];
        const NJson::TJsonValue& contHostName = i["container_hostname"];
        const NJson::TJsonValue& port = i["port"];
        const NJson::TJsonValue& tags = i["itags"];
        if (!hostName.IsString() || !contHostName.IsString() || !port.IsUInteger() || !tags.IsArray()) {
            ERROR_LOG << "Can't parse json reply from nanny: " << i.GetStringRobust() << Endl;
            return false;
        }
        const NJson::TJsonValue::TArray& arrTags = tags.GetArraySafe();
        NRTYCluster::TSlotData sd(
            UseContainerNames ? contHostName.GetString() : hostName.GetString(),
            port.GetInteger());
        sd.SetRealHost(hostName.GetString());
        bool dcDefined = false;
        for (auto&& tag : arrTags) {
            if (!tag.IsString()) {
                ERROR_LOG << "Can't parse json reply from nanny: " << i.GetStringRobust() << Endl;
                return false;
            }
            if (tag.GetString() == SASMarker) {
                dcDefined = true;
                sd.SetDC(NRTYCluster::TDatacenter::SASMarker);
            } else if (tag.GetString() == MSKMarker) {
                dcDefined = true;
                sd.SetDC(NRTYCluster::TDatacenter::MSKMarker);
            } else if (tag.GetString() == MANMarker) {
                dcDefined = true;
                sd.SetDC(NRTYCluster::TDatacenter::MANMarker);
            } else if (tag.GetString() == VLAMarker) {
                dcDefined = true;
                sd.SetDC(NRTYCluster::TDatacenter::VLAMarker);
            }
        }
        if (!UseContainerNames && hostName.GetString() != contHostName.GetString()) {
            TDatacenterUtil::Instance().SetRealHost(contHostName.GetString(), hostName.GetString());
            if (dcDefined) {
                TDatacenterUtil::Instance().SetDatacenter(contHostName.GetString(), sd.GetDC());
            }
        }

        if (dcDefined) {
            SlotsResult.push_back(sd);
        } else {
            ERROR_LOG << "Can't define DC from nanny host: " << i.GetStringRobust() << Endl;
        }
    }
    return true;
}

bool TGetSlotsRequest::Parse(const NUtil::THttpReply& reply) {
    RawText = reply.Content();
    return ParseText(RawText);
}

bool TGetSlotsRequest::DoPutToCache() {
    TString key = GetKey();
    if (!RawText) {
        ERROR_LOG << "incorrect cache usage: no nanny reply for " << key << Endl;
        return false;
    }
    if (!NannyConfig.CacheDir) {
        WARNING_LOG << "no nanny cache dir in config" << Endl;
        return false;
    }
    TFsPath dirPath(NannyConfig.CacheDir);
    if (!dirPath.Exists()) {
        dirPath.MkDirs();
    }
    TFsPath path = dirPath / key;
    TFile file(path, CreateAlways | WrOnly);
    file.Write(RawText.data(), RawText.size());
    return true;
}

bool TGetSlotsRequest::DoGetFromCache() {
    TString key = GetKey();
    if (!NannyConfig.CacheDir) {
        WARNING_LOG << "no nanny cache dir in config" << Endl;
        return false;
    }
    TFsPath dirPath(NannyConfig.CacheDir);
    TFsPath path = dirPath / key;
    if (!path.Exists()) {
        return false;
    }
    TString text = TIFStream(path).ReadAll();
    return ParseText(text);
}

TString TGetSlotsRequest::GetKey() const {
    return "slots_" + Tag;
}

bool TGetSlotsRequest::PutToCache() {
    try {
        bool result = DoPutToCache();
        return result;
    } catch(...) {
        ERROR_LOG << "Error with nanny cache put: " << CurrentExceptionMessage() << Endl;
        return false;
    }
}

bool TGetSlotsRequest::GetFromCache() {
    try {
        bool result = DoGetFromCache();
        return result;
    } catch(...) {
        ERROR_LOG << "Error with nanny cache get: " << CurrentExceptionMessage() << Endl;
        return false;
    }
}

TVector<NRTYCluster::TSlotData> TGetSlotsRequest::GetResult() const {
    return SlotsResult;
}

TGetRuntimeAttrsRequest::TGetRuntimeAttrsRequest(
    const TDeployManagerConfig::TNannyConfig& nannyConfig,
    const TString& tag
)
    : IRequest(nannyConfig)
    , Tag(tag)
{
    Request = nannyConfig.RequestRuntimeAttrsByTagTemplate;
    SubstGlobal(Request, "%tag_name%", tag);
}

bool TGetRuntimeAttrsRequest::Execute(NUtil::THttpReply& reply) const {
    reply = (
        NUtil::THttpRequest(Request)
        .SetAttemptionsMax(1)
        .SetTimeout(10000)
        .SetIsHttps()
        .SetHeader(TString("Authorization"), TString("OAuth " + NannyConfig.Token))
        .Execute(NannyConfig.Host, NannyConfig.Port)
    );

    return true;
}

bool TGetRuntimeAttrsRequest::Parse(const NUtil::THttpReply& reply) {
    NJson::TJsonValue replyJson;

    if (
        !NJson::ReadJsonFastTree(reply.Content(), &replyJson)
        || !replyJson.Has("content")
        || !replyJson["content"].Has("resources")
        || !replyJson["content"]["resources"].Has("sandbox_files")
        || !replyJson["content"]["resources"].Has("url_files")
    ) {
        ERROR_LOG <<  "Can't parse json reply from nanny: " << reply.Content() << Endl;
        return false;
    }
    Result["files"] = NJson::JSON_MAP;
    for (const auto& filesList : replyJson["content"]["resources"].GetMap()) {
        for (const auto& file : filesList.second.GetArray()) {
            if (file.Has("local_path"))
                Result["files"].InsertValue(file["local_path"].GetString(), file).InsertValue("file_type", filesList.first);
        }
    }
    Result["groups"] = NJson::JSON_MAP;
    for (const auto& group : replyJson["content"]["instances"]["extended_gencfg_groups"]["groups"].GetArray()) {
        TString release = group["release"].GetString();
        if (release.StartsWith("tags/"))
            release = release.substr(5);
        Result["groups"][group["name"].GetString()].AppendValue(release);
    }
    return true;
}

const NJson::TJsonValue& TGetRuntimeAttrsRequest::GetResult() const {
    return Result;
}

