#include "realm_config.h"

#include "common_indexers_config.h"
#include "config.h"
#include "get_value.h"
#include "indexer_config.h"
#include "merger_config.h"

#include <robot/library/oxygen/indexer/processor/protos/config.pb.h>

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

#include <util/string/printf.h>

namespace NRTYServer {

TRealmListConfig::TRealmListConfig(const TRTYServerConfig& owner)
    : Owner(owner)
    , CloseThreads(1)
    , AutoGenerated(true)
{}

ui32 TRealmListConfig::GetNumberDiskRealms() const {
    ui32 cnt = 0;
    for (auto&& [realmName, realmConfig] : RealmsConfig) {
        cnt += (realmConfig.StorageType == NRTYServer::EStorageType::Disk);
    }
    return cnt;
}

const TRealmConfig& TRealmListConfig::GetRealmConfigByConfigName(const TString& realmConfigName) const {
    for (auto&& [realmName, realmConfig] : RealmsConfig) {
        if (realmConfigName == realmConfig.ConfigName) {
            return realmConfig;
        }
    }
    ythrow yexception() << "No realm found with config name: " << realmConfigName;
}

const TRealmConfig& TRealmListConfig::GetRealmConfig(const TFsPath& indexDir) const {
    for (auto&& [realmName, realmConfig] : RealmsConfig) {
        if (realmConfig.StorageType == NRTYServer::EStorageType::Memory) {
            continue;
        }
        if (indexDir.IsNonStrictSubpathOf(realmConfig.RealmDirectory)) {
            return realmConfig;
        }
    }
    ERROR_LOG << "Realm for " << indexDir << "wasn't found returning main config" << Endl;
    Y_ENSURE(RealmsConfig.size(), "RealmsList is empty");
    return RealmsConfig.begin()->second;
}

TVector<TString> TRealmListConfig::GetRealmConfigNames() const {
    TVector<TString> realmConfigNames;
    for (auto&& [realmName, realmConfig] : RealmsConfig) {
        realmConfigNames.push_back(realmConfig.ConfigName);
    }
    return realmConfigNames;
}

const TRealmConfig& TRealmListConfig::GetMainRealmConfig() const {
    return RealmsConfig.begin()->second;
}

void TRealmListConfig::UpdateIndexersPtr() {
    for (auto&& [realmName, realmConfig] : RealmsConfig) {
        realmConfig.UpdateIndexersPtr();
    }
}

void TRealmListConfig::InitDefaultConfig(const TYandexConfig::Section* indexerSection, const TYandexConfig::Section* mergerSection) {
    AutoGenerated = true;
    CloseThreads = Owner.IndexerDiskConfig->CloseThreads;
    TYandexConfig::TSectionsMap indexerSections = indexerSection->GetAllChildren();

    TRealmConfig realmConfigDisk(Owner);

    realmConfigDisk.IndexingOrder = 1;
    realmConfigDisk.ConfigName = "Persistent";
    realmConfigDisk.Name = TRealmConfig::MakeRealmName(realmConfigDisk.IndexingOrder, realmConfigDisk.ConfigName);
    TString name = realmConfigDisk.Name;
    realmConfigDisk.Type = NRTYServer::ERealm::Persistent;
    realmConfigDisk.SearchersCountLimit = Owner.SearchersCountLimit;
    realmConfigDisk.StorageType = NRTYServer::EStorageType::Disk;
    realmConfigDisk.RealmDirectory = Owner.IndexDir;
    realmConfigDisk.ClearOnStart = false;
    realmConfigDisk.OxygenOptionsFile = Owner.CommonIndexers->OxygenOptionsFile;
    realmConfigDisk.OxygenOptions = ::GetOxygenOptions(realmConfigDisk.OxygenOptionsFile, Owner.ConfigPatcher ? Owner.ConfigPatcher->GetPreprocessor() : nullptr);

    TYandexConfig::TSectionsMap::const_iterator indIter = indexerSections.find("Disk");
    AssertCorrectConfig(indIter != indexerSections.end(), "There is no Disk default section in Realm indexer config");
    realmConfigDisk.IndexerConfigDisk->Init(indIter->second);
    ui32 diskMaxDocuments = realmConfigDisk.IndexerConfigDisk->MaxDocuments;
    ui32 diskThreads = realmConfigDisk.IndexerConfigDisk->Threads;
    TString sod = realmConfigDisk.IndexerConfigDisk->SearchObjectsDirectory;

    realmConfigDisk.MergerConfig->Init(*mergerSection);

    AssertCorrectConfig(RealmsConfig.emplace(name, std::move(realmConfigDisk)).second, "Multiple Realms with same name");

    TRealmConfig realmConfigMemory(Owner);
    if (Owner.IndexerMemoryConfig->Enabled) {
        realmConfigMemory.IndexingOrder = 3;
        realmConfigMemory.ConfigName = "Realtime";
        realmConfigMemory.Name = TRealmConfig::MakeRealmName(realmConfigMemory.IndexingOrder, realmConfigMemory.ConfigName);
        name = realmConfigMemory.Name;
        realmConfigMemory.Type = NRTYServer::ERealm::Realtime;
        realmConfigMemory.SearchersCountLimit = Owner.SearchersCountLimit;
        realmConfigMemory.StorageType = NRTYServer::EStorageType::Memory;

        indIter = indexerSections.find("Memory");
        AssertCorrectConfig(indIter != indexerSections.end(), "There is no Memory default section in Realm indexer config");
        realmConfigMemory.IndexerConfigMemory->Init(indIter->second);
        realmConfigMemory.IndexerConfigMemory->MaxDocuments = Max<ui32>(500, diskMaxDocuments * realmConfigMemory.IndexerConfigMemory->MaxDocumentsReserveCapacityCoeff);

        realmConfigMemory.RealmDirectory = realmConfigMemory.IndexerConfigMemory->GetDirectory();
        realmConfigMemory.MergerConfig->Init(*mergerSection);
    } else if (Owner.IndexerDiskConfig->SearchEnabled) {
        realmConfigMemory.IndexingOrder = 2;
        realmConfigMemory.ConfigName = "Realtime"; // PseudoRT
        realmConfigMemory.Name = TRealmConfig::MakeRealmName(realmConfigMemory.IndexingOrder, realmConfigMemory.ConfigName);
        name = realmConfigMemory.Name;
        realmConfigMemory.Type = NRTYServer::ERealm::Realtime;
        realmConfigMemory.SearchersCountLimit = Owner.SearchersCountLimit;
        realmConfigMemory.StorageType = NRTYServer::EStorageType::Disk;
        realmConfigMemory.ClearOnStart = true;

        indIter = indexerSections.find("Memory");
        AssertCorrectConfig(indIter != indexerSections.end(), "There is no Memory default section in Realm indexer config");
        realmConfigMemory.IndexerConfigDisk->MaxDocuments = 0;
        realmConfigMemory.IndexerConfigDisk->Init(indIter->second);
        if (!realmConfigMemory.IndexerConfigDisk->MaxDocuments) {
            realmConfigMemory.IndexerConfigDisk->MaxDocuments = 2000;
        }
        if (!realmConfigMemory.IndexerConfigDisk->TimeToLiveSec) {
            realmConfigMemory.IndexerConfigDisk->TimeToLiveSec = 20;
        }
        realmConfigMemory.IndexerConfigDisk->Threads = diskThreads;
        realmConfigMemory.RealmDirectory = sod;
        realmConfigMemory.IndexerConfigDisk->SearchEnabled = true;

        realmConfigMemory.MergerConfig->Init(*mergerSection);
        realmConfigMemory.MergerConfig->MergerCheckPolicy = TMergerConfig::mcpNewIndexSimple;
        realmConfigMemory.MergerConfig->LockPolicy = TMergerConfig::ELockPolicy::None;
        realmConfigMemory.MergerConfig->MaxSegments = 1;

    } else {
        UpdateIndexersPtr();
        return;
    }

    realmConfigMemory.OxygenOptionsFile = Owner.IndexerMemoryConfig->RealTimeOxygenOptionsFile;
    realmConfigMemory.OxygenOptions = ::GetOxygenOptions(realmConfigMemory.OxygenOptionsFile, Owner.ConfigPatcher ? Owner.ConfigPatcher->GetPreprocessor() : nullptr);

    AssertCorrectConfig(RealmsConfig.emplace(name, std::move(realmConfigMemory)).second, "Multiple Realms with same name");
    UpdateIndexersPtr();
}

void TRealmListConfig::InitByDirectives(const TYandexConfig::Directives& directives) {
    GET_VALUE(CloseThreads);
}

void TRealmListConfig::Init(const TYandexConfig::Section* section) {
    InitByDirectives(section->GetDirectives());
    AutoGenerated = false;
    TYandexConfig::TSectionsMap realmListSection = section->GetAllChildren();
    ui32 realTimeCnt = 0;
    for (auto&& realmSection : realmListSection) {
        TString name = realmSection.first;
        TRealmConfig realmConfig(this->Owner);
        realmConfig.Init(name, realmSection.second);
        realTimeCnt += realmConfig.StorageType == NRTYServer::EStorageType::Memory;
        name = realmConfig.Name;
        AssertCorrectConfig(RealmsConfig.emplace(name, std::move(realmConfig)).second, "Multiple Realms with same name");
    }
    AssertCorrectConfig(realTimeCnt <= 1, "More than 1 Realtime Realm");
    UpdateIndexersPtr();
}

TString TRealmListConfig::ToString() const {
    if (AutoGenerated) {
        return "";
    }
    TString s;
    TStringOutput so(s);
    so << "<Realms>" << Endl;
    for (auto&& [k, v] : RealmsConfig) {
        so << v.ToString() << Endl;
    }
    so << "</Realms>" << Endl;
    return s;
}

TRealmConfig::TRealmConfig(const TRTYServerConfig& owner)
    : Owner(owner)
    , Type(NRTYServer::ERealm::Persistent)
    , SearchersCountLimit(owner.SearchersCountLimit)
    , ClearOnStart(false)
    , IndexerConfigDisk(MakeHolder<TIndexerConfigDisk>(*Owner.CommonIndexers))
    , IndexerConfigMemory(MakeHolder<TIndexerConfigMemory>(*Owner.CommonIndexers))
    , MergerConfig(MakeHolder<TMergerConfig>(owner))
{}

TRealmConfig::~TRealmConfig() = default;

const NOxygen::TOxygenOptions* TRealmConfig::GetOxygenOptions() const {
    return OxygenOptions ? OxygenOptions.Get() : Owner.CommonIndexers->OxygenOptions.Get();
}

TString TRealmConfig::ToString() const {
    TString s;
    TStringOutput so(s);
    so << "<" << ConfigName << ">" << Endl;
    so << "Type : " << ::ToString(Type) << Endl;
    so << "StorageType : " << ::ToString(StorageType) << Endl;
    so << "IndexingOrder : " << IndexingOrder << Endl;
    so << "SearchersCountLimit : " << SearchersCountLimit << Endl;
    so << "RealmDirectory : " << RealmDirectory << Endl;
    so << "ClearOnStart : " << ClearOnStart << Endl;
    so << "<Indexer>" << Endl;
    if (StorageType == NRTYServer::EStorageType::Disk) {
        IndexerConfigDisk->ToStringDirectives(so);
    } else {
        IndexerConfigMemory->ToStringDirectives(so);
    }
    so << "</Indexer>" << Endl;
    so << Endl << MergerConfig->ToString() << Endl;
    so << "</" << ConfigName << ">" << Endl;
    return s;
}

void TRealmConfig::UpdateIndexersPtr() {
    if (StorageType == NRTYServer::EStorageType::Disk) {
        IndexerConfigDisk->RealmConfig = this;
    } else {
        IndexerConfigMemory->RealmConfig = this;
    }
}

void TRealmConfig::Init(const TString& name, const TYandexConfig::Section* section) {
    TRealmConfig::InitByDirectives(section->GetDirectives());
    TYandexConfig::TSectionsMap realmSection = section->GetAllChildren();
    TYandexConfig::TSectionsMap::const_iterator iter = realmSection.find("Indexer");
    AssertCorrectConfig(iter != realmSection.end(), "There is no Indexer section in Realm config");
    {
        if (StorageType == NRTYServer::EStorageType::Memory) {
            IndexerConfigMemory->Init(iter->second);
        } else {
            IndexerConfigDisk->Init(iter->second);
        }
    }
    ConfigName = name;
    Name = MakeRealmName(IndexingOrder, name);
    iter = realmSection.find("Merger");
    MergerConfig->Init(*iter->second);
}

void TRealmConfig::InitByDirectives(const TYandexConfig::Directives& directives) {
    GET_VALUE(Type);
    GET_VALUE(IndexingOrder);
    GET_VALUE(SearchersCountLimit);
    GET_VALUE(StorageType);
    GET_VALUE(RealmDirectory);
    GET_VALUE(OxygenOptionsFile);
    GET_VALUE(ClearOnStart);
    OxygenOptions = ::GetOxygenOptions(OxygenOptionsFile, Owner.ConfigPatcher ? Owner.ConfigPatcher->GetPreprocessor() : nullptr);
}

TString TRealmConfig::MakeRealmName(ui32 indexingOrder, const TString& realmName) {
    return Sprintf("%03i_%s", indexingOrder, realmName.c_str());
}

const NRTYServer::TIndexerConfigDisk& TRealmConfig::GetIndexerConfigDisk() const {
    Y_ASSERT(!!IndexerConfigDisk);
    return *IndexerConfigDisk;
}

const NRTYServer::TIndexerConfigMemory& TRealmConfig::GetIndexerConfigMemory() const {
    Y_ASSERT(!!IndexerConfigMemory);
    return *IndexerConfigMemory;
}

const NRTYServer::TMergerConfig& TRealmConfig::GetMergerConfig() const {
    return *MergerConfig;
}

}
