#include "serviceconfig.h"
#include <saas/searchproxy/common/abstract.h>

#include <library/cpp/logger/global/global.h>
#include <library/cpp/mediator/global_notifications/system_status.h>

#include <util/string/subst.h>
#include <util/string/split.h>

namespace {
    const TString ServiceNamePlaceholder = "SERVICENAME";

    TString DetectCtype(TConfigPatcher& preprocessor) {
        const auto& vars = preprocessor.GetVariables();
        auto ctypeIt = vars.find("CTYPE");
        if (ctypeIt != vars.end()) {
            return ctypeIt->second;
        }
        return {};
    }

    TVector<ui32> ParseTvmSourceTvmIds(const TYandexConfig::Section& section, const TString& ctype, const TString& service) {
        static const TString sectionName = "AllowedSourceServiceIds";
        TVector<ui32> ret;
        const auto& sections = section.GetAllChildren();
        auto allowedSection = sections.find(sectionName);
        if (allowedSection != sections.end()) {
            Y_VERIFY(ctype);
            Y_ENSURE(allowedSection->second->GetDirectives().TryFillArray(ctype, ret), "Bad AllowedSourceServiceIds for " + service);
        } else {
            Y_ENSURE(section.GetDirectives().TryFillArray(sectionName, ret), "Bad AllowedSourceServiceIds for " + service);
        }
        return ret;
    }

    struct TSla {
        struct TCtype {
            ui32 AbcUserService = 0;
        };
        THashMap<TString, TCtype> Ctypes;
    };

    TServiceConfig::TTvmParams ReadTvmParams(const TYandexConfig::Section& section, const TString& ctype, const TSla::TCtype& sla, const TString& service) {
        TServiceConfig::TTvmParams ret;
        const auto& directives = section.GetDirectives();
        directives.GetValue("Mode", ret.Mode);

        const TVector<ui32> sources = ParseTvmSourceTvmIds(section, ctype, service);
        ret.AllowedSourceTvmIds.insert(sources.begin(), sources.end());

        Y_ENSURE(directives.TryFillArray("UserAbcGroups", ret.AllowedUserAbcGroups), "Bad UserAbcGroups for " + service);
        if (const auto id = sla.AbcUserService) {
            ret.AllowedUserAbcGroups.push_back(id);
        }

        return ret;
    }

    TSla ParseSla(const TYandexConfig::Section& section) {
        const TYandexConfig::TSectionsMap& sections = section.GetAllChildren();
        TSla result;
        auto [it, upper] = sections.equal_range("Ctype");
        for (; it != upper; ++it) {
            const auto& ctypeSect = it->second;
            const auto& directives = ctypeSect->GetDirectives();

            TString ctypeName;
            directives.GetValue("Ctype", ctypeName);

            const ui32 abcId = directives.Value("AbcUserService", 0);

            result.Ctypes[ctypeName] = {abcId};
        }
        return result;
    }
}

void TServiceConfig::TExtraCgi::Add(const TString& name, const TString& value, EPolicy policy) {
    auto parameter = std::find_if(Parameters.begin(), Parameters.end(), [&name, policy](const TParameter& p) {
        return p.Name == name && p.Policy == policy;
    });
    if (parameter == Parameters.end() || policy == EPolicy::CalculateAdditional) {
        parameter = Parameters.insert(Parameters.end(), TParameter());
        parameter->Name = name;
        parameter->Policy = policy;
    }
    CHECK_WITH_LOG(parameter->Name == name);
    CHECK_WITH_LOG(parameter->Policy == policy);

    auto& values = parameter->Values;
    if (std::find(values.begin(), values.end(), value) == values.end()) {
        values.push_back(value);
    }
}

void TServiceConfig::TExtraCgi::Update(TExtraCgi&& patch) {
    TSet<EPolicy> patchPolicies;

    for (const TParameter& patchParameter: patch.Get()) {
        patchPolicies.insert(patchParameter.Policy);
    }

    Parameters.erase(std::remove_if(Parameters.begin(), Parameters.end(), [&patchPolicies](const TParameter& p) {
        return patchPolicies.contains(p.Policy);
    }), Parameters.end());

    std::move(patch.Get().begin(), patch.Get().end(), std::back_inserter(Parameters));
    patch.Clear(); // safebelt
}

void TServiceConfig::TExtraCgi::Clear() {
    Parameters.clear();
}

size_t TServiceConfig::TExtraCgi::Size() const {
    return Parameters.size();
}

void TServiceConfig::ReadCgiParams(const TYandexConfig::TSectionsMap& sections, const TString& sectionName, TExtraCgi::EPolicy defaultPolicy, TExtraCgi& extraCgi) {
    const TYandexConfig::TSectionsMap::const_iterator& iter = sections.find(sectionName);
    if (iter != sections.end()) {
        const TYandexConfig::Directives& directives = iter->second->GetDirectives();
        for (TYandexConfig::Directives::const_iterator iter =
            directives.begin(), end = directives.end(); iter != end; ++iter) {
            for (const auto& value: SplitString(iter->second, "&")) {
                extraCgi.Add(iter->first, value, defaultPolicy);
            }
        }
    }
}

void TServiceConfig::ReadCustomRearranges(const TYandexConfig::TSectionsMap& sections, const TString& sectionName) {
    const TYandexConfig::TSectionsMap::const_iterator& iter = sections.find(sectionName);
    if (iter != sections.end()) {
        TSet<TString> rearranges;
        Singleton<ICustomRearrangeFactory::TFactory>()->GetKeys(rearranges);
        const TYandexConfig::Directives& directives = iter->second->GetDirectives();
        for (TYandexConfig::Directives::const_iterator dIter =
            directives.begin(), end = directives.end(); dIter != end; ++dIter) {
            TCustomRearrangeParams oneRule;
            if (oneRule.Init(dIter->first, dIter->second)) {
                VERIFY_WITH_LOG(rearranges.contains(oneRule.Name), "Unknown custom rearrange in config %s for service %s", oneRule.Name.data(), Name.data());
                CustomRearranges.push_back(oneRule);
            }
        }
    }
}

void TServiceConfig::ReadExtraCgi(const TYandexConfig::TSectionsMap& sections, const TString& sectionName, TExtraCgi& extraCgi) {
    auto section = sections.find(sectionName);
    if (section == sections.end()) {
        return;
    }

    const auto& directives = section->second->GetDirectives();
    for (auto&& directive : directives) {
        TString name = directive.first;
        TString value = directive.second;
        TExtraCgi::EPolicy policy = TExtraCgi::EPolicy::Replace;
        if (name.StartsWith("+$")) {
            name = name.substr(2);
            policy = TExtraCgi::EPolicy::CalculateAdditional;
        }
        if (name.StartsWith("?$")) {
            name = name.substr(2);
            policy = TExtraCgi::EPolicy::CalculateIfEmpty;
        }
        if (name[0] == '+') {
            name = name.substr(1);
            policy = TExtraCgi::EPolicy::Add;
        }
        if (name[0] == '-') {
            name = name.substr(1);
            policy = TExtraCgi::EPolicy::Remove;
        }
        if (name[0] == '?') {
            name = name.substr(1);
            policy = TExtraCgi::EPolicy::AddIfEmpty;
        }
        if (name[0] == '$') {
            name = name.substr(1);
            policy = TExtraCgi::EPolicy::Calculate;
        }
        for (auto&& v: SplitString(value, "&")) {
            extraCgi.Add(name, v, policy);
        }
    }
}

void TServiceConfig::InitFromSection(TYandexConfig::Section* section, TConfigPatcher& preprocessor, bool forceMetaSearch) {
    const TYandexConfig::TSectionsMap& sections = section->GetAllChildren();
    const TYandexConfig::Directives& directives = section->GetDirectives();
    directives.GetValue("Name", Name);
    directives.GetValue("EventLogTemplate", EventLogTemplate);
    directives.GetValue("EventLogCompressionFormat", EventLogCompressionFormat);
    directives.GetValue("EventLogEnabled", EventLogEnable);
    directives.GetValue("ReqAnsLogEnabled", ReqAnsLogEnabled);
    directives.GetValue("ConsistencyPolicy", ConsistencyPolicy);
    directives.GetValue("Compression", Compression);
    directives.GetValue("GroupingByDC", GroupingByDC);
    directives.GetValue("SmartQueue", SmartQueue);
    directives.GetValue("FactorsLogAggregationStrategy", FactorsLogAggregationStrategy);

    TString defaultMetaSearch;
    directives.GetValue("DefaultMetaSearch", defaultMetaSearch);
    if (defaultMetaSearch) {
        AssertCorrectConfig(IProxy::TFactory::Has(defaultMetaSearch), "Unknown proxy type %s for service %s", defaultMetaSearch.data(), Name.data());
        DefaultMetaSearch = defaultMetaSearch;
    }

    TExtraCgi extraCgi;
    ReadExtraCgi(sections, "CgiParams", extraCgi);
    ReadCgiParams(sections, "StrictCgiParams", TExtraCgi::EPolicy::Replace, extraCgi);
    ReadCgiParams(sections, "SoftCgiParams", TExtraCgi::EPolicy::AddIfEmpty, extraCgi);
    ReadCgiParams(sections, "AppendCgiParams", TExtraCgi::EPolicy::Add, extraCgi);
    if (extraCgi.Size()) {
        ExtraCgi.Update(std::move(extraCgi));
    }

    TExtraCgi globalExtraCgi;
    ReadExtraCgi(sections, "GlobalCgiParams", globalExtraCgi);
    ReadCgiParams(sections, "GlobalSoftCgiParams", TExtraCgi::EPolicy::AddIfEmpty, globalExtraCgi);

    for (auto&& param : globalExtraCgi.Get()) {
        if (param.Name == "sp_meta_search") {
            AssertCorrectConfig(!defaultMetaSearch, "Can't use sp_meta_search replacement with DefaultMetaSearch option for service " + Name);
        }
    }
    if (globalExtraCgi.Size()) {
        GlobalExtraCgi.Update(std::move(globalExtraCgi));
    }

    ReadCustomRearranges(sections, "CustomRearranges");

    TString cgiCorrectorRules;
    directives.GetValue("CgiCorrectorRules", cgiCorrectorRules);
    if (cgiCorrectorRules) {
        StringSplitter(cgiCorrectorRules).Split(',').SkipEmpty().Collect(&CgiCorrectorRules);
    }

    const TYandexConfig::TSectionsMap::const_iterator& iterProxyMeta = sections.find("ProxyMeta");
    const bool hasProxyMeta = iterProxyMeta != sections.end();
    if (iterProxyMeta != sections.end()) {
        if (!ProxyMetaConfig) {
            ProxyMetaConfig = MakeHolder<NProxyMeta::TConfig>().Release();
        }
        ProxyMetaConfig->InitFromSection(iterProxyMeta->second, GetEventLogFromTemplate());
    } else {
        ProxyMetaConfig.Destroy();
    }

    const TYandexConfig::TSectionsMap::const_iterator& iterMetaSearch = sections.find("MetaSearch");
    const bool hasMetaSearch = iterMetaSearch != sections.end();
    if (iterMetaSearch != sections.end()) {
        CHECK_WITH_LOG(MetaSearchConfig);
        MetaSearchConfig->InitFromSection(iterMetaSearch->second, preprocessor);
    } else if ((!hasMetaSearch && !hasProxyMeta) || forceMetaSearch) {
        CHECK_WITH_LOG(MetaSearchConfig);
        MetaSearchConfig->InitFromSection(section, preprocessor);
    } else {
        MetaSearchConfig.Destroy();
    }

    // default is different for full-text and key-value (SAAS-4313, SAAS-5470)
    NormalizeReplicas = directives.Value("NormalizeReplicas", MetaSearchConfig ? false : true);

    auto iterHttpStatus = sections.find("HttpStatuses");
    if (iterHttpStatus != sections.end()) {
        HttpStatusManagerConfig = MakeHolder<THttpStatusManagerConfig>(iterHttpStatus->second->GetDirectives()).Release();
    }

    auto iterTvm = sections.find("Tvm");
    if (iterTvm != sections.end()) {
        TSla sla;
        auto iterSla = sections.find("Sla");
        if (iterSla != sections.end()) {
            sla = ParseSla(*iterSla->second);
        }

        const TString ctype = DetectCtype(preprocessor);
        Tvm = ReadTvmParams(*iterTvm->second, ctype, sla.Ctypes[ctype], Name);
    }

    TString factorsConfig;
    directives.GetValue("FactorsInfo", factorsConfig);
    if (!factorsConfig.empty()) {
        try {
            Factors = new NRTYFactors::TConfig(factorsConfig.data(), {}, preprocessor.GetPreprocessor());
        } catch(yexception&) {
            AssertCorrectConfig(false, "in factors config: %s", CurrentExceptionMessage().data());
        }
    }
}

void TServiceConfig::SetMainThreadCount(ui32 count) {
    if (MetaSearchConfig) {
        MetaSearchConfig->ProtoCollection_.SetRequestThreads(count);
    }
}

void TServiceConfig::SetPort(ui16 port) {
    if (MetaSearchConfig) {
        MetaSearchConfig->ProtoServer_.SetPort(port);
    }
}

void TServiceConfig::SetWorkDir(const TString &directory) {
    if (MetaSearchConfig) {
        MetaSearchConfig->SetWorkDir(directory);
    }
}

TString TServiceConfig::GetEventLogFromTemplate() const {
    if (!EventLogTemplate) {
        return Default<TString>();
    }
    if (EventLogTemplate.find(ServiceNamePlaceholder) == TString::npos) {
        ERROR_LOG << "EventLogTemplate for service " << Name << " does not include ServiceNamePlaceholder " << ServiceNamePlaceholder << Endl;
        return Default<TString>();
    }

    TString result = EventLogTemplate;
    SubstGlobal(result, ServiceNamePlaceholder, Name);
    return result;
}

const TString& TServiceConfig::GetDefaultMetaSearch() const {
    return DefaultMetaSearch;
}

TString TServiceConfig::GetEventLogName() const {
    TString result;
    if (MetaSearchConfig) {
        result = MetaSearchConfig->ProtoCollection_.GetEventLog();
    }
    if (!result) {
        result = GetEventLogFromTemplate();
    }
    return result;
}

TString TServiceConfig::GetEventLogCompressionFormat() const {
    return EventLogCompressionFormat;
}

bool TServiceConfig::IsEventLogEnabled() const {
    return EventLogEnable || (MetaSearchConfig ? !!MetaSearchConfig->ProtoCollection_.GetEventLog() : false);
}

bool TServiceConfig::IsReqAnsLogEnabled() const {
    return ReqAnsLogEnabled;
}

TSourcesConfig TServiceConfig::GetSourcesConfig(bool metaservice) const {
    TSourcesConfig result = MetaSearchConfig ? *MetaSearchConfig : Default<TSearchServerConfig>();
    if (metaservice) {
        result.AdjustForMetaService();
    }
    return result;
}
