#include "configs_script.h"
#include "config_tab_worker.h"
#include "projects_utils.h"
#include "relev_utils.h"
#include "users_utils.h"
#include <saas/deploy_manager/modules/resources/resources.h>
#include <saas/deploy_manager/scripts/interface/interface.pb.h>
#include <saas/deploy_manager/scripts/cluster/cluster_task.h>

#include <kernel/web_factors_info/factor_names.h>
#include <util/string/hex.h>

namespace NRTYDeploy {
    namespace {
        void FillConfigSnapshot(NDMInterface::TConfigSnapshot& config, IVersionedStorage& storage, const TString& project, const TString& version) {
            NDMInterface::TProjectConfig prjCfg;
            if (version == "NEW")
                CreateProjectConfig(prjCfg, storage, project);
            else {
                i64 ver = -1;
                if (version != "LAST")
                    ver = FromString<int>(version);
                storage.GetValue("/projects/" + project + "/configs", prjCfg, ver);
            }
            for (const auto& component : prjCfg.GetComponents()) {
                if (component.GetComponent().GetServiceType() == RTYSERVER_SERVICE) {
                    TConfigTabWorker::FillTab(NDMInterface::TConfigTab::RTYSERVER_MAIN, *config.AddTabs(), storage, &component, prjCfg);
                    TConfigTabWorker::FillTab(NDMInterface::TConfigTab::RTYSERVER_HTML_PARSER, *config.AddTabs(), storage, &component, prjCfg);
                    TConfigTabWorker::FillTab(NDMInterface::TConfigTab::RTYSERVER_XML_PARSER, *config.AddTabs(), storage, &component, prjCfg);
                    TConfigTabWorker::FillTab(NDMInterface::TConfigTab::RTYSERVER_QUERY_LANGUAGE, *config.AddTabs(), storage, &component, prjCfg);
                    TConfigTabWorker::FillTab(NDMInterface::TConfigTab::RTYSERVER_RELEV, *config.AddTabs(), storage, &component, prjCfg);
                }
            }
        }

        NDMInterface::TCurrentConfigVersion GetCurrentConfigVersion(IVersionedStorage& storage, const TString& project, const TString& ctype) {
            NDMInterface::TCurrentConfigVersion result;
            TString path = "/projects/" + project + "/" + ctype + "/current_version";
            try {
                storage.GetValue(path, result);
                if (result.HasChangeProgress() && (Now() - TInstant::MicroSeconds(result.GetChangeProgress().GetStart())) >= TDuration::Minutes(1)) {
                    result.SetVersion(result.GetChangeProgress().GetTo());
                    result.ClearChangeProgress();
                }
                storage.SetValue(path, result);
            } catch (...) {
                result.Clear();
                TString data;
                if (storage.GetValue(path, data)) {
                    ui32 ver;
                    if (TryFromString(data, ver)) {
                        result.SetVersion(ver);
                        storage.SetValue(path, result);
                    }
                }
            }
            return result;
        }

        NDMInterface::TCurrentConfigVersion SetCurrentConfigVersion(IVersionedStorage& storage, const TString& project, const TString& ctype, ui32 version) {
            NDMInterface::TCurrentConfigVersion result = GetCurrentConfigVersion(storage, project, ctype);
            if (result.HasChangeProgress())
                ythrow TCodedException(400) << "Config changing already in process";
            if (result.HasVersion() && result.GetVersion() == version)
                return result;
            if (result.HasVersion())
                result.MutableChangeProgress()->SetFrom(result.GetVersion());
            result.MutableChangeProgress()->SetTo(version);
            result.MutableChangeProgress()->SetStart(Now().GetValue());
            result.ClearVersion();
            TString path = "/projects/" + project + "/" + ctype + "/current_version";
            storage.SetValue(path, result);
            return result;
        }

        void ReportCurrentConfigVersions(IDeployInfoRequest& request, const TString& project, NJson::TJsonValue& result) {
            const TVector<TString>& ctypes = request.GetResourcesManager().GetCTypes();
            for (const auto& ctype : ctypes) {
                NDMInterface::TCurrentConfigVersion ver = GetCurrentConfigVersion(request.GetStorage(), project, ctype);
                if (!ver.HasVersion() && !ver.HasChangeProgress())
                    continue;
                NJson::TJsonValue& ct = result.AppendValue(NJson::JSON_MAP);
                Proto2Json(ver, ct, true);
                ct.InsertValue("ctype", ctype);
            }
        }
    }

    void TScriptConfigs::DoProcess(IDeployInfoRequest& request) {
        const TString& action = request.GetRD().CgiParam.Get("action");
        const TCgiParameters& cgi = request.GetRD().CgiParam;
        const TString& projectName = cgi.Get("project");
        if (action == "get_config_versions") {
            if (GetUserRoleInProject(request.GetStorage(), User.Login, projectName) < NDMInterface::TUser::WATCHER)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to watch configs of project " << projectName;
            ui32 p = 0;
            if (cgi.Has("p"))
                p = FromString<ui32>(cgi.Get("p"));
            ui32 numdoc = 10;
            if (cgi.Has("numdoc"))
                numdoc = FromString<ui32>(cgi.Get("numdoc"));
            TString path = "/projects/" + projectName + "/configs";
            i64 ver = -1;
            request.GetStorage().GetVersion(path, ver);
            NJson::TJsonValue& versions = Report.InsertValue("results", NJson::JSON_ARRAY);
            Report.InsertValue("page", p);
            Report.InsertValue("per_page", numdoc);
            ui32 versionsCount = 0;
            if (numdoc > 0) {
                ui32 b = p * numdoc;
                ui32 e = (p + 1) * numdoc;
                for (int i = ver; i >= 0; --i) {
                    NDMInterface::TProjectConfig prjCfg;
                    try {
                        request.GetStorage().GetValue(path, prjCfg, i, false);
                    } catch (...) {
                        continue;
                    }
                    if (versionsCount >= b && versionsCount < e) {
                        NJson::TJsonValue& res = versions.AppendValue(NJson::JSON_MAP);
                        res.InsertValue("id", i);
                        res.InsertValue("version", i);
                        res.InsertValue("timestamp", prjCfg.GetTimestamp());
                    }
                    ++versionsCount;
                }
            }
            Report.InsertValue("versions_count", versionsCount);
            ReportCurrentConfigVersions(request, projectName, Report["current_configs"]);
        } else if (action == "save_config_version") {
            if (GetUserRoleInProject(request.GetStorage(), User.Login, projectName) < NDMInterface::TUser::ADMINISTRATOR)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to change configs of project " << projectName;
            SaveConfigVersion(projectName, request.GetStorage(), &Report);
        } else if (action == "get_configs") {
            if (GetUserRoleInProject(request.GetStorage(), User.Login, projectName) < NDMInterface::TUser::WATCHER)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to watch configs of project " << projectName;
            NDMInterface::TConfigSnapshot configs;
            TString version = "NEW";
            if (cgi.Has("version"))
                version = cgi.Get("version");
            FillConfigSnapshot(configs, request.GetStorage(), projectName, version);
            for (const auto& tab : configs.GetTabs()) {
                NJson::TJsonValue& tabJson = Report.AppendValue(NJson::JSON_MAP);
                Proto2Json(tab, tabJson, true);
            }
        } else if (action == "set_configs") {
            if (GetUserRoleInProject(request.GetStorage(), User.Login, projectName) < NDMInterface::TUser::ADMINISTRATOR)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to change configs of project " << projectName;
            TMemoryInput mi(request.GetBlob().AsCharPtr(), request.GetBlob().Size());
            if (!NJson::ReadJsonTree(&mi, &Report))
                ythrow yexception() << "errors in json";
            if (!Report.IsArray())
                ythrow yexception() << "json must be array";
            for (const auto& tab : Report.GetArray()) {
                NDMInterface::TConfigTab cfg;
                Json2Proto(tab, cfg);
                TConfigTabWorker::SetChangedConfig(cfg, request.GetStorage());
            }
            SaveConfigVersion(projectName, request.GetStorage(), nullptr);
        } else if (action == "set_current_config_version") {
            if (GetUserRoleInProject(request.GetStorage(), User.Login, projectName) < NDMInterface::TUser::ADMINISTRATOR)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to change configs of project " << projectName;
            const TString& ctype = cgi.Get("ctype");
            i64 version = -1;
            if (!request.GetStorage().GetVersion("/projects/" + projectName + "/configs", version))
                ythrow yexception() << "there is no configs for " << projectName;
            if (cgi.Has("version")) {
                const TString& ver = cgi.Get("version");
                if (!!ver) {
                    if (FromString<int>(ver) > version)
                        ythrow yexception() << "there is no version " << ver;
                    version = FromString<int>(ver);
                }
            }
            SetCurrentConfigVersion(request.GetStorage(), projectName, ctype, version);
            ReportCurrentConfigVersions(request, projectName, Report);
        } else if (action == "get_current_config_version") {
            if (GetUserRoleInProject(request.GetStorage(), User.Login, projectName) < NDMInterface::TUser::WATCHER)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to view config version of project " << projectName;
            ReportCurrentConfigVersions(request, projectName, Report);
        } else if (action == "upload_matrixnet") {
            if (GetUserRoleInProject(request.GetStorage(), User.Login, projectName) < NDMInterface::TUser::ADMINISTRATOR)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to change configs of project " << projectName;
            const TString& name = cgi.Get("name");
            if (!name)
                ythrow yexception() << "name of matrixnet not set";
            NDMInterface::TProject proj = GetProject(request.GetStorage(), projectName);
            bool success = false;
            for (const auto& comp : proj.GetComponents()) {
                if (comp.GetServiceType() == RTYSERVER_SERVICE) {
                    TString path = GetConfigDir(comp) + "/" + name;
                    TString data(request.GetBlob().AsCharPtr(), request.GetBlob().Length());
                    if (cgi.Has("hex") && FromString<bool>(cgi.Get("hex")))
                        data = HexDecode(data);
                    if (!request.GetStorage().SetValue(path, data))
                        ythrow yexception() << "cannot write " << path;
                    NDMInterface::TRelevConfig::TMatrixNet mn;
                    FillMatrixNetMetadata(mn, request.GetStorage(), path, -1);
                    Proto2Json(mn, Report);
                    success = true;
                    break;
                }
            }
            if (!success)
                ythrow yexception() << "there is no search component for project " << projectName;
        } else if (action == "list_matrixnet") {
            if (GetUserRoleInProject(request.GetStorage(), User.Login, projectName) < NDMInterface::TUser::WATCHER)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to watch configs of project " << projectName;
            NDMInterface::TProject proj = GetProject(request.GetStorage(), projectName);
            TVector <NDMInterface::TRelevConfig::TMatrixNet> res;
            Report.SetType(NJson::JSON_ARRAY);
            ListMatrixNetMetadata(res, request.GetStorage(), "/common");
            for (const auto& comp : proj.GetComponents()) {
                if (comp.GetServiceType() == RTYSERVER_SERVICE) {
                    ListMatrixNetMetadata(res, request.GetStorage(), GetConfigDir(comp));
                    break;
                }
            }
            for (const auto& mn : res)
                Proto2Json(mn, Report.AppendValue(NJson::JSON_MAP));
        } else if (action == "decode_polynom") {
            TString data(request.GetBlob().AsCharPtr(), request.GetBlob().Length());
            NDMInterface::TRelevConfig::TPolynom poly;
            DecodePolynom(poly, data);
            Proto2Json(poly, Report);
        } else if (action == "encode_polynom") {
            NJson::TJsonValue json;
            TMemoryInput mi(request.GetBlob().AsCharPtr(), request.GetBlob().Size());
            if (!NJson::ReadJsonTree(&mi, &json))
                ythrow yexception() << "errors in json";
            NDMInterface::TRelevConfig::TPolynom poly;
            Json2Proto(json, poly);
            Report.InsertValue("result", EncodePolynom(poly));
        } else if (action == "polynom_to_text") {
            const TString& polynom = cgi.Get("polynom");
            TMap<ui32, TString> indexToName;
            Report = DecodePolynom(polynom, indexToName);
        } else if (action == "text_to_polynom") {
            const TString& polynom = cgi.Get("polynom");
            TMap<TString, ui32> nameToIndex;
            Report = EncodePolynom(polynom, nameToIndex);
        }
        else if (action == "get_dynamic_factors_list") {
            const IFactorsInfo& fi = *GetWebFactorsInfo();
            Report.SetType(NJson::JSON_ARRAY);
            for (ui32 i = 0; i < fi.GetFactorCount(); ++i) {
                if (!fi.IsQueryFactor(i) && !fi.IsTextFactor(i))
                    continue;
                if (fi.IsUnusedFactor(i) || fi.IsDeprecatedFactor(i) || fi.IsRemovedFactor(i) || fi.IsUnimplementedFactor(i))
                    continue;
                TVector<TString> desc;
                if (fi.IsTextFactor(i))
                    desc.push_back("text");
                if (fi.IsQueryFactor(i))
                    desc.push_back("query");
                if (fi.IsBinary(i))
                    desc.push_back("binary");
                NJson::TJsonValue& f = Report.AppendValue(NJson::JSON_MAP);
                f.InsertValue("name", fi.GetFactorName(i));
                f.InsertValue("description", JoinStrings(desc, ","));
            }
            static const char* rtyFactors[] = {"LiveTime", "InvLiveTime", "FreshnessDay", "FreshnessWeek", "FreshnessMonth"};
            for (const auto& name : rtyFactors) {
                NJson::TJsonValue& f = Report.AppendValue(NJson::JSON_MAP);
                f.InsertValue("name", name);
                f.InsertValue("description", "rty special factors");
            }
        } else {
            ythrow yexception() << "unknown action " << action;
        }
    }

    TScriptConfigs::TFactory::TRegistrator<TScriptConfigs> TScriptConfigs::Registrator("configs");

}
