#include "users_utils.h"
#include "service_wizard_anketa_processor.h"
#include <saas/protos/rtyservice.pb.h>

#include <util/string/strip.h>

const TString TAnketaProcessor::SERVICE_NAME_NAME = "service_name";

TAnketaProcessor::TQuestionData::TQuestionData(const TString& page, const NDMInterface::TServiceWizardQuestion& data)
    : Page(page)
    , Data(&data)
{}

TAnketaProcessor::TQuestionData::TQuestionData()
    : Data(nullptr)
{}

void TAnketaProcessor::SetupServiceSuggest()
{
    if (!SuggestService)
        return;
    SetupServiceCommon(SuggestService);
    TString path = "/configs/" + SuggestService + "/";
    NJson::TJsonValue& rty = JsonFiles[path + "rtyserver.diff-" + SuggestService];
    rty["IndexGenerator"] = "Suggest";
    rty["Searcher.TwoStepQuery"] = "false";
    rty["ComponentsConfig.Suggest.ZonesWeightsFileInfo"] = "${WorkDir and WorkDir or _BIN_DIRECTORY}/configs/zone_weights.cfg";
    rty["ComponentsConfig.Suggest.ShardMin"] = "${SHARD_MIN or 0}";
    rty["ComponentsConfig.Suggest.ShardMax"] = "${SHARD_MAX or 65533}";
    rty["ComponentsConfig.Suggest.ClearUnusefulData"] = "1";
    rty["Indexer.Memory.Enabled"] = "0";
    rty["IsSecondaryMetaServiceComponent"] = "1";
    rty["AddSearcherAfterMergeOnly"] = "1";
    NRTYService::TSuggestZonesInfo zonesWeight;
    zonesWeight.SetDefaultWeight(Anketa["suggest_weight_default"].GetUIntegerRobust());
    for (const auto& zone : Anketa["search_zones"].GetArray()) {
        const TString& name = Strip(zone["name"].GetString());
        ui64 weight = zone["suggest_weight"].GetUIntegerRobust();
        if (!!name && weight) {
            NRTYService::TSuggestZoneInfo& z = *zonesWeight.AddZones();
            z.SetZoneName(name);
            z.SetWeight(weight);
            SearchZones.insert(name);
        }
    }
    CgiParamsByService[SuggestService]["normal_kv_report"] = "yes";
    CgiParamsByService[SuggestService]["meta_search"] = "first_found";
    AddFile(path + "zone_weights.cfg", zonesWeight);
    NJson::TJsonValue& sp = SearchProxyConfig(SuggestService);
    sp["TwoStepQuery"] = "0";
    sp["MergeOptions"] = "SkipSameDocids=true";
    SetupHtmlParser(SuggestService);
    SetupXmlParser(SuggestService);
}

void TAnketaProcessor::SetupServiceKeyValue()
{
    if (!SearchService)
        return;
    SetupServiceCommon(SearchService);
    NJson::TJsonValue& rty = JsonFiles["/configs/" + SearchService + "/rtyserver.diff-" + SearchService];
    rty["IndexGenerator"] = "FULLARC";
    rty["Searcher.TwoStepQuery"] = "false";
    rty["ComponentsConfig.FULLARC.Layers.full.ReadContextDataAccessType"] = "MEMORY_FROM_FILE";
    rty["ComponentsConfig.FULLARC.Layers.full.MinPartSizeFactor"] = "250";
    rty["ComponentsConfig.FULLARC.Layers.full.MaxPartCount"] = "10000";
}

void TAnketaProcessor::SetupServiceSearch() {
    if (!SearchService)
        return;
    SetupServiceCommon(SearchService);
    NJson::TJsonValue& rty = JsonFiles["/configs/" + SearchService + "/rtyserver.diff-" + SearchService];
    if (Anketa["use_cache"].GetBooleanRobust()) {
        rty["Searcher.QueryCache.UseCache"] = true;
        rty["Searcher.QueryCache.CacheLifeTime"] = "3600s";
        rty["Searcher.ReArrangeOptions"] = "CacheSupporter";
        rty["Searcher.ReAskBaseSearches"] = true;
    }
    if (Anketa["use_reqwizard"].GetBooleanRobust())
        SearchProxyConfig(SearchService)["RemoteWizards"] = "reqwizard.yandex.net:8891";
    const TString& msp = Strip(Anketa["misspell_policy"].GetString());
    if (!!msp && msp != "no") {
        NJson::TJsonValue& sp = SearchProxyConfig(SearchService);
        NJson::TJsonValue& mspJson = sp["Misspell"][0];
        mspJson["Host"] = "${MISSPELL_HOST}";
        mspJson["Port"] = "${MISSPELL_PORT}";
        mspJson["EnablePorno"] = false;
        AddRequestParam("msp", true);
        CgiParamsByService[SearchService]["msp"] = msp;
    }
    if (Anketa["use_instant_search"].GetBooleanRobust())
        AddRequestParam("instant", true);
    SetupQueryLanguage();
    SetupGroupAttrs();
    SetupSnippetDeniedZones();
    SetupProperties();
    SetupFacets();
    rty["Indexer.Common.StoreTextToArchive"] = Anketa["store_archive"].GetBooleanRobust();
    rty["Indexer.Common.MadeUrlAttributes"] = Anketa["create_url_attributes"].GetBooleanRobust();
    rty["Indexer.Common.TokenizeUrl"] = Anketa["tokenize_url"].GetBooleanRobust();
    SetupLanguages();
    SetupHtmlParser(SearchService);
    SetupXmlParser(SearchService);
    SetupZoneToProperies();
}

NJson::TJsonValue& TAnketaProcessor::SearchProxyConfig(const TString& service)
{
    NJson::TJsonValue& result = YConfigFiles["/configs/" + service + "/searchproxy-" + service + ".conf"]["Service"][0];
    result["Name"] = service;
    return result;
}

void TAnketaProcessor::SetupServiceCommon(const TString& service)
{
    NJson::TJsonValue& rty = JsonFiles["/configs/" + service + "/rtyserver.diff-" + service];
    rty.SetType(NJson::JSON_MAP);
    if (!Anketa["is_prefixed"].GetBooleanRobust()) {
        if (GetCheckListValues("indexing_timetable").contains("ab-switch"))
            Error(TError::ERROR, "A-B switch невозможен с непрефиксным индексом. Исправьте $indexing_timetable$ или $is_prefixed$.", true);
        rty["IsPrefixedIndex"] = "0";
    } else {
        TString kpsname = (service == SuggestService) ? "sgkps" : "kps";
        AddRequestParam(kpsname, false);
    }
}

void TAnketaProcessor::SetupQueryLanguageEntity(const TString& question, const TString& prefix, const TString& option, THashSet<TString>& setToSave) {
    TString ql = "/configs/" + SearchService + "/query-language-" + SearchService;
    for (const auto& zone : Anketa[question].GetArray()) {
        const TString& name = Strip(zone["name"].GetString());
        if (!name)
            continue;
        setToSave.insert(name);
        if (!name.StartsWith(prefix))
            YConfigFiles[ql]["QueryLanguage"][0][name] = option;
    }
}

void TAnketaProcessor::SetupQueryLanguage() {
    TString ql = "/configs/" + SearchService + "/query-language-" + SearchService;
    SetupQueryLanguageEntity("search_zones", "z_", "ZONE, doc", SearchZones);
    SetupQueryLanguageEntity("search_attrs_literal", "s_", "ATTR_LITERAL, doc", SearchLiteralAttrs);
    SetupQueryLanguageEntity("search_attrs_integer", "i_", "ATTR_INTEGER, doc", SearchIntegerAttrs);
    if (YConfigFiles.Has(ql)) {
        NJson::TJsonValue& qlJson = YConfigFiles[ql]["QueryLanguage"][0];
        qlJson["z_"] = "ZONE, doc, template";
        qlJson["s_"] = "ATTR_LITERAL, doc, template";
        qlJson["i_"] = "ATTR_INTEGER, doc, template";
        SearchProxyConfig(SearchService)["#include"].AppendValue("${WorkDir and WorkDir or _BIN_DIRECTORY}/configs/query-language-" + SearchService);
    }
}

void TAnketaProcessor::SetupProperties() {
    TVector<TString> props;
    for (const auto& prop : Anketa["properties"].GetArray()) {
        const TString& name = Strip(prop["name"].GetString());
        if (!!name)
            props.push_back(name);
    }
    if (props.size())
        JsonFiles["/configs/" + SearchService + "/rtyserver.diff-" + SearchService]["Indexer.Common.DocProperty"] = JoinStrings(props, ",");
}

void TAnketaProcessor::SetupFacets() {
    //TODO
}

void TAnketaProcessor::SetupGroupAttrs() {
    TString errors;
    TString groups;
    for (const auto& attr : Anketa["grouping_attrs"].GetArray()) {
        const TString& name = Strip(attr["name"].GetString());
        if (!name)
            continue;
        const TString& type = Strip(attr["type"].GetString());
        bool uniq = attr["uniq"].GetBoolean();
        if (uniq && type == "2:named")
            errors += (!!errors ? ", " : "") + name;
        else
            groups += " " + name + ":" + type + (uniq ? ":unique" : "");
    }
    if (!!errors)
        Error(TError::ERROR, "$grouping_attrs$ " + errors + " не могут быть одновременно литеральными и уникальными.", true);
    else if (!!groups)
        JsonFiles["/configs/" + SearchService + "/rtyserver.diff-" + SearchService]["Indexer.Commmon.Groups"] = groups;
}

void TAnketaProcessor::SetupSnippetDeniedZones() {
    TVector<TString> sdzones;
    for (const auto& zone : Anketa["search_zones"].GetArray()) {
        const TString& name = Strip(zone["name"].GetString());
        if (!name)
            continue;
        if (zone["snippet_denied"].GetBooleanRobust())
            sdzones.push_back(name);
    }
    if (sdzones.size())
        JsonFiles["/configs/" + SearchService + "/rtyserver.diff-" + SearchService]["Searcher.SnippetsDeniedZones"] = JoinStrings(sdzones, ",");
}

NJson::TJsonValue TAnketaProcessor::SetupParser(const TString& ml) {
    NJson::TJsonValue result(NJson::JSON_NULL);
    if (!GetCheckListValues("indexing_parsing").contains(ml))
        return result;
    typedef THashMap<std::pair<TString, TString>, THashSet<TString> > TMlElemToYAtr;
    TMlElemToYAtr mlElemToYAtr;
    THashSet<TString> setedAttrs;
    for (const auto& attrConf : Anketa[ml + "_parser_attrs"].GetArray()) {
        const TString& name = Strip(attrConf["name"].GetString());
        if (!name)
            continue;
        TString type;
        if (SearchLiteralAttrs.contains(name))
            type = "LITERAL";
        else if (SearchIntegerAttrs.contains(name))
            type = "INTEGER";
        else
            Error(TError::ERROR, "Ошибка настройки " + ml + "-parser, $" + ml + "_parser_attrs$ " + name + " неизвестен. Задайте его в $search_attrs_integer$ или $search_attrs_literal$.", true);
        TVector<TString> attrStr;
        for (const auto& mlJson : attrConf[ml].GetArray()) {
            const TString& elem = Strip(mlJson["element"].GetString());
            const TString& attr = Strip(mlJson["attr"].GetString());
            if (!elem || !attr)
                Error(TError::ERROR, "Ошибка настройки " + ml + "-parser, $" + ml + "_parser_attrs$ " + name + " задан некорректно, не задан " + ml + " элемент или атрибут.", true);
            attrStr.push_back(elem + "." + attr);
            mlElemToYAtr[std::make_pair(elem, attr)].insert(name);
        }
        if (!attrStr.empty()) {
            result["Attributes"][0][name] = type + "/" + JoinStrings(attrStr, ",");
            setedAttrs.insert(name);
        }
    }
    for (const auto& attr : SearchIntegerAttrs)
        if (!setedAttrs.contains(attr))
            Error(TError::WARNING, attr + " из $search_attrs_integer$ не задан в " + ml + "-parser, в секции $" + ml + "_parser_attrs$");
    for (const auto& attr : SearchLiteralAttrs)
        if (!setedAttrs.contains(attr))
            Error(TError::WARNING, attr + " из $search_attrs_literal$ не задан в " + ml + "-parser, в секции $" + ml + "_parser_attrs$");
    THashSet<TString> setedZones;
    ui32 fake_attrs_count = 0;
    for (const auto& zoneConf : Anketa[ml + "_parser_zones"].GetArray()) {
        const TString& name = Strip(zoneConf["name"].GetString());
        if (!name)
            continue;
        if (!SearchZones.contains(name))
            Error(TError::WARNING, "$" + ml + "_parser_zones$ " + name + " задан в " + ml + "-parser, но не задан в $search_zones$");
        TVector<TString> elemStr;
        THashSet<std::pair<TString, TString> > yattr;
        THashSet<TString> yattrs = setedAttrs;
        bool needYAttr = false;
        for (const auto& mlJson : zoneConf[ml].GetArray()) {
            const TString& elem = Strip(mlJson["element"].GetString());
            const TString& attr = Strip(mlJson["attr"].GetString());
            if (!elem)
                Error(TError::ERROR, "Ошибка настройки " + ml + "-parser, $" + ml + "_parser_zones$ " + name + " задан некорректно, не задан " + ml + " элемент.", true);
            elemStr.push_back(elem);
            if (!!attr) {
                needYAttr = true;
                std::pair<TString, TString> elemAttr(elem, attr);
                yattr.insert(elemAttr);
                if (!yattrs.empty()) {
                    TMlElemToYAtr::const_iterator createdYAttr = mlElemToYAtr.find(elemAttr);
                    if (createdYAttr == mlElemToYAtr.end())
                        createdYAttr = mlElemToYAtr.find(std::make_pair("_", attr));
                    if (createdYAttr == mlElemToYAtr.end())
                        yattrs.clear();
                    else {
                        THashSet<TString> newYAttrs;
                        for (const auto& y : createdYAttr->second)
                            if (yattrs.contains(y))
                                newYAttrs.insert(y);
                        yattrs = newYAttrs;
                    }
                }
            } else
                yattr.insert(std::make_pair(elem, "_"));
        }
        if (!elemStr.empty()) {
            TString resultStr = JoinStrings(elemStr, ",");
            if (needYAttr) {
                TString yattrName;
                if (!yattrs.empty())
                    yattrName = *yattrs.begin();
                else {
                    yattrName = ml + "_fake_attr_" + ToString(fake_attrs_count++);
                    TVector<TString> newYAttrStr;
                    for (const auto& mlAttr : yattr) {
                        mlElemToYAtr[mlAttr].insert(yattrName);
                        newYAttrStr.push_back(mlAttr.first + "." + mlAttr.second);
                    }
                    setedAttrs.insert(yattrName);
                    result["Attributes"][0][yattrName] =
                        "LITERAL,doc,,ignore/" + JoinStrings(newYAttrStr, ",");
                }
                resultStr += "/" + yattrName;
            }
            result["Zones"][0][name] = resultStr;
            setedZones.insert(name);
        }
    }
    for (const auto& zone : SearchZones)
        if (!setedZones.contains(zone))
            Error(TError::WARNING, zone + " из $search_zones$ не задан в " + ml + "-parser, в секции $" + ml + "_parser_zones$");
    return result;
}

void TAnketaProcessor::SetupXmlParser(const TString& service) {
    if (!XmlParserConfig)
        XmlParserConfig = MakeHolder<NJson::TJsonValue>(SetupParser("xml"));
    if (!XmlParserConfig->IsNull()) {
        YConfigFiles["/configs/" + service + "/xml.conf-" + service]["XmlParser"][0]["DOCTYPE"][0] = *XmlParserConfig;
        JsonFiles["/configs/" + service + "/rtyserver.diff-" + service]["Indexer.Common.XmlParserConfigFile"] = "${WorkDir and WorkDir or _BIN_DIRECTORY}/configs/xml.conf-" + service;
    }
}
void TAnketaProcessor::SetupHtmlParser(const TString& service) {
    if (!HtmlParserConfig)
        HtmlParserConfig = MakeHolder<NJson::TJsonValue>(SetupParser("html"));
    if (!HtmlParserConfig->IsNull()) {
        YConfigFiles["/configs/" + service + "/html.conf-" + service]["HtmlParser"][0] = *HtmlParserConfig;
        JsonFiles["/configs/" + service + "/rtyserver.diff-" + service]["Indexer.Common.HtmlParserConfigFile"] = "${WorkDir and WorkDir or _BIN_DIRECTORY}/configs/html.conf-" + service;
    }
}

void TAnketaProcessor::SetupZoneToProperies() {
    //TODO
}

void TAnketaProcessor::SetupLanguages() {
    //TODO
}

void TAnketaProcessor::SetupRelev() {
    //TODO
}


void TAnketaProcessor::SetupServiceMeta() {
    if (!MetaService)
        return;
}

void TAnketaProcessor::SetupProject() {
    TSet<TString> searchTypes = GetCheckListValues("search_types");
    enum { NO, SEARCH, KEY_VALUE } searchType = NO;
    if (searchTypes.contains("key-value"))
        searchType = KEY_VALUE;
    if (searchTypes.contains("search"))
        searchType = SEARCH;
    bool suggest = searchTypes.contains("suggest");
    if (suggest && searchType != NO) {
        SearchService = Project + "-search";
        SuggestService = Project + "-suggest";
    }
    else if (suggest) {
        SuggestService = Project;
    }
    else if (searchType != NO) {
        SearchService = Project;
    }
    else
        Error(TError::ERROR, "Не выбран ни один $search_types$", true);
    Reply.MutableProject()->SetName(Project);
    Reply.MutableProject()->SetCaption(Strip(Anketa["service_caption"].GetString()));
    FillProjectComponent(MetaService, META_SERVICE_SERVICE, false);
    FillProjectComponent(SearchService, RTYSERVER_SERVICE, true);
    FillProjectComponent(SuggestService, RTYSERVER_SERVICE, false);
    SetupServiceMeta();
    switch (searchType) {
    case SEARCH:
        SetupServiceSearch();
        break;
    case KEY_VALUE:
        SetupServiceKeyValue();
        break;
    case NO:
        break;
    }
    SetupServiceSuggest();
    SetupCgiParams();
}

void TAnketaProcessor::FillProjectComponent(const TString& name, const TString& type, bool main)
{
    if (!name)
        return;
    NDMInterface::TProject::TComponent& comp = *Reply.MutableProject()->AddComponents();
    comp.SetServiceName(name);
    comp.SetServiceType(type);
    comp.SetMain(main);
}

void TAnketaProcessor::SetupUser()
{
    NDMInterface::TUser user = NRTYDeploy::GetUser(Storage, User.Login);
    if (user.GetDefaultRole() < NDMInterface::TUser::ADMINISTRATOR)
        Error(TError::ERROR, "user " + User.Login + " not permitted to create project", true);
    NDMInterface::TUser::TRoleInProject& roleInPrj = *user.AddRoleInProject();
    roleInPrj.SetRole(NDMInterface::TUser::ADMINISTRATOR);
    roleInPrj.SetProject(Project);
    AddFile("/users/" + User.Login, user);
}

void TAnketaProcessor::DoProcess()
{
    SetupUser();
    TString project = Strip(Anketa[SERVICE_NAME_NAME].GetString());
    if (!Project)
        Project = project;
    else if (!!project && project != Project) {
        Error(TError::ERROR, "Параметр $service_name$ не может быть изменен", true);
    }

    if (!Project)
        Error(TError::ERROR, "$service_name$ не задан", true);
    Storage.SetValue("/projects/" + Project + "/anketa", NJson::WriteJson(Anketa));
    AddFile("/projects/" + Project + "/anketa", Anketa);
    if (!Anketa["access_gotten"].GetBooleanRobust())
        Error(TError::WARNING, "Вы не получили доступы к SAAS. Сделайте это и поставьте галочку $access_gotten$.");
    SetupProject();
    AddRequestParam("text");
    AddRequestParam("template", true);
    AddFile("/projects/" + Project, Reply.GetProject());
    Reply.SetOk(true);
}

TSet<TString> TAnketaProcessor::GetCheckListValues(const TString& name) const
{
    TSet<TString> result;
    for (const auto& i : Anketa[name].GetArray()) {
        TString value = Strip(i.GetStringRobust());
        if (!!value)
            result.insert(value);
    }
    return result;
}

NDMInterface::TProject::TRequestExample::TParam& TAnketaProcessor::AddRequestParam(const TString& name, bool optional)
{
    NDMInterface::TProject::TRequestExample::TParam& result = AddRequestParam(name);
    result.SetOptional(optional);
    return result;
}

NDMInterface::TProject::TRequestExample::TParam& TAnketaProcessor::AddRequestParam(const TString& name)
{
    if (Reply.GetProject().RequestExamplesSize() == 0)
        Reply.MutableProject()->AddRequestExamples();
    NDMInterface::TProject::TRequestExample::TParam& result = *Reply.MutableProject()->MutableRequestExamples(0)->AddParams();
    TParamsById::const_iterator i = ParamsById.find(name);
    if (i != ParamsById.end())
        result = i->second;
    else
        result.SetName(name);
    return result;
}

void TAnketaProcessor::AddFile(const TString& name, const google::protobuf::Message& content)
{
    TString contentStr;
    ::google::protobuf::TextFormat::PrintToString(content, &contentStr);
    AddFile(name, contentStr);
}

void TAnketaProcessor::AddFile(const TString& name, const NJson::TJsonValue& content)
{
    AddFile(name, NJson::WriteJson(content, true, true));
}

void TAnketaProcessor::AddFile(const TString& name, const TString& content)
{
    NDMInterface::TServiceWizardReply::TFile& file = *Reply.AddFiles();
    file.SetName(name);
    file.SetContent(content);
}

void TAnketaProcessor::AddYConfigFile(const TString& name, const NJson::TJsonValue& content)
{
    TStringStream ss;
    PrintYConfigLevel(ss, "", content, 0);
    AddFile(name, ss.Str());
}

void TAnketaProcessor::PrintYConfigLevel(IOutputStream& out, const TString& name, const NJson::TJsonValue& content, ui32 level)
{
    TString tab(level * 4, ' ');
    switch (content.GetType()) {
    case NJson::JSON_MAP:
        for (const auto& field : content.GetMap())
            PrintYConfigLevel(out, field.first, field.second, level);
        break;
    case NJson::JSON_ARRAY:
        for (const auto& field : content.GetArray()) {
            if (name == "#include")
                out << tab << "#include " << field.GetStringRobust() << Endl;
            else {
                out << tab << "<" << name << ">" << Endl;
                PrintYConfigLevel(out, name, field, level + 1);
                out << tab << "</" << name << ">" << Endl;
            }
        }
        break;
    default:
        out << tab << name << ": " << content.GetStringRobust() << Endl;
    }
}

void TAnketaProcessor::Error(TError::TLevel level, const TString& message, bool stopProcess /*= false*/)
{
    TError e;
    e.SetLevel(level);
    TString resultMessage;
    for (TStringBuf part, msg(message); msg.NextTok('$', part);) {
        resultMessage += part;
        if (msg.NextTok('$', part)) {
            TQById::const_iterator q = QById.find(part);
            if (q == QById.end())
                ythrow yexception() << "unknown question " << part << " in message " << message;
            TError::TLink& link = *e.AddLinks();
            link.SetQuestionId(q->second.Data->GetCommon().GetId());
            link.SetPageId(q->second.Page);
            link.SetText(q->second.Data->GetCommon().GetTitle());
            resultMessage += "%s";
        }
    }
    e.SetMessage(resultMessage);
    if (stopProcess)
        throw e;
    else
        *Reply.AddErrors() = e;
}

void TAnketaProcessor::Process()
{
    try {
        DoProcess();
    }
    catch (const TError& e) {
        *Reply.AddErrors() = e;
    }
    catch (...) {
        Error(TError::ERROR, CurrentExceptionMessage());
    }
    for (const auto& file : JsonFiles.GetMap())
        AddFile(file.first, file.second);
    for (const auto& file : YConfigFiles.GetMap())
        AddYConfigFile(file.first, file.second);
}

const NDMInterface::TServiceWizardReply& TAnketaProcessor::GetReply() const
{
    return Reply;
}

TAnketaProcessor::TAnketaProcessor(IDeployInfoRequest& request, const NRTYDeploy::TScriptInterface::TUser& user)
    : Storage(request.GetStorage())
    , User(user)
{
    TMemoryInput is(request.GetBlob().AsCharPtr(), request.GetBlob().Size());
    NJson::ReadJsonTree(&is, &Anketa, true);
    DEBUG_LOG << WriteJson(Anketa, true, true) << Endl;
    Project = request.GetRD().CgiParam.Get("project");
    request.GetStorage().GetValue("service_wizard/questions", Questions);
    for (const auto& page : Questions.GetPages())
        for (const auto& question : page.GetQuestions())
            QById[question.GetCommon().GetId()] = TQuestionData(page.GetCommon().GetId(), question);

    NDMInterface::TProject::TRequestExample req;
    request.GetStorage().GetValue("service_wizard/request_params", req);
    for (const auto& param : req.GetParams())
        ParamsById[param.GetName()] = param;
}

void TAnketaProcessor::SetupCgiParams() {
    for (const auto& service : CgiParamsByService) {
        NJson::TJsonValue& sp = SearchProxyConfig(service.first)["CgiParams"][0];
        sp["rwr"] = "on:PlainNumbers";
        sp["wizextra"] = "usesoftness=da;usextsyntax=da";
        for (const auto& param : service.second)
            sp[param.first] = param.second;
    }
}

