#include "searchproxyconfig.h"
#include "misspellcorrectorconfig.h"
#include "searchserverconfig.h"

#include <saas/searchproxy/logging/loggerconfig.h>
#include <saas/library/searchserver/http_status_config.h>
#include <saas/library/daemon_base/config/daemon_config.h>
#include <saas/library/daemon_base/daemon/messages.h>
#include <saas/library/searchmap/parsers/parser.h>
#include <saas/library/searchserver/protocol.h>

#include <kernel/searchlog/errorlog.h>

#include <library/cpp/eventlog/eventlog.h>
#include <library/cpp/logger/global/global.h>

#include <util/folder/dirut.h>
#include <util/generic/string.h>
#include <util/generic/yexception.h>
#include <util/stream/file.h>
#include <utility>

void TSearchProxyConfig::ParseLogger() {
    const TYandexConfig::TSectionsMap& sections = ServerSection->GetAllChildren();

    TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Logger");
    if (iter != sections.end()) {
        LoggerConfig.Reset(new TLoggerConfig(iter->second->GetDirectives(), WorkDir, DaemonConfig));
    }
}

void TSearchProxyConfig::ParseServices(TConfigPatcher& preprocessor)
{
    const TYandexConfig::TSectionsMap& children =
        ServerSection->GetAllChildren();
    const char service[] = "Service";
    for (TYandexConfig::TSectionsMap::const_iterator iter =
        children.lower_bound(service), end = children.upper_bound(service);
        iter != end; ++iter) {
        TString service;
        if (!iter->second->GetDirectives().GetValue("Name", service) || !service)
        {
            throw yexception() << "No name specified for service";
        }

        if (!SearchMap.HasService(service)) {
            if (!UpperSearch)
                continue;

            throw yexception() << "Service " << service.Quote() << " not defined in searchmap";
        }

        DEBUG_LOG << "Parsing service " << service << Endl;
        TServiceConfig config(DefaultServiceConfig);
        config.InitFromSection(iter->second, preprocessor, /*forceMetaSearch=*/false);

        if (!ServicesConfigs.insert(
            std::make_pair(service, config)).second)
        {
            throw yexception() << "Service " << service.Quote()
                << " defined twice";
        }
    }
}

void TSearchProxyConfig::ParseMode()
{
    const TYandexConfig::Directives& directives = ServerSection->GetDirectives();
    directives.GetValue("UpperSearch", UpperSearch);
}

namespace {
    const TYandexConfig::Section* FindSectionPtr(const TYandexConfig::Section& parent, const char* name) {
        const TYandexConfig::TSectionsMap& children = parent.GetAllChildren();
        const TYandexConfig::TSectionsMap::const_iterator it = children.find(name);
        return it == children.end() ? nullptr : it->second;
    }
}

void TSearchProxyConfig::ParseAppHostConfig() {
    AppHostConfig.Grpc.Threads = NehServerOptions.nThreads;

    const auto* appHost = FindSectionPtr(*ServerSection, "AppHost");
    if (!appHost) {
        return;
    }

    const TYandexConfig::Directives& directives = appHost->GetDirectives();
    directives.GetValue("Enabled", AppHostConfig.Enabled);
    directives.GetValue("Port", AppHostConfig.Port);
    if (AppHostConfig.Enabled && !AppHostConfig.Port)
        ythrow yexception() << "AppHost enabled, but port no set";

    if (const auto* grpc = FindSectionPtr(*appHost, "Grpc")) {
        ParseAppHostGrpcConfig(*grpc, AppHostConfig.Grpc);
    }

    if (const auto* tvm = FindSectionPtr(*appHost, "Tvm")) {
        ParseAppHostTvmConfig(*tvm, AppHostConfig.Tvm);
    }

    if (AppHostConfig.Tvm.Enabled && !AppHostConfig.Grpc.Enabled) {
        ythrow yexception() << "AppHost TVM enabled, but GRPC is not";
    }
}

void TSearchProxyConfig::ParseAppHostTvmConfig(const TYandexConfig::Section& section, TAppHostConfig::TTvmParams& tvm) {
    const auto& directives = section.GetDirectives();
    directives.GetValue("Enabled", tvm.Enabled);
    directives.GetValue("TvmId", tvm.TvmId);
    Y_ENSURE(directives.TryFillArray("AllowedSourceTvmIds", tvm.AllowedSourceTvmIds));
}

void TSearchProxyConfig::ParseAppHostGrpcConfig(const TYandexConfig::Section& section, TAppHostConfig::TGrpcParams& grpc) {
    const auto& directives = section.GetDirectives();
    directives.GetValue("Enabled", grpc.Enabled);
    directives.GetValue("Port", grpc.Port);
    directives.GetValue("Threads", grpc.Threads);

    if (grpc.Enabled && !grpc.Port) {
        ythrow yexception() << "AppHost GRPC enabled, but port no set";
    }
}

void TSearchProxyConfig::ParseTvmConfig() {
    const TYandexConfig::TSectionsMap& children = ServerSection->GetAllChildren();
    const TYandexConfig::TSectionsMap::const_iterator it = children.find("Tvm");
    if (it == children.end()) {
        return;
    }

    const TYandexConfig::Directives& directives = it->second->GetDirectives();
    directives.GetValue("Enabled", Tvm.Enabled);

    directives.GetValue("TvmId", Tvm.TvmId);
    directives.GetValue("FlowMirrorTvmId", Tvm.FlowMirrorTvmId);

    directives.GetValue("DiskCacheDir", Tvm.DiskCacheDir);

    if (Tvm.Enabled && !Tvm.TvmId) {
        ythrow yexception() << "TVM enabled, but TvmId is not set";
    }

    directives.GetValue("TvmProxyTvmId", Tvm.TvmProxyTvmId);
    Y_ENSURE(directives.TryFillArray("UserAbcGroups", Tvm.AllowedUserAbcGroups));

    directives.GetValue("EnableUserTickets", Tvm.EnableUserTickets);
    directives.GetValue("AbcApiTvmId", Tvm.AbcApiTvmId);

    // FIXME Check DiskCacheDir existance and rights before TTvmClient creation
}

void TSearchProxyConfig::ParseFlowMirrorConfig() {
    const TYandexConfig::TSectionsMap& children = ServerSection->GetAllChildren();
    const TYandexConfig::TSectionsMap::const_iterator flow = children.find("FlowMirror");
    if (flow == children.end())
        return;

    const TYandexConfig::Directives& directives = flow->second->GetDirectives();
    directives.GetValue("Enabled", FlowMirrorConfig.Enabled);
    directives.GetValue("Host", FlowMirrorConfig.Host);
    directives.GetValue("Port", FlowMirrorConfig.Port);
    directives.GetValue("Threads", FlowMirrorConfig.Threads);
    directives.GetValue("Fraction", FlowMirrorConfig.Fraction);
    directives.GetValue("CopyHeaders", FlowMirrorConfig.CopyHeaders);

    const TYandexConfig::TSectionsMap& flowChildren = flow->second->GetAllChildren();
    const auto slicesSections = flowChildren.equal_range("Slice");
    for (auto sliceSection = slicesSections.first; sliceSection != slicesSections.second; ++sliceSection) {
        TFlowMirror::TConfig::TSlice slice;

        const TYandexConfig::Directives& sliceDirectives = sliceSection->second->GetDirectives();
        sliceDirectives.GetValue("Fraction", slice.Fraction);
        sliceDirectives.GetValue("Regexp", slice.Regexp);

        slice.CompiledRegexp.Compile(slice.Regexp);
        FlowMirrorConfig.Slices.push_back(std::move(slice));
    }
}

void TSearchProxyConfig::ParseConfig(TConfigPatcher& preprocessor)
{
    const TYandexConfig::Directives& directives = ServerSection->GetDirectives();
    const TYandexConfig::TSectionsMap& children = ServerSection->GetAllChildren();

    HttpServerInstances = directives.Value("HttpServerInstances", 1);

    SleepBeforeListen = TDuration::Seconds(directives.Value("SleepBeforeListenS", 0));

    const auto httpSection = children.find("HttpServerOptions");
    if (httpSection != children.end()) {
        HttpServerOptions = TDaemonConfig::ParseHttpServerOptions(httpSection->second->GetDirectives());
    } else {
        // compatibility: HTTP options can be embedded into the SearchProxy section
        HttpServerOptions = TDaemonConfig::ParseHttpServerOptions(directives);
    }
    if (HttpServerInstances > 1) {
        HttpServerOptions.EnableReusePort(true);
    }

    const auto nehSection = children.find("NehServerOptions");
    if (nehSection != children.end()) {
        NehServerOptions = TDaemonConfig::ParseHttpServerOptions(nehSection->second->GetDirectives());
    } else {
        // by default Neh options are derived from HTTP options
        NehServerOptions  = HttpServerOptions;
        NehServerOptions.Port += 1;
    }

    NehServerSchemes.push_back(MetaSearchNehProtocol);
    NehServerSchemes.push_back(InprocNehProtocol);

    TString cwd = NFs::CurrentWorkingDirectory() + GetDirectorySeparator();
    if (directives.GetValue("WorkDir", WorkDir)) {
        resolvepath(WorkDir, cwd);
    } else {
        WorkDir = cwd;
    }

    directives.GetValue("AuthKeyWord", AuthKeyWord);

    DefaultServiceConfig.SetWorkDir(WorkDir);
    DefaultServiceConfig.SetMainThreadCount(HttpServerOptions.nThreads);
    DefaultServiceConfig.SetPort(HttpServerOptions.Port);
    const TYandexConfig::TSectionsMap::const_iterator iter =
        children.find("SearchConfig");
    if (iter != children.end()) {
        DefaultServiceConfig.InitFromSection(iter->second, preprocessor, /*forceMetaSearch=*/true);

        TString sdOptionsStr;
        iter->second->GetDirectives().GetValue("ServiceDiscoveryOptions", sdOptionsStr);
        if (sdOptionsStr != "") {
            ServiceDiscoveryOptions.Parse(sdOptionsStr);
        }
    }

    if (TLoggerOperator<TGlobalLog>::Usage()) {
        GET_ERRORLOG = TLoggerOperator<TGlobalLog>::Log();
    }

    ParseServices(preprocessor);
    ParseLogger();
    ParseFlowMirrorConfig();
    ParseAppHostConfig();
    ParseTvmConfig();
}

void TSearchProxyConfig::ParseSearchMap(TConfigPatcher& preprocessor)
{
    TString searchMap;
    if (!ServerSection->GetDirectives().GetValue("SearchMap", searchMap) || !searchMap)
        throw yexception() << "Directive SearchMap wasn't found";

    THolder<NSearchMapParser::ISearchMapParser> parser(NSearchMapParser::OpenSearchMap(preprocessor.GetPreprocessor()->ReadAndProcess(searchMap)));
    SearchMap = parser->GetSearchMap(false, UpperSearch);
}

const TServiceConfig& TSearchProxyConfig::GetServiceConfig(const TString& service) const
{
    TServicesConfigs::const_iterator iter = ServicesConfigs.find(service);
    if (iter == ServicesConfigs.end()) {
        return DefaultServiceConfig;
    } else {
        return iter->second;
    }
}

const TSearchProxyConfig::TServicesConfigs& TSearchProxyConfig::GetServicesConfigs() const {
    return ServicesConfigs;
}

TSearchProxyConfig::TSearchProxyConfig(const TServerConfigConstructorParams& params)
    : TParser(params.Text.data(), "SearchProxy")
    , DaemonConfig(*params.Daemon)
    , UpperSearch(true)
{
    try {
        ParseMode();
        ParseSearchMap(*params.Preprocessor);
        ParseConfig(*params.Preprocessor);
    } catch (const yexception& e) {
        AssertCorrectConfig(false, "cannot parse config: %s", e.what());
    }
}

TSearchProxyConfig::TSearchProxyConfig(const char* config,
    const TDaemonConfig& daemonConfig,
    const NSearchMapParser::TSearchMap& searchMap, TConfigPatcher& preprocessor)
    : TParser(config, "SearchProxy")
    , DaemonConfig(daemonConfig)
    , SearchMap(searchMap)
    , UpperSearch(true)
{
    ParseMode();
    ParseConfig(preprocessor);
}
