#include "deferred_index_updater.h"
#include <saas/rtyserver/indexer_core/parsed_doc_serializer.h>
#include <saas/rtyserver/common/common_messages.h>
#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/indexer_config.h>
#include <saas/rtyserver/config/realm_config.h>
#include <saas/util/logging/exception_process.h>

TDeferredUpdatesStorage::TDeferredUpdatesStorage(const TRTYServerConfig& config)
    : Config(config)
{}

TDeferredUpdatesStorage::~TDeferredUpdatesStorage() = default;

namespace {
    TMutex MutexDirectoriesConstruction;
}

struct TDeferredUpdatesStorage::TOneIndexStorage {
    TOneIndexStorage(const TString& indexDirName, const TRTYServerConfig& config)
        : Serializer(config, NRTYServer::TDocSerializeContext(), NRTYServer::TDocParseContext(), NRTYServer::TMessage::DEPRECATED__UPDATE_DOCUMENT)
    {
        TFsPath indexDir = TFsPath(config.GetRealmListConfig().GetMainRealmConfig().RealmDirectory) / indexDirName;
        Dir = TFsPath(config.GetRealmListConfig().GetMainRealmConfig().RealmDirectory) / "deferred_updates" / indexDirName;
        ui32 docsCount = 0;
        if (!Dir.Exists()) {
            TGuard<TMutex> g(::MutexDirectoriesConstruction);
            if (!Dir.Exists()) {
                Dir.MkDirs();
                TFsPath indexFrq = indexDir / "indexfrq";
                if (indexFrq.Exists())
                    docsCount = TFile(indexFrq, RdOnly).GetLength() / sizeof(i16);
                else
                    docsCount = config.GetRealmListConfig().GetMainRealmConfig().GetIndexerConfigDisk().MaxDocuments;
            }
        }

        NRTYArchive::TMultipartConfig mpConfig;
        mpConfig.ReadContextDataAccessType = NRTYArchive::IDataAccessor::DIRECT_FILE;
        mpConfig.Compression = NRTYArchive::IArchivePart::COMPRESSED;
        Archive = TArchiveOwner::Create(Dir / "updates", mpConfig, docsCount);
        CHECK_WITH_LOG(!!Archive);
    }

    ~TOneIndexStorage() {
        Clear();
    }

    void Clear() {
        try {
            Archive = nullptr;
            if (Dir.Exists())
                Dir.ForceDelete();
        } catch (...) {
            ERROR_LOG << "cannot clear " << Dir.GetPath() << ": " << CurrentExceptionMessage() << Endl;
        }
    }
    TArchiveOwner::TPtr Archive;
    NRTYServer::TParsedDocumentSerializer Serializer;
    TFsPath Dir;
};

void TDeferredUpdatesStorage::ApplyDeferredUpdates(const TString& indexDirName, TParsedDocument& doc, ui32 docId) {
    TReadGuard g(Mutex);
    if (auto* updates = AllUpdates.FindPtr(indexDirName)) {
        if (auto up = (*updates)->Serializer((*updates)->Archive->GetDocument(docId))) {
            doc.ApplyPatch(*up);
        }
    }
}

void TDeferredUpdatesStorage::RemoveDocs(const TString& indexDirName, const TVector<ui32>& docIds) {
    TReadGuard g(Mutex);
    if (auto* updates = AllUpdates.FindPtr(indexDirName)) {
        for (const auto& docid : docIds) {
            (*updates)->Archive->RemoveDocument(docid);
        }
    }
}

void TDeferredUpdatesStorage::ApplyDeferredUpdates(IIndexUpdater& updater, const IDecoder& decoder) {
    TWriteGuard g(Mutex);
    INFO_LOG << "Deferred updates applying... " << AllUpdates.size() << Endl;
    ui32 updatesCounter = 0;
    for (const auto& [_, updates]: AllUpdates) {
        updates->Archive->Flush();
        for (auto doc = updates->Archive->CreateIterator(); doc->IsValid(); doc->Next()) {
            ui32 newDocId = 0;
            TParsedDocument::TPtr pd = updates->Serializer(doc->GetDocument());
            DEBUG_LOG << "Deferred update for url=" << pd->GetDocSearchInfo().GetUrl() << "/" << pd->GetDocSearchInfo().GetHash().Quote() << " from index=" << updates->Dir << " starting..." << Endl;
            if (!!pd && decoder.Decode(pd->GetDocSearchInfo().GetHash(), doc->GetDocid(), newDocId) && newDocId != REMAP_NOWHERE) {
                DEBUG_LOG << "Deferred update for url=" << pd->GetDocSearchInfo().GetUrl() << " from index=" << updates->Dir << ":" << doc->GetDocid() << " -> " << newDocId << Endl;
                updater.UpdateDoc(newDocId, pd);
                ++updatesCounter;
            }
        }
        updates->Clear();
    }
    SendGlobalDebugMessage<TMessageDeferredUpdatesNotification>(updatesCounter);
    AllUpdates.clear();
}

void TDeferredUpdatesStorage::SaveUpdate(const TString& indexDirName, ui32 docId, TParsedDocument::TPtr document) {
    TWriteGuard gw(Mutex);
    DEBUG_LOG << "Saved update for url=" << document->GetDocSearchInfo().GetUrl() << " index=" << indexDirName << Endl;
    auto& updates = AllUpdates[indexDirName];
    if (!updates) {
        updates = MakeHolder<TOneIndexStorage>(indexDirName, Config);
    }

    CHECK_WITH_LOG(updates->Archive);

    TParsedDocument::TPtr oldDoc = updates->Serializer(updates->Archive->GetDocument(docId));
    if (oldDoc) {
        oldDoc->ApplyPatch(*document);
    } else {
        oldDoc = document;
    }
    updates->Archive->PutDocument(*oldDoc, docId, updates->Serializer);
    DEBUG_LOG << "Saved update for url=" << document->GetDocSearchInfo().GetUrl() << " index=" << indexDirName << " ... OK" << Endl;
}

TDeferredIndexUpdater::TDeferredIndexUpdater(IIndexController& owner, TDeferredUpdatesStorage::TPtr storage)
    : TIndexUpdater(owner)
    , DeferredUpdatesStorage(storage)
    , Owner(owner)
{}

bool TDeferredIndexUpdater::UpdateDoc(ui32 docId, const TParsedDocument::TPtr doc) {
    TRY
        if (docId != REMAP_NOWHERE && Owner.IsRemoved(docId))
            return false;
        TReadGuard g(MutexDeferredUpdatesStorage);
        bool deferredActivated = false;
        if (!!DeferredUpdatesStorage) {
            deferredActivated = true;
            DeferredUpdatesStorage->SaveUpdate(Owner.Directory().BaseName(), docId, doc);
        }
        if (Index.GetType() != IIndexController::DISK) {
            return UpdateDocNative(docId, doc) || deferredActivated;
        } else {
            return deferredActivated;
        }
    CATCH("while UpdateDoc " + doc->GetDocSearchInfo().GetUrl());
    return false;
}

bool TDeferredIndexUpdater::UpdateDocNative(ui32 docId, const TParsedDocument::TPtr doc) {
    return TIndexUpdater::UpdateDoc(docId, doc);
}

bool TDeferredIndexUpdater::RestoreDoc(ui32 docId, TParsedDocument::TPtr& doc) {
    if (!TIndexUpdater::RestoreDoc(docId, doc))
        return false;
    TReadGuard g(MutexDeferredUpdatesStorage);
    if (!!DeferredUpdatesStorage)
        DeferredUpdatesStorage->ApplyDeferredUpdates(Owner.Directory().BaseName(), *doc, docId);
    return true;
}

void TDeferredIndexUpdater::SetDeferredUpdatesStorage(TDeferredUpdatesStorage::TPtr storage) {
    TWriteGuard g(MutexDeferredUpdatesStorage);
    DeferredUpdatesStorage = storage;
}

void TDeferredIndexUpdater::ApplyUpdatesAndDropStorage(IIndexUpdater& updater, const TDeferredUpdatesStorage::IDecoder& decoder) {
    TWriteGuard g(MutexDeferredUpdatesStorage);
    if (!!DeferredUpdatesStorage)
        DeferredUpdatesStorage->ApplyDeferredUpdates(updater, decoder);
    DeferredUpdatesStorage = nullptr;
}

TDeferredUpdatesStorage::TPtr TDeferredIndexUpdater::GetDeferredUpdatesStorage() {
    TReadGuard g(MutexDeferredUpdatesStorage);
    return DeferredUpdatesStorage;
}

void TDeferredIndexUpdater::RemoveDocs(const TVector<ui32>& docIds) {
    TReadGuard g(MutexDeferredUpdatesStorage);
    if (!!DeferredUpdatesStorage){
        DeferredUpdatesStorage->RemoveDocs(Owner.Directory().BaseName(), docIds);
    }
}
