#include "deferred.h"

#include <saas/rtyserver/common/message_collect_server_info.h>
#include <saas/rtyserver/config/common_indexers_config.h>
#include <saas/rtyserver/config/indexer_config.h>
#include <saas/rtyserver/indexer_server/indexer_server.h>
#include <saas/rtyserver/zoo_sync/zoo_sync.h>

#include <library/cpp/threading/future/async.h>

#include <util/folder/filelist.h>

class TDeferredIndexationLogicConfig : public TPluginConfig<TRTYServerConfig::IExternalLogicConfig> {
public:
    TString StoragePath;
    ui32 ExecutorThreads = 4;

protected:
    bool DoCheck() const override {
        return true;
    }

    void DoInit(const TYandexConfig::Section& componentSection) override {
        StoragePath = componentSection.GetDirectives().Value<TString>("StoragePath", StoragePath);
        ExecutorThreads = componentSection.GetDirectives().Value<ui32>("ExecutorThreads", ExecutorThreads);
    }

    void DoToString(IOutputStream& so) const override {
        so << "StoragePath : " << StoragePath << Endl;
        so << "ExecutorThreads : " << ExecutorThreads << Endl;
    }

private:
    static TFactory::TRegistrator<TDeferredIndexationLogicConfig> Registrator;
};

void TExternalLogicDeferredIndexation::StartIndexation(TIndexerServer& server) {
    Server = &server;
    Config = server.Config.Common.Owner.ExternalLogicConfig.Get<TDeferredIndexationLogicConfig>(NRTYServer::DeferredIndexationLogic);
    CHECK_WITH_LOG(Config);
    CHECK_WITH_LOG(Config->StoragePath);
    Executor.Start(Config->ExecutorThreads);

    if (!TFsPath(Config->StoragePath).Exists())
        TFsPath(Config->StoragePath).MkDirs();

    TFileList fl;
    fl.Fill(Config->StoragePath);

    ui32 currentVersion = NRTYServer::UndefinedVersion;
    const char* path = nullptr;
    while (path = fl.Next()) {
        TString fileName(path);
        if (fileName.EndsWith(".fat")) {
            try {
                ui32 version = FromString<ui32>(fileName.substr(0, fileName.size() - 4));
                currentVersion = currentVersion ? Min(currentVersion, version) : version;
                NRTYArchive::TStorage::TLimits limits;
                Queues[version] = new NRTYArchive::TStorage(Config->StoragePath + "/" + ToString(version), limits, NRTYArchive::TMultipartConfig());
                INFO_LOG << "Queue for version " << version << " loaded" << Endl;
            } catch (...) {
                ERROR_LOG << "Incorrect data for deferred queues: " << fileName << Endl;
            }
        }
    }
    CurrentVersion = Max(currentVersion, NRTYServer::MinimumVersion);
    INFO_LOG << "CurrentVersion detected as " << *CurrentVersion << Endl;

    Active = true;
    RegisterGlobalMessageProcessor(this);
}

void TExternalLogicDeferredIndexation::FinishIndexation() {
    INFO_LOG << "External deferred logic finishing..." << Endl;
    UnregisterGlobalMessageProcessor(this);
    Active = false;
    Executor.Stop();
    Queues.clear();
    INFO_LOG << "External deferred logic finished" << Endl;
}

bool TExternalLogicDeferredIndexation::Process(IMessage* message_) {
    if (auto message = message_->As<TMessageBaseVersionChanged>()) {
        SwitchVersion(message->NewVersion, message->Switcher);
        return true;
    }
    if (auto message = message_->As<TMessageCollectServerInfo>()) {
        if (CurrentVersion) {
            message->Fields["database_version"] = *CurrentVersion;
        }
        return true;
    }
    return false;
}

TString TExternalLogicDeferredIndexation::Name() const {
    return GetName();
}

bool TExternalLogicDeferredIndexation::AddMessage(const NRTYServer::TMessage& message, IRTYReplierAbstract* /*replier*/) {
    ui32 bv = message.GetDocument().GetDatabaseVersion();
    if (bv > *CurrentVersion && !!bv) {
        TReadGuard rgCheck(MutexQueue);
        if (!Queues.contains(bv)) {
            rgCheck.Release();
            TWriteGuard wg(MutexQueue);
            NRTYArchive::TStorage::TLimits limits;
            Queues[bv] = new NRTYArchive::TStorage(Config->StoragePath + "/" + ToString(bv), limits, NRTYArchive::TMultipartConfig());
        }
        TReadGuard rgMain(MutexQueue);
        CHECK_WITH_LOG(Queues.contains(bv));
        Queues.find(bv)->second->Put(TBlob::FromString(message.SerializeAsString()));
        NOTICE_LOG << "Message " << message.GetDocument().GetUrl() << " stored for deferred indexation with version " << bv << Endl;
        return true;
    } else {
        NOTICE_LOG << "Message " << message.GetDocument().GetUrl() << " provided for immediate indexation with version " << bv << Endl;
        return false;
    }
}

TString TExternalLogicDeferredIndexation::GetName() const {
    return "ExternalLogicDeferredIndexation";
}

void TExternalLogicDeferredIndexation::SwitchVersion(ui32 version, NRTYServer::ISwitcherPtr switcher) {
    auto task = [this, version, switcher] {
        TVector<NThreading::TFuture<void>> indexers;

        INFO_LOG << "Version changing: " << *CurrentVersion << " -> " << version << Endl;
        NRTYServer::ISwitcher::ILockPtr lock = switcher ? switcher->Lock() : nullptr;
        {
            TWriteGuard wg(MutexQueue);

            TVector<ui32> consumedVersions;
            for (auto&& q : Queues) {
                const ui32 queueVersion = q.first;
                const auto queue = q.second;
                if (queueVersion <= version) {
                    indexers.push_back(ConsumeQueue(queue));
                    consumedVersions.push_back(queueVersion);
                }
            }
            for (auto&& v : consumedVersions) {
                Queues.erase(v);
            }
        }

        auto waiter = NThreading::WaitExceptionOrAll(indexers);
        waiter.Wait();
        waiter.GetValue();

        INFO_LOG << "Version changed to " << version << Endl;
        CurrentVersion = version;
    };

    CHECK_WITH_LOG(Executor.AddFunc(task));
}

NThreading::TFuture<void> TExternalLogicDeferredIndexation::ConsumeQueue(NRTYArchive::TStorage::TPtr queue) {
    auto task = [this, queue] {
        NRTYArchive::TStorage::TDocument::TPtr doc;
        while (Active && !!(doc = queue->Get(false))) {
            TBlob data = doc->GetBlob();
            try {
                NRTYServer::TMessage message;
                Y_PROTOBUF_SUPPRESS_NODISCARD message.ParseFromString(TString(data.AsCharPtr(), data.Size()));
                const TString url = message.GetDocument().GetUrl();
                DEBUG_LOG << "KeyId: " << message.GetDocument().GetUrl() << " taken from queue" << Endl;
                IReplier::TPtr replier = MakeAtomicShared<TFakeReplier>(TIndexerServer::RequestsTransaction, message.GetMessageId());
                TQueuedDocument queuedDocument = MakeAtomicShared<TGuardedDocument>(message, replier, Server->Config.Common.Owner);
                if (queuedDocument->IsCorrect()) {
                    DEBUG_LOG << "KeyId: " << url << " made key stat" << Endl;
                    Server->AddDocument(queuedDocument, message);
                } else {
                    WARNING_LOG << "KeyId: " << url << " incorrect for made key stat" << Endl;
                }
            } catch (...) {
                const TString exMessage = CurrentExceptionMessage();
                FAIL_LOG("Can't index deferred data: %s", exMessage.data());
            }
            doc->Ack();
        }
    };

    return NThreading::Async(task, Executor);
}

TRTYServerConfig::IExternalLogicConfig::TFactory::TRegistrator<TDeferredIndexationLogicConfig> TDeferredIndexationLogicConfig::Registrator(NRTYServer::DeferredIndexationLogic);
TExternalLogicDeferredIndexation::TFactory::TRegistrator<TExternalLogicDeferredIndexation> TExternalLogicDeferredIndexation::Registrator(NRTYServer::DeferredIndexationLogic);
