#include "config.h"

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

#include <saas/util/external/dc.h>

#include <library/cpp/charset/codepage.h>
#include <library/cpp/html/face/parsface.h>
#include <library/cpp/logger/global/global.h>

#include <util/folder/dirut.h>
#include <util/stream/file.h>
#include <util/system/fs.h>
#include <util/system/env.h>
#include <util/string/split.h>

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

TDeployManagerConfig::TDeployManagerConfig(const TServerConfigConstructorParams& params)
    : DaemonConfig(*params.Daemon)
    , TimeCacheLive(TDuration::Minutes(10))
    , ExecuteScriptThreads(16)
    , TasksExecutorThreads(8)
    , ServiceQueueThreads(32)
{
    InitFromString(params.Text.data());
}

void TDeployManagerConfig::InitFromString(const char* configText) {
    TAnyYandexConfig deployManagerConfig;
    CHECK_WITH_LOG(deployManagerConfig.ParseMemory(configText));

    TYandexConfig::Section* managerSection = deployManagerConfig.GetFirstChild("DeployManager");
    CHECK_WITH_LOG(managerSection);
    TYandexConfig::TSectionsMap sections = managerSection->GetAllChildren();

    Init(managerSection->GetDirectives());

    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("HttpOptions");
        VERIFY_WITH_LOG(iter != sections.end(), "No HttpOptions section");
        HttpOptionsConfig.Init(iter->second->GetDirectives(), false);
        HttpOptionsConfig.KeepAliveEnabled = false;
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("DeployManagerBalanser");
        VERIFY_WITH_LOG(iter != sections.end(), "No DeployManagerBalanser section");
        DeployManagerBalanser.Init(iter->second->GetDirectives(), false);
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Storage");
        VERIFY_WITH_LOG(iter != sections.end(), "No Storage section");
        StorageOptions.Init(*iter->second);
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Nanny");
        if (iter != sections.end())
            Nanny.Init(iter->second->GetDirectives());
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("GolemApi");
        if (iter != sections.end())
            GolemApi.Init(iter->second->GetDirectives());
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Services20");
        if (iter != sections.end())
            Services20Config.Init(*iter->second);
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Alerts");
        if (iter != sections.end())
            AlertsConfig.Init(*iter->second);
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("WatchDog");
        if (iter != sections.end())
            WatchDog.Init(iter->second->GetDirectives());
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Watcher");
        if (iter != sections.end())
            Watcher.Init(iter->second->GetDirectives());
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Experiments");
        if (iter != sections.end())
            ExperimentsConfig.Init(iter->second->GetDirectives());
    }
    {
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find("Checker");
        if (iter != sections.end())
            Checker.Init(iter->second->GetDirectives());
    }
    {
        TString rwc;
        managerSection->GetDirectives().GetValue("RequestWizardConfig", rwc);
        RequestWizardConfig.Init(rwc);
    }
}

void TDeployManagerConfig::Init(const TYandexConfig::Directives& directives) {
    directives.GetValue("PrivateKey", PrivateKey);
    directives.GetValue("TimeCacheLive", TimeCacheLive);
    directives.GetValue("ExecuteScriptThreads", ExecuteScriptThreads);
    directives.GetValue("TasksExecutorThreads", TasksExecutorThreads);
    directives.GetValue("ServiceQueueThreads", ServiceQueueThreads);
    directives.GetValue("ServiceDiscoveryOptions", ServiceDiscoveryOptions);
    //    VERIFY_WITH_LOG(directives.GetValue("SVNPath", SVNPath), "Incorrect SVNPath");
    directives.GetValue("NotifyAddress", NotifyAddress);
    directives.GetValue("UseExecuteScriptInDeploy", UseExecuteScriptInDeploy);
    directives.GetValue("RequiredUriPrefix", RequiredUriPrefix);
    if (!!RequiredUriPrefix) {
        if (!RequiredUriPrefix.EndsWith('/'))
            RequiredUriPrefix += "/";
        if (!RequiredUriPrefix.StartsWith('/'))
            RequiredUriPrefix = "/" + RequiredUriPrefix;
    }

    directives.GetValue("RegularCommandsFile", RegularCommandsFile);
    if (!!RegularCommandsFile) {
        CHECK_WITH_LOG(TFsPath(RegularCommandsFile).Exists());
        TUnbufferedFileInput fi(RegularCommandsFile);
        TString jsonText = fi.ReadAll();
        NJson::TJsonValue json;
        CHECK_WITH_LOG(NJson::ReadJsonFastTree(jsonText, &json));
        auto actions = json["actions"];
        NJson::TJsonValue::TArray arrActions;
        CHECK_WITH_LOG(actions.GetArray(&arrActions));
        for (auto&& i : arrActions) {
            TRegularCommandInfo commandInfo(i);
            RegularCommands.push_back(commandInfo);
        }
    }
}

TString TDeployManagerConfig::ToString() const {
    TString s;
    TStringOutput so(s);
    so << DaemonConfig.ToString("DaemonConfig") << Endl;
    so << "<DeployManager>" << Endl;
    so << HttpOptionsConfig.ToString("HttpOptions");
    so << DeployManagerBalanser.ToString("DeployManagerBalanser");
    so << GolemApi.ToString();
    so << WatchDog.ToString();
    so << Watcher.ToString();
    so << Nanny.ToString();
    so << Services20Config.ToString();
    so << AlertsConfig.ToString();
    so << ExperimentsConfig.ToString();
    so << Checker.ToString();
    so << StorageOptions.ToString();
    so << "PrivateKey: " << PrivateKey << Endl;
    so << "ServiceDiscoveryOptions: " << ServiceDiscoveryOptions << Endl;
    so << "TimeCacheLive: " << TimeCacheLive << Endl;
    so << "ExecuteScriptThreads: " << ExecuteScriptThreads << Endl;
    so << "TasksExecutorThreads: " << TasksExecutorThreads << Endl;
    so << "ServiceQueueThreads: " << ServiceQueueThreads << Endl;
    so << "NotifyAddress: " << NotifyAddress << Endl;
    so << "UseExecuteScriptInDeploy: " << UseExecuteScriptInDeploy << Endl;
    so << "RequestWizardConfig: " << RequestWizardConfig.ToString() << Endl;
    so << "RequiredUriPrefix: " << RequiredUriPrefix << Endl;
    so << "RegularCommandsFile: " << RegularCommandsFile << Endl;
    so << "</DeployManager>" << Endl;
    return s;
}

TDeployManagerConfig::TGolemApiConfig::TGolemApiConfig()
: Uri("/api/get_host_dc.sbml?host=")
{
    Host = "ro.admin.yandex-team.ru";
}

void TDeployManagerConfig::TGolemApiConfig::Init(const TYandexConfig::Directives& directives) {
    directives.GetValue("Host", Host);
    directives.GetValue("Aliases", AliasesString);
    directives.GetValue("Uri", Uri);

    for (auto&& alias : static_cast<TVector<TString>>(StringSplitter(AliasesString).SplitBySet(" ,").SkipEmpty())) {
        auto splitted = SplitString(alias.data(), "=");
        if (splitted.size() != 2) {
            ERROR_LOG << "Incorrect alias description " << alias << Endl;
            continue;
        }

        Aliases[splitted[0]] = splitted[1];
        Aliases[splitted[1]] = splitted[1];
    }

    TDatacenterUtil::Instance().SetApiHost(Host);
    TDatacenterUtil::Instance().SetApiUrl(Uri);
    for (auto&& aliasInfo : Aliases) {
        TDatacenterUtil::Instance().AddDatacenterAlias(aliasInfo.first, aliasInfo.second);
    }
}

TString TDeployManagerConfig::TGolemApiConfig::ToString() const {
    TStringStream ss;
    ss << "<GolemApi>" << Endl;
    ss << "Aliases :" << AliasesString << Endl;
    ss << "Host :" << Host << Endl;
    ss << "Port :" << Port << Endl;
    ss << "Uri :" << Uri << Endl;
    ss << "</GolemApi>" << Endl;
    return ss.Str();
}

TDeployManagerConfig::TServices20Config::TServices20Config() {
    Host = "nanny.yandex-team.ru";
    Port = 80;
    Uri = "/v2/";
}

void TDeployManagerConfig::TServices20Config::Init(const TYandexConfig::Section& section) {
    Init(section.GetDirectives());
    TYandexConfig::TSectionsMap children = section.GetAllChildren();
    TYandexConfig::TSectionsMap::const_iterator i = children.find("ClustersByCTypes");
    if (i != children.end()) {
        TYandexConfig::TSectionsMap ctypes = i->second->GetAllChildren();
        for (const auto& ctype : ctypes)
            for (const auto& dir : ctype.second->GetDirectives())
                ClustersByCType[ctype.first][dir.first] = dir.second;
    }
}

void TDeployManagerConfig::TServices20Config::Init(const TYandexConfig::Directives& directives) {
    directives.GetValue("Host", Host);
    directives.GetValue("Port", Port);
    TString timeout;
    if (directives.GetValue("ConnectionTimeout", timeout))
        ConnectionTimeout = TDuration::Parse(timeout);
    directives.GetValue("AuthKeyPath", AuthKeyPath);
    directives.GetValue("Uri", Uri);
    if (!Uri || Uri.back() != '/')
        Uri.append('/');
    VERIFY_WITH_LOG(!AuthKeyPath.IsDefined() || AuthKeyPath.Exists(), "AuthKeyPath does not exists");
}

TString TDeployManagerConfig::TServices20Config::ToString() const {
    TStringStream ss;
    ss << "<Services20>" << Endl;
    ss << "Host :" << Host << Endl;
    ss << "Port :" << Port << Endl;
    ss << "ConnectionTimeout :" << ConnectionTimeout.MilliSeconds() << "ms" << Endl;
    ss << "Uri :" << Uri << Endl;
    ss << "AuthKeyPath :" << AuthKeyPath << Endl;
    ss << "<ClustersByCTypes>" << Endl;
    for (const auto& ctype : ClustersByCType) {
        ss << "<" << ctype.first << ">" << Endl;
            for (const auto& c : ctype.second)
                ss << c.first << " :" << c.second << Endl;
        ss << "</" << ctype.first << ">" << Endl;
    }
    ss << "</ClustersByCTypes>" << Endl;
    ss << "</Services20>" << Endl;
    return ss.Str();
}


TDeployManagerConfig::TWatchDogConfig::TWatchDogConfig()
    : MaxMemoryCount(1024 * 1024 * 1024)
    , Enabled(false)
{}

void TDeployManagerConfig::TWatchDogConfig::Init(const TYandexConfig::Directives& directives) {
    directives.GetValue("Enabled", Enabled);
    directives.GetValue("MaxMemoryCount", MaxMemoryCount);
}

TString TDeployManagerConfig::TWatchDogConfig::ToString() const {
    TStringStream ss;
    ss << "<WatchDog>" << Endl;
    ss << "Enabled :" << Enabled << Endl;
    ss << "MaxMemoryCount :" << MaxMemoryCount << Endl;
    ss << "</WatchDog>" << Endl;
    return ss.Str();
}


TDeployManagerConfig::TWatcherConfig::TWatcherConfig()
    : Enabled(false)
    , Period(TDuration::Seconds(10))
    , KeepDays(2)
{}

void TDeployManagerConfig::TWatcherConfig::Init(const TYandexConfig::Directives& directives) {
    directives.GetValue("Enabled", Enabled);
    directives.GetValue("KeepDays", KeepDays);
    TString periondStr;
    directives.GetValue("Period", periondStr);
    if (!!periondStr)
        Period = TDuration::Parse(periondStr);
}

TString TDeployManagerConfig::TWatcherConfig::ToString() const {
    TStringStream ss;
    ss << "<Watcher>" << Endl;
    ss << "Enabled :" << Enabled << Endl;
    ss << "Period :" << Period.ToString() << Endl;
    ss << "KeepDays :" << KeepDays << Endl;
    ss << "</Watcher>" << Endl;
    return ss.Str();
}

void TDeployManagerConfig::TNannyConfig::Init(const TYandexConfig::Directives& directives) {
    directives.GetValue("Host", Host);
    directives.GetValue("Port", Port);
    directives.GetValue("RequestSlotsByServiceTemplate", RequestSlotsByServiceTemplate);
    directives.GetValue("RequestSlotsByTagTemplate", RequestSlotsByTagTemplate);
    directives.GetValue("RequestRuntimeAttrsByTagTemplate", RequestRuntimeAttrsByTagTemplate);
    directives.GetValue("InterSendTimeout", InterSendTimeout);
    directives.GetValue("CacheDir", CacheDir);
    directives.GetValue("CacheTestMode", CacheTestMode);
    directives.GetValue("CacheFallbackErrorsCnt", CacheFallbackErrorsCnt);
    directives.GetValue("CacheFallbackUndoMinutes", CacheFallbackUndoMinutes);
    directives.GetValue("TokenEnv", TokenEnv);
    Token = GetEnv(TokenEnv);
    if (!Token)
        ERROR_LOG << "No Nanny token in " << TokenEnv << " environment variable" << Endl;
}

TString TDeployManagerConfig::TNannyConfig::ToString() const {
    TStringStream ss;
    ss << "<Nanny>" << Endl;
    ss << "Host:" << Host << Endl;
    ss << "Port:" << Port << Endl;
    ss << "RequestSlotsByServiceTemplate:" << RequestSlotsByServiceTemplate << Endl;
    ss << "RequestSlotsByTagTemplate:" << RequestSlotsByTagTemplate << Endl;
    ss << "RequestRuntimeAttrsByTagTemplate:" << RequestRuntimeAttrsByTagTemplate << Endl;
    ss << "InterSendTimeout:" << InterSendTimeout << Endl;
    ss << "CacheDir:" << CacheDir << Endl;
    ss << "CacheTestMode:" << CacheTestMode << Endl;
    ss << "CacheFallbackErrorsCnt:" << CacheFallbackErrorsCnt << Endl;
    ss << "CacheFallbackUndoMinutes:" << CacheFallbackUndoMinutes << Endl;
    ss << "TokenEnv:" << TokenEnv << Endl;
    ss << "</Nanny>" << Endl;
    return ss.Str();
}

bool TDeployManagerConfig::TNannyConfig::IsInitialized() const {
    return !!Token;
}

TDeployManagerConfig::TJugglerConfig::TJugglerConfig()
    : CommonTag("")
    , Namespace("search.saas")
    , Token("")
    , IsTesting(false) {
};

void TDeployManagerConfig::TJugglerConfig::Init(const TYandexConfig::Directives& directives, bool verifyThreads) {
   TDaemonConfig::THttpOptions::Init(directives, verifyThreads);
   directives.GetValue("CommonTag", CommonTag);
   directives.GetValue("Namespace", Namespace);
   directives.GetValue("Token", Token);
   directives.GetValue("IsTesting", IsTesting);
   directives.GetValue("TokenEnv", TokenEnv);
   if (TokenEnv) {
        Token = GetEnv(TokenEnv);
        if (!Token) {
            ERROR_LOG << "No juggler token in '" << TokenEnv << "' environment variable" << Endl;
        }
   }
}

void TDeployManagerConfig::TJugglerConfig::Verify() const {
   VERIFY_WITH_LOG(!!CommonTag, "Empty common tag for juggler");
   VERIFY_WITH_LOG(!!Namespace, "Empty namespace for juggler");
   VERIFY_WITH_LOG(!!Token, "Empty juggler oauth token");
   VERIFY_WITH_LOG(!!Host, "Empty juggler host");
}

void TDeployManagerConfig::TGolovanConfig::Verify() const {
   VERIFY_WITH_LOG(!!AlertsPrefix, "Empty alerts prefix for golovan");
   VERIFY_WITH_LOG(!!Host, "Empty golovan host");
}

TString TDeployManagerConfig::TJugglerConfig::ToString() const {
    TStringStream ss;
    ss << "<Juggler>" << Endl;
    ss << "CommonTag : " << CommonTag << Endl;
    ss << "Namespace : " << Namespace << Endl;
    ss << "TokenEnv : " << TokenEnv << Endl;
    if (!TokenEnv) {
        ss << "Token : " << Token << Endl;
    }
    TDaemonConfig::THttpOptions::ToStringImpl(ss);
    ss << "IsTesting : " << IsTesting << Endl;
    ss << "</Juggler>" << Endl;
    return ss.Str();
}

TDeployManagerConfig::TGolovanConfig::TGolovanConfig()
    : AlertsPrefix("saas.gen") {
};

void TDeployManagerConfig::TGolovanConfig::Init(const TYandexConfig::Directives& directives, bool verifyThreads) {
   TDaemonConfig::THttpOptions::Init(directives, verifyThreads);
   directives.GetValue("AlertsPrefix", AlertsPrefix);
}

TString TDeployManagerConfig::TGolovanConfig::ToString() const {
    TStringStream ss;
    ss << "<Golovan>" << Endl;
    ss << "AlertsPrefix : " << AlertsPrefix << Endl;
    TDaemonConfig::THttpOptions::ToStringImpl(ss);
    ss << "</Golovan>" << Endl;
    return ss.Str();
}

TDeployManagerConfig::TAlertsConfig::TAlertsConfig()
    : Enable(false)
    , CtypesPrefix("stable")
{
}

void TDeployManagerConfig::TAlertsConfig::Verify() const {
    if (!Enable) {
        return;
    }
    VERIFY_WITH_LOG(!!CtypesPrefix, "Empty alerts ctypes filter");
    Juggler.Verify();
    Golovan.Verify();
}

void TDeployManagerConfig::TAlertsConfig::Init(const TYandexConfig::Section& section) {
    Init(section.GetDirectives());
    TYandexConfig::TSectionsMap children = section.GetAllChildren();
    TYandexConfig::TSectionsMap::const_iterator i = children.find("Juggler");
    if (i != children.end()) {
        Juggler.Init(i->second->GetDirectives(), false);
    }
    i = children.find("Golovan");
    if (i != children.end()) {
        Golovan.Init(i->second->GetDirectives(), false);
    }
    Verify();
}

void TDeployManagerConfig::TAlertsConfig::Init(const TYandexConfig::Directives& directives) {
    directives.GetValue("Enable", Enable);
    directives.GetValue("CtypesPrefix", CtypesPrefix);
}

TString TDeployManagerConfig::TAlertsConfig::ToString() const {
    TStringStream ss;
    ss << "<Alerts>" << Endl;
    ss << "Enable : " << Enable << Endl;
    ss << "CtypesPrefix : " << CtypesPrefix << Endl;
    ss << Juggler.ToString() << Endl;
    ss << Golovan.ToString() << Endl;
    ss << "</Alerts>" << Endl;
    return ss.Str();
}

TDeployManagerConfig::TCheckerConfig::TCheckerConfig()
    : HoursForCheck(3)
    , PercentForCheck(50)
    , AttemptsPing(2)
    , TimeoutPing(100)
    , CheckerThreads(10)
    , CacheValidityPeriodSeconds(10)
{}

void TDeployManagerConfig::TCheckerConfig::Init(const TYandexConfig::Directives& directives) {
    directives.GetValue("HoursForCheck", HoursForCheck);
    directives.GetValue("PercentForCheck", PercentForCheck);
    directives.GetValue("AttemptsPing", AttemptsPing);
    directives.GetValue("TimeoutPing", TimeoutPing);
    directives.GetValue("CheckerThreads", CheckerThreads);
    directives.GetValue("Persist", Persist);
    directives.GetValue("Enabled", Enabled);
    directives.GetValue("CacheValidityPeriodSeconds", CacheValidityPeriodSeconds);
    CHECK_WITH_LOG(PercentForCheck <= 100);
}

TString TDeployManagerConfig::TCheckerConfig::ToString() const {
    TStringStream ss;
    ss << "<Checker>" << Endl;
    ss << "HoursForCheck :" << HoursForCheck << Endl;
    ss << "PercentForCheck :" << PercentForCheck << Endl;
    ss << "AttemptsPing :" << AttemptsPing << Endl;
    ss << "TimeoutPing :" << TimeoutPing << Endl;
    ss << "CheckerThreads :" << CheckerThreads << Endl;
    ss << "Persist :" << Persist << Endl;
    ss << "Enabled" << Enabled << Endl;
    ss << "CacheValidityPeriodSeconds :" << CacheValidityPeriodSeconds << Endl;
    ss << "</Checker>" << Endl;
    return ss.Str();
}

TDeployManagerConfig::TExperimentsConfig::TExperimentsConfig()
    : ConfigUrl("http://exp.yandex-team.ru/report/config/-WEB/yandex/production")
    , ConfigPath("/configs/searchproxy/experiments.xml")
{}

void TDeployManagerConfig::TExperimentsConfig::Init(const TYandexConfig::Directives& directives) {
    directives.GetValue("ConfigUrl", ConfigUrl);
    directives.GetValue("ConfigPath", ConfigPath);
}

TString TDeployManagerConfig::TExperimentsConfig::ToString() const {
    TStringStream ss;
    ss << "<Experiments>" << Endl;
    ss << "ConfigUrl :" << ConfigUrl << Endl;
    ss << "ConfigPath :" << ConfigPath << Endl;
    ss << "</Experiments>" << Endl;
    return ss.Str();
}

void TDeployManagerConfig::TDmBalanser::Init(const TYandexConfig::Directives& directives, bool verifyThreads) {
    TDaemonConfig::THttpOptions::Init(directives, verifyThreads);
    directives.GetValue("UriPrefix", UriPrefix);
    if (!!UriPrefix && !UriPrefix.EndsWith('/'))
        UriPrefix += "/";
}

void TDeployManagerConfig::TDmBalanser::ToStringImpl(TStringStream& so) const {
    TDaemonConfig::THttpOptions::ToStringImpl(so);
    so << "UriPrefix: " << UriPrefix << Endl;
}

