#include "config.h"

#include <saas/library/daemon_base/config/daemon_config.h>
#include <saas/library/daemon_base/daemon/messages.h>

#include <library/cpp/digest/md5/md5.h>
#include <library/cpp/html/face/parsface.h>

#include <util/folder/dirut.h>
#include <util/string/split.h>
#include <util/string/vector.h>
#include <util/system/fs.h>
#include <library/cpp/logger/global/global.h>
#include <saas/indexerproxy/logging/rty_ipr.h>

#define GET_VALUE(name) { if (directives.find(#name) != directives.end()) name = directives.Value(#name, name); }

TAdapterConfig::TAdapterConfig(const TProxyConfig& owner)
    : Owner(owner)
{
}

TExportConfig::TExportConfig(const TProxyConfig& owner)
    : Owner(owner)
{
}

TProxyConfig::TProxyConfig(const TServerConfigConstructorParams& params)
    : Impl(new TAnyYandexConfig)
    , DaemonConfig(*params.Daemon)
    , DispatcherConfig(*this)
    , ExportConfig(*this)
    , Preprocessor(*params.Preprocessor)
{
    InitFromString(params.Text.data());
}


void TProxyServiceConfig::Init(const TString& serviceName, const TYandexConfig::Section& configSection) {
    TYandexConfig::TSectionsMap sections = configSection.GetAllChildren();
    const TYandexConfig::Directives& directives = configSection.GetDirectives();
    GET_VALUE(Realtimable);
    GET_VALUE(SessionPrefix);
    GET_VALUE(IndexationDisabled);
    GET_VALUE(DeferredQueueEnabled);
    GET_VALUE(SpaceLimitInMBytes);
    GET_VALUE(MaxDocsInWorkPerBackend);
    GET_VALUE(QueuePartSizeLimit);
    GET_VALUE(SendAttemptsCount);
    GET_VALUE(LoggingEnabled);
    GET_VALUE(Quorum);
    VERIFY_WITH_LOG(SendAttemptsCount >= 2, "SendAttemptsCount must be greater then 2 (2 recommend). %d now for service %s", SendAttemptsCount, serviceName.data());
    GET_VALUE(MaxInFlightForInstant);

    GET_VALUE(DeferredMessagesLifetime);
    VERIFY_WITH_LOG(TDuration::TryParse(DeferredMessagesLifetime, DeferredMessagesLifetimeDuration),
        "Failed to parse DeferredMessagesLifetime %s for service %s", DeferredMessagesLifetime.data(), serviceName.data());

    GET_VALUE(InteractionTimeout);
    VERIFY_WITH_LOG(TDuration::TryParse(InteractionTimeout, InteractionTimeoutDuration),
        "Failed to parse InteractionTimeout %s for service %s", InteractionTimeout.data(), serviceName.data());

    GET_VALUE(ConnectionTimeout);
    VERIFY_WITH_LOG(TDuration::TryParse(ConnectionTimeout, ConnectionTimeoutDuration),
        "Failed to parse ConnectionTimeout %s for service %s", ConnectionTimeout.data(), serviceName.data());

    TString distributionMetrics;
    GET_VALUE(distributionMetrics);
    SplitString(distributionMetrics.data(), distributionMetrics.data() + distributionMetrics.size(), TCharDelimiter<const char>(','),
                                TContainerConsumer<TSet<TString>>(&DistributionMetrics));

    TYandexConfig::TSectionsMap::const_iterator tvmSection = sections.find("TVM");
    if (tvmSection != sections.end()) {
        TvmConfig.ConstructInPlace().Init(*tvmSection->second);
    }

    ServiceName = serviceName;
}

TString TProxyServiceConfig::ToString() const {
    TStringStream ss;
    ss << "<" << ServiceName << ">" << Endl;
    ss << "Realtimable : " << Realtimable << Endl;
    ss << "SessionPrefix : " << SessionPrefix << Endl;
    ss << "IndexationDisabled : " << IndexationDisabled << Endl;
    ss << "DeferredQueueEnabled : " << DeferredQueueEnabled << Endl;
    ss << "DeferredMessagesLifetime : " << DeferredMessagesLifetime << Endl;
    ss << "InteractionTimeout : " << InteractionTimeout << Endl;
    ss << "ConnectionTimeout : " << ConnectionTimeout << Endl;
    ss << "SendAttemptsCount : " << SendAttemptsCount << Endl;
    ss << "MaxInFlightForInstant : " << MaxInFlightForInstant << Endl;
    ss << "SpaceLimitInMBytes : " << SpaceLimitInMBytes << Endl;
    ss << "MaxDocsInWorkPerBackend : " << MaxDocsInWorkPerBackend << Endl;
    ss << "QueuePartSizeLimit : " << QueuePartSizeLimit << Endl;
    ss << "DistibutionMetrics : " << JoinStrings(DistributionMetrics.begin(), DistributionMetrics.end(), TStringBuf(",")) << Endl;
    ss << "LoggingEnabled : " << LoggingEnabled << Endl;
    ss << "Quorum : " << Quorum << Endl;
    if (TvmConfig) {
        ss << TvmConfig->ToString("TVM") << Endl;
    }
    ss << "</" << ServiceName << ">" << Endl;
    return ss.Str();
}

void TProxyServicesConfig::Init(const TYandexConfig::Section& configSection) {
    TYandexConfig::TSectionsMap sections = configSection.GetAllChildren();
    TYandexConfig::TSectionsMap::const_iterator defIter = sections.find("default");
    if (defIter != sections.end())
        DefaultConfig.Init(defIter->first, *defIter->second);
    for (auto&& section : sections) {
        auto insertResult = Configs.insert(std::make_pair(section.first, DefaultConfig));
        AssertCorrectConfig(insertResult.second, "Service configuration duplication for %s", section.first.data());
        insertResult.first->second.Init(section.first, *section.second);
    }
    Configs["default"] = DefaultConfig;
}

void TProxyConfig::InitFromString(const char* configText) {
    if(!Impl->ParseMemory(configText)) {
        ythrow yexception() << "Bad config text";
    }

    TYandexConfig::Section* proxySection = Impl->GetFirstChild("Proxy");
    VERIFY_WITH_LOG(proxySection != nullptr, "No Proxy section");
    TYandexConfig::TSectionsMap sections = proxySection->GetAllChildren();

    Init(proxySection->GetDirectives());

    HttpServerInstances = proxySection->GetDirectives().Value("HttpServerInstances", 1);

    TYandexConfig::TSectionsMap::const_iterator iter = sections.find("HttpOptions");
    VERIFY_WITH_LOG(iter != sections.end(), "No HttpOptions section");
    HttpOptionsConfig.Init(iter->second->GetDirectives());
    if (!iter->second->GetDirectives().contains("KeepAliveEnabled")) {
        HttpOptionsConfig.KeepAliveEnabled = false;
    }
    if (HttpServerInstances > 1) {
        HttpOptionsConfig.EnableReusePort(true);
    }

    TYandexConfig::TSectionsMap::const_iterator iterServices = sections.find("Services");
    if (iterServices != sections.end()) {
        ServicesConfig.Init(*iterServices->second);
    }

    TYandexConfig::TSectionsMap::const_iterator iterDisp = sections.find("Dispatcher");
    VERIFY_WITH_LOG(iterDisp != sections.end(), "No Dispatcher section");
    DispatcherConfig.Init(*iterDisp->second);

    TYandexConfig::TSectionsMap::const_iterator iterExport = sections.find("Export");
    if (iterExport != sections.end())
        ExportConfig.Init(iterExport->second->GetDirectives());

    TYandexConfig::TSectionsMap::const_iterator iterFlowMirror = sections.find("FlowMirror");
    if (iterFlowMirror != sections.end())
        FlowMirrorConfig.Init(iterFlowMirror->second->GetDirectives());

    for (TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Adapter");
        iter != sections.end() && iter->first == "Adapter"; ++iter) {
        Adapters.push_back(TAdapterConfig(*this));
        Adapters.back().Init(iter->second->GetDirectives());

        for (auto&& srv : Services) {
            DecodeHashToInfo[MD5::Calc(srv + " " + Adapters.back().SecretCode)] = THashInfoStructure(srv, Adapters.back().Name);
        }

    }
    VERIFY_WITH_LOG(Adapters.size(), "No defined adapters configuration");
}

void TProxyConfig::Init(const TYandexConfig::Directives& directives) {
    IndexLog = directives.Value<TString>("IndexLog", "");
    DaemonConfig.StartLogging<TRTYIprLog>(IndexLog);

    AuthKeyWord = directives.Value<TString>("AuthKeyWord", "");
    TString watchdogOptionsFiles = directives.Value<TString>("WatchdogOptionsFile", "");
    StringSplitter(watchdogOptionsFiles).Split(' ').AddTo(&WatchdogOptionsFiles);

    DumpFile = directives.Value<TString>("DumpFile", "");
    DumpServices = directives.Value<TString>("DumpServices", "");
    SearchMapFile = directives.Value<TString>("SearchMapFile", "");
    SenderThreads = directives.Value<ui32>("SenderThreads", 20);
    UseSmartQueues = directives.Value<bool>("UseSmartQueues", true);
    VERIFY_WITH_LOG(NFs::Exists(SearchMapFile), "Incorrect searchmap path %s", SearchMapFile.data());
    SearchMapParser.Reset(NSearchMapParser::OpenSearchMap(Preprocessor.GetPreprocessor()->ReadAndProcess(SearchMapFile)));
    {
        SearchMap = SearchMapParser->GetSearchMap();
        const auto& pureServiceMap = SearchMap.GetServiceMap();
        Copy(pureServiceMap.begin(), pureServiceMap.end(), inserter(ServiceMap, ServiceMap.end()));
        for (auto&& i : SearchMap.GetMetaServices()) {
            CHECK_WITH_LOG(Services.insert(i.Name).second);
        }
        for (auto&& i : pureServiceMap) {
            CHECK_WITH_LOG(Services.insert(i.first).second);
        }
    }
}

void TAdapterConfig::Init(const TYandexConfig::Directives& directives) {
    GET_VALUE(Name);
    GET_VALUE(SecretCode);
    VERIFY_WITH_LOG(!!Name, "Adapter.Name is empty");
}

void TExportConfig::Init(const TYandexConfig::Directives& directives) {
    GET_VALUE(TupleParsingOptions);
    GET_VALUE(User);
    GET_VALUE(LogFile);
    GET_VALUE(ResendAttempts);
    GET_VALUE(Port);
    GET_VALUE(Enabled);
    GET_VALUE(Threads);
    GET_VALUE(VerboseLogging);

    GET_VALUE(MaxInFlight);
    GET_VALUE(MaxInBytes);
    GET_VALUE(MaxTimeout);
}

void TFlowMirrorConfig::Init(const TYandexConfig::Directives& directives) {
    GET_VALUE(Enabled);
    GET_VALUE(Host);
    GET_VALUE(Port);;
    GET_VALUE(Threads);
    GET_VALUE(Fraction);
    GET_VALUE(CopyHeaders);
}

const NSearchMapParser::TServiceSpecificOptions* TProxyConfig::GetServiceInfo(const TString& serviceName) const {
    if (SearchMap.GetServiceMap().contains(serviceName)) {
        return &SearchMap.GetServiceMap().find(serviceName)->second;
    } else {
        auto* meta = SearchMap.GetMetaService(serviceName);
        return meta;
    }
    return nullptr;
}

TString TProxyConfig::ToString() const {
    TString s;
    TStringOutput so(s);
    so << DaemonConfig.ToString("DaemonConfig") << Endl;
    so << "<Proxy>" << Endl;
    so << "HttpServerInstances : " << HttpServerInstances << Endl;
    so << HttpOptionsConfig.ToString("HttpOptions");
    for (unsigned i = 0; i < Adapters.size(); i++) {
        so << Adapters[i].ToString() << Endl;
    }
    so << "SenderThreads : " << SenderThreads << Endl;
    so << "IndexLog : " << IndexLog << Endl;
    so << "SearchMapFile : " << SearchMapFile << Endl;
    so << "AuthKeyWord : " << AuthKeyWord << Endl;
    so << "WatchdogOptionsFile : " << JoinVectorIntoString(WatchdogOptionsFiles, " ") << Endl;
    so << "DumpFile : " << DumpFile << Endl;
    so << "DumpServices : " << DumpServices << Endl;
    so << "UseSmartQueues : " << UseSmartQueues << Endl;
    so << DispatcherConfig.ToString() << Endl;
    so << ExportConfig.ToString() << Endl;
    so << ServicesConfig.ToString() << Endl;
    so << "</Proxy>" << Endl;
    return s;
}

TString TAdapterConfig::ToString() const {
    TString s;
    TStringOutput so(s);
    so << "<Adapter>" << Endl;
    so << "Name : " << Name << Endl;
    so << "SecretCode : " << SecretCode << Endl;
    so << "</Adapter>" << Endl;
    return s;
}

TString TExportConfig::ToString() const {
    TStringStream ss;
    ss << "<Export>" << Endl;
    ss << "User : " << User << Endl;
    ss << "LogFile : " << LogFile << Endl;
    ss << "Threads : " << Threads << Endl;
    ss << "ResendAttempts : " << ResendAttempts << Endl;
    ss << "Enabled : " << Enabled << Endl;
    ss << "VerboseLogging : " << VerboseLogging << Endl;
    ss << "</Export>" << Endl;
    return ss.Str();
}

TString TFlowMirrorConfig::ToString() const {
    TStringStream ss;
    ss << "<FlowMirror>" << Endl;
    ss << "Enabled : " << Enabled << Endl;
    ss << "Host : " << Host << Endl;
    ss << "Port : " << Port << Endl;
    ss << "Threads : " << Threads << Endl;
    ss << "Fraction : " << Fraction << Endl;
    ss << "CopyHeaders : " << CopyHeaders << Endl;
    ss << "</FlowMirror>" << Endl;
    return ss.Str();
}

TProxyWatchdogSubsciber::TProxyWatchdogSubsciber()
    : MinRank(std::numeric_limits<double>::lowest())
{
}

void TProxyWatchdogSubsciber::OnWatchdogOption(const TString& key, const TString& value) {
    if (key == "refresh.iproxy.rank_threshold") {
        if (value == "paused")
            ythrow (yexception() << "'paused' is not supported in indexer_proxy");
        else if (!value)
            MinRank = std::numeric_limits<double>::lowest();
        else
            MinRank = FromString<double>(value);
    }
}
