#include "indexers_list.h"
#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/index_storage/index_storage.h>
#include <saas/rtyserver/indexer_disk/indexer.h>
#include <saas/rtyserver/indexer_memory/memory_indexer.h>
#include <saas/rtyserver/model/realm/realm.h>
#include <saas/rtyserver/logging/rty_index.h>

TIndexersPack::TIndexersPack(const TRTYServerConfig& config, TIndexStorage& storage)
    : Config(config)
    , IndexStorage(storage)
{
}

void TIndexersPack::Index(TQueuedDocument document, ui32 indexerThreadID, TIndexerMetrics* indexerMetrics, const TString& serviceName, TModifyResult& modifyResult) {
    const ui32 docIdent = document->GetMessageId();
    const TString docUrl = document->GetDocument().GetDocSearchInfo().GetUrl();
    try {
        if (document->IsEmpty()) {
            DEBUG_LOG << "Document id=" << docIdent << ";url=" << docUrl << " is empty" << Endl;
            document->SetStatus(NRTYServer::TReply::OK, "");
            QueueDocumentLog(serviceName, document->GetDocument(), GetFirstIndexerName(), "EMPTY");
            return;
        }

        NRTYServer::TReply::TRTYStatus currentPossibleDocumentStatus = NRTYServer::TReply::INCORRECT_DOCUMENT;
        bool currentSuccess = true;

        for (auto&& [indexerID, indexer] : Indexers) {
            TString indexerName = indexer->Directory().BaseName();
            bool indexRTDocuments = (Config.GetRealmListConfig().RealmsConfig.at(IndexersName[indexerID]).IndexingOrder == Config.GetRealmListConfig().GetMainRealmConfig().IndexingOrder);
            if (!indexRTDocuments && !document->GetRuntimeKey()) {
                break;
            }
            if (IsMemoryIndexer(indexerID)) {
                continue;
            }
            DEBUG_LOG << "Got document id=" << docIdent << ";url=" << docUrl << " to indexer " << indexerName << Endl;
            if (!indexer->Index(document, indexerThreadID) || document->MutableDocument().IsFailed()) {
                TString errorsInfo = document->GetDocument().GetErrorsCollector().GetStringReport();
                DEBUG_LOG << "Failed Index(document) id=" << docIdent << ";url=" << docUrl << " to indexer " << indexerName << "/" << errorsInfo << Endl;
                QueueDocumentLog(serviceName, document->GetDocument(), indexerName, "FAILED");
                document->SetStatus(currentPossibleDocumentStatus, errorsInfo);
                if (indexerMetrics) {
                    indexerMetrics->DocumentFail.Inc();
                }
                currentSuccess = false;
                break;
            } else {
                currentPossibleDocumentStatus = NRTYServer::TReply::INTERNAL_ERROR;
                DEBUG_LOG << "OK Index(document) id=" << docIdent << ";url=" << docUrl << " to indexer " << indexerName << Endl;
                document->SetStatus(NRTYServer::TReply::OK, "");
                QueueDocumentLog(serviceName, document->GetDocument(), indexerName, "SUCCESS");
                if (indexerMetrics) {
                    if (modifyResult.Count) {
                        indexerMetrics->DocumentModify.Inc();
                    } else {
                        indexerMetrics->DocumentAdd.Inc();
                    }
                }
            }
        }
        if (currentSuccess && document->GetRuntimeKey() && MemoryIndexerID.Defined()) {
            TString realtimeIndexerName = Indexers.at(*MemoryIndexerID)->Directory().BaseName();
            DEBUG_LOG << "Start RT Index(document) id=" << docIdent << ";url=" << docUrl << Endl;
            document->MutableCommand() = NRTYServer::TMessage::ADD_DOCUMENT;
            if (!Indexers.at(*MemoryIndexerID)->Index(document, indexerThreadID)) {
                DEBUG_LOG << "Failed RT Index(document) id=" << docIdent << ";url=" << docUrl << Endl;
                QueueDocumentLog(serviceName, document->GetDocument(), realtimeIndexerName, "FAIL");
                document->SetStatus(currentPossibleDocumentStatus, document->GetDocument().GetErrorsCollector().GetStringReport());
                DEBUG_LOG << "currentPossibleDocumentStatus: " << (currentPossibleDocumentStatus == NRTYServer::TReply::INTERNAL_ERROR) << Endl;
            } else {
                DEBUG_LOG << "OK RT Index(document) id=" << docIdent << ";url=" << docUrl << Endl;
                QueueDocumentLog(serviceName, document->GetDocument(), realtimeIndexerName, "SUCCESS");
            }
        }
    } catch (...) {
        ERROR_LOG << "Exception while document id=" << docIdent << ";url=" << docUrl << " that indexing" << " (" << CurrentExceptionMessage() << ")" << Endl;
    }
}

TString TIndexersPack::GetFirstIndexerName() {
    return Indexers.size() ? Indexers.begin()->second->Directory().BaseName() : "";
}

bool TIndexersPack::IsMemoryIndexer(ui32 indexerID) {
    return MemoryIndexerID.Defined() && indexerID == *MemoryIndexerID;
}

void TIndexersPack::UpdateTimestampsOnDelete(TQueuedDocument& document, ui32 indexerThreadID) {
    for (auto&& [indexerID, indexer] : Indexers) {
        if (indexer->GetRealm() == NRTYServer::ERealm::Realtime) {
            if (document->GetRuntimeKey()) {
                indexer->UpdateTimestampsOnDelete(document, indexerThreadID);
            }
        } else {
            indexer->UpdateTimestampsOnDelete(document, indexerThreadID);
        }
    }
}

TVector<TAtomicSharedPtr<IIndexer> > TIndexersPack::ResetIndexers(bool force, ui32 shard, bool timeToLiveSec, bool memoryIndexer, const TString& realmName) {
    TVector<TAtomicSharedPtr<IIndexer> > indexersForClose;
    for (auto [indexerID, indexer] : Indexers) {
        if (realmName != "" && realmName != Config.GetRealmListConfig().RealmsConfig.at(IndexersName[indexerID]).ConfigName) {
            continue;
        }
        if (!memoryIndexer && IsMemoryIndexer(indexerID)) {
            continue;
        }
        NRTYServer::EStorageType storageType = Config.GetRealmListConfig().RealmsConfig.at(IndexersName[indexerID]).StorageType;
        ui32 realmTimeToLiveSec = storageType == NRTYServer::EStorageType::Disk
                ? Config.GetRealmListConfig().RealmsConfig.at(IndexersName[indexerID]).GetIndexerConfigDisk().TimeToLiveSec
                : Config.GetRealmListConfig().RealmsConfig.at(IndexersName[indexerID]).GetIndexerConfigMemory().TimeToLiveSec;
        if (!timeToLiveSec || !!realmTimeToLiveSec) {
            if ((force && !indexer->IsEmpty()) || indexer->IsFull()) {
                indexersForClose.push_back(indexer);
                NOTICE_LOG << "Index is full " << indexer->Directory().BaseName() << Endl;
                Spawn(IndexersName[indexerID], shard);
                NOTICE_LOG << "Index " << indexer->Directory().BaseName() << " deactivated" << Endl;
            }
        }
    }
    return indexersForClose;
}

TVector<TAtomicSharedPtr<IIndexer> > TIndexersPack::GetIndexers() {
    TVector<TAtomicSharedPtr<IIndexer> > result;
    for (auto&& [indexerID, indexer] : Indexers) {
        result.push_back(indexer);
    }
    return result;
}

void TIndexersPack::SpawnAllIndexers(ui32 shard) {
    for (auto&& [indexerName, indexer] : Config.GetRealmListConfig().RealmsConfig) {
        Spawn(indexerName, shard);
    }
}

void TIndexersPack::Spawn(const TString& indexerName, ui32 shard) {
    const NRTYServer::TRealmConfig& realmConfig = Config.GetRealmListConfig().RealmsConfig.at(indexerName);
    NOTICE_LOG << "status=indexer_allocation_start;shard=" << shard << ";itype=" << (ui32)realmConfig.Type << Endl;
    TString indexDirName;
    TString tempDirName;

    const bool isDisk = realmConfig.StorageType == NRTYServer::EStorageType::Disk;
    const bool isPrep = isDisk ? realmConfig.GetIndexerConfigDisk().PreparatesMode : realmConfig.GetIndexerConfigMemory().PreparatesMode;

    IndexStorage.AllocateIndex(indexDirName, tempDirName, shard, isDisk, isPrep);
    IndexersName[realmConfig.IndexingOrder] = realmConfig.Name;
    TString path;
    if (realmConfig.StorageType == NRTYServer::EStorageType::Disk) {
        NOTICE_LOG << "status=disk_indexer_allocation;shard=" << shard << ";name=" << tempDirName << Endl;
        Indexers[realmConfig.IndexingOrder] = new TIndexer(indexDirName, tempDirName, realmConfig.RealmDirectory, realmConfig.GetIndexerConfigDisk(), IndexStorage, shard);
        path = realmConfig.RealmDirectory;
    } else if (realmConfig.StorageType == NRTYServer::EStorageType::Memory) {
        MemoryIndexerID = realmConfig.IndexingOrder;
        NOTICE_LOG << "status=memory_indexer_allocation;shard=" << shard << ";name=" << tempDirName << Endl;
        Indexers[realmConfig.IndexingOrder] = new TMemoryIndexer(indexDirName, tempDirName, realmConfig.RealmDirectory, realmConfig.GetIndexerConfigMemory(), shard);
        path = realmConfig.RealmDirectory;
    } else {
        Y_FAIL();
    }

    CHECK_WITH_LOG(!!path);
    TFile normalIndex(TFsPath(path) / tempDirName / "normal_index", WrOnly | OpenAlways);
    NOTICE_LOG << "status=indexer_allocated;shard=" << shard << ";name=" << tempDirName << ";itype=" << (ui32)realmConfig.Type << Endl;
}

