#include "index_merger.h"
#include "merger_action.h"
#include "merger_analyzer.h"

#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/merger_config.h>
#include <saas/rtyserver/config/realm_config.h>
#include <saas/rtyserver/config/shards_config.h>
#include <saas/library/daemon_base/threads/rty_pool.h>
#include <saas/rtyserver/common/common_messages.h>
#include <saas/rtyserver/common/should_stop.h>
#include <saas/rtyserver/final_index/final_index_normalizer.h>
#include <saas/util/logging/exception_process.h>
#include <saas/util/app_exception.h>

#include <kernel/tarc/iface/settings.h>

#include <library/cpp/mediator/messenger.h>

void TIndexMerger::WaitMergingFinished(const TString& path) const {
    if (!Config.GetMergerConfig().Enabled)
        return;
    auto i = Analyzers.find(path);
    VERIFY_WITH_LOG(i != Analyzers.end(), "Incorrect path: %s", path.data());
    i->second->WaitMergingFinished();
}

void TIndexMerger::SetWatchdog(TWatchdogOptionsHandlePtr w) {
    // we cannot use IWatchdogOptions here, because we need to own the sharedptr (for resubscribing)
    WatchdogOptions = w;
}

void TIndexMerger::Start() {
    VERIFY_WITH_LOG(Status == tsStopped, "Incorrect usage TIndexMerger::Start");
    if (!Config.GetMergerConfig().Enabled)
        return;
    SetStatus(tsStarting);
    TGuard<TMutex> g(MutexStop);
    try {
        NOTICE_LOG << "MergeAnalyzerThread construction..." << Endl;
        for (auto&& [realmName, realmConfig] : Config.GetRealmListConfig().RealmsConfig) {
            if (realmConfig.StorageType == NRTYServer::EStorageType::Disk) {
                Analyzers[realmConfig.RealmDirectory] = new TAnalyzerAgent(
                    realmConfig.RealmDirectory,
                    realmConfig.GetMergerConfig().MaxSegments,
                    *this,
                    Config,
                    IMergerPolicyBehavior::TFactory::Construct(realmConfig.GetMergerConfig().MergerCheckPolicy, Config),
                    IMergerLockPolicy::TFactory::Construct(realmConfig.GetMergerConfig().LockPolicy, Config),
                    realmConfig.Type,
                    realmConfig.ConfigName
                );
            }
        }

        if (WatchdogOptions) {
            for (auto&& i : Analyzers)
                i.second->SubscribeToWatchdog(*WatchdogOptions);
        }

        MergerAnalyzers.Start(Analyzers.size());
        for (auto&& i : Analyzers) {
            MergerAnalyzers.Add(i.second.Get());
        }
        NOTICE_LOG << "MergeAnalyzerThread construction...OK" << Endl;

        NOTICE_LOG << "MergingAgents construction...(" << Config.GetMergerConfig().Threads << ")" << Endl;
        MergeAgents.clear();
        MergingThreads.Start(Config.GetMergerConfig().Threads);
        for (int iThread = 0; iThread < Config.GetMergerConfig().Threads; iThread++) {
            MergeAgents.push_back(new TMergerAgent(Config, *this));
            MergingThreads.Add(MergeAgents.back().Get());
        }
        NOTICE_LOG << "MergingAgents construction...(" << Config.GetMergerConfig().Threads << ")... OK" << Endl;

        SetStatus(tsActive); // locks MutexQueue
        return;
    } catch (...) {
        TString info = CurrentExceptionMessage();
        FAIL_LOG("Incorrect index merger start: %s", info.data());
    };
    SetStatus(tsFailOnStart); // never happens
}

void TIndexMerger::DoTask(IMergerTask::TPtr task, const std::atomic<bool>* rigidStop) {
    try {
        task->AddProgressInfo("Decoder building...");
        task->BuildDecoder(IndexStorage);
        task->AddProgressInfo("Decoder building...OK");
        task->BuildDirectoriesInfo(IndexStorage);
        task->AddProgressInfo("Starting merger: " + task->GetName() + ". DocsCount : " + ToString(task->GetDecoder()->GetSize()));
        TMergerAction mergerAction(Config, this, task->GetName());
        task->AddProgressInfo("OnStart...");
        if (!task->OnStart(rigidStop))
            ythrow yexception() << "Can't start merger task " << task->GetName();
        task->AddProgressInfo("OnStart...OK");
        if (ShouldStop(rigidStop))
            ythrow TMergeCancelException();

        task->AddProgressInfo("DoTask...");
        task->OnBeforeMainStage();
        if (!mergerAction.Do(task->GetSources(), task->GetTempDestinations(), task->GetTmpfsDestinations(), task->GetDecoder(), task->GetInfo(),
                             &*task, rigidStop, &*task)) {
            if (ShouldStop(rigidStop))
                ythrow TMergeCancelException();
            else
                ythrow yexception() << "while MergerAction " << task->GetName();
        }
        task->OnAfterMainStage();
        task->AddProgressInfo("DoTask...OK");
        SendGlobalMessage<TRTYMessageException>(TRTYMessageException::ecMergerDoTask);

        for (ui32 dest = 0; dest < task->GetTempDestinations().size(); ++dest) {
            TString tempDir = task->GetTempDestinations()[dest];
            if (task->GetTmpfsDestinations().size()) {
                TString tmpfsDir = task->GetTmpfsDestinations()[dest];
                task->AddProgressInfo("Remove " + tmpfsDir + "...");
                TFsPath(tmpfsDir).ForceDelete();
                task->AddProgressInfo("Remove " + tmpfsDir + "...OK");
            }

            if (task->GetDecoder()->GetNewDocsCount(dest)) {
                task->AddProgressInfo("Build service files for " + tempDir + "...");
                if (!TFinalIndexNormalizer::BuildServiceFiles(TPathName{tempDir}, Config, task->GetExecutionContext()))
                    ythrow yexception() << "while finalize index " << task->GetName();
                if (task->NeedWriteSourceIndexes()) {
                    TFixedBufferFileOutput f(tempDir + "/source_indexes");
                    for (unsigned i = 0; i < task->GetSourceSegments().size(); ++i)
                        f << task->GetSourceSegments()[i] << Endl;
                }
                task->AddProgressInfo("Build service files for " + tempDir + "...OK");
                task->AddProgressInfo("Move from temp " + ToString(dest) + "...");
                task->MoveFromTemp(dest, IndexStorage, rigidStop);
                task->AddProgressInfo("Move from temp " + ToString(dest) + "...OK");
            } else {
                task->AddProgressInfo("Remove temp " + tempDir + "...");
                TFsPath(tempDir).ForceDelete();
                task->AddProgressInfo("Remove temp " + tempDir + "...OK");
            }
        }
        task->AddProgressInfo("Finishing...");
        task->OnFinished(rigidStop);
        task->AddProgressInfo("Finishing...OK");
    } catch (TMergeCancelException& e) {
        ERROR_LOG << "MergerAction '" << task->GetName() << "' cancelled: " << CurrentExceptionMessage() << Endl;
        CHECK_WITH_LOG(ShouldStop(rigidStop));
        task->OnFinished(rigidStop);
        throw;
    } catch (NUtil::TFileFormatError& e) {
        AbortFromCorruptedIndex("MergerAction '%s' failed: %s", task->GetName().data(), CurrentExceptionMessage().data());
        throw;
    } catch (...) {
        ERROR_LOG << "MergerAction '" << task->GetName() << "' exception: " << CurrentExceptionMessage() << Endl;
        task->OnFailed();
        throw;
    }
}

bool TIndexMerger::Process(IMessage* message) {
    TMessageGetMergerTaskCount* messCount = dynamic_cast<TMessageGetMergerTaskCount*>(message);
    if (messCount) {
        TGuard<TMutex> g(MutexQueue);
        for (unsigned shard = 0; shard < Config.GetShardsConfig().Number; ++shard) {
            for (auto&& i : Analyzers)
                if (i.second->HasActiveTaskForShard(shard)) {
                    messCount->IncCount(shard, i.second->GetRealmName());
                }
        }
        for (TDeque< IMergerTask::TPtr >::const_iterator i = Tasks.begin(); i != Tasks.end(); ++i) {
            messCount->IncCount(unsigned((*i)->GetShardNumber()), (*i)->GetRealmName());
        }
        return true;
    }

    TMessageCollectServerInfo* messInfo = dynamic_cast<TMessageCollectServerInfo*>(message);
    if (messInfo) {
        messInfo->SetMergerStatus(Status);
        if (Status == tsActive) {
            TGuard<TMutex> g(MutexStop);
            if (Status == tsActive) {
                for (auto&& i : Analyzers) {
                    messInfo->AddMergerTasks(i.second->GetTasksCount());
                }
                for (TDeque< IMergerTask::TPtr >::const_iterator i = Tasks.begin(); i != Tasks.end(); ++i)
                    messInfo->AddMergerTasks(1);
            }
        }
        return true;
    }

    TMessageGetServerHealth *messHealth = dynamic_cast<TMessageGetServerHealth *>(message);
    if (messHealth) {
        messHealth->MergeFailedCount = Metrics.MergeFailed.Get();
        messHealth->LastMergeTime = Metrics.LastMergeTime.Get();
        messHealth->LastMergeElapsedTimed = (Metrics.MergeStartTime.Get() ? TInstant::Now().Seconds() - Metrics.MergeStartTime.Get() : 0);
        return true;
    }

    return false;
}

TString TIndexMerger::Name() const {
    return "IndexMerger";
}

TIndexMerger::~TIndexMerger() {
    UnregisterGlobalMessageProcessor(this);
    VERIFY_WITH_LOG(Status == tsStopped, "Incorrect usage TIndexMerger::Start");
}

TIndexMerger::TIndexMerger(TIndexStorage& indexStorage, const TRTYServerConfig& config)
    : Config(config)
    , IndexStorage(indexStorage)
    , MergerAnalyzers(Singleton<TRTYPools>()->Get(TRTYPools::TLoadtype::Batch), "IdxMergerAnal")
    , MergingThreads(Singleton<TRTYPools>()->Get(TRTYPools::TLoadtype::Batch), "IdxMerger")
    , Status(tsStopped)
{
    Singleton<TArchiveSettings>()->DocIdCheckerEnabled = true;
    RegisterGlobalMessageProcessor(this);
}

void TIndexMerger::Stop(bool rigidStop) {
    if (!Config.GetMergerConfig().Enabled)
        return;
    SendGlobalDebugMessage<TUniversalAsyncMessage>("mergerStopping");        // for TestSimultaneousDetach
    TGuard<TMutex> gS(MutexStop);
    NOTICE_LOG << "TIndexMerger::Stop::" << (TThreadStatus)Status << Endl;
    TGuard<TMutex> gQ(MutexQueue);
    VERIFY_WITH_LOG(Status==tsActive, "Incorrect usage TIndexMerger::Stop");
    Status = tsStopping;
    Tasks.clear();
    gQ.Release();
    TRY
        NOTICE_LOG << "Stopping index merging thread." << Endl;
        for (auto&& i : Analyzers) {
            i.second->Stop();
        }
        MergerAnalyzers.Stop();

        for (auto&& i : MergeAgents) {
            i->Stop(rigidStop);
        }
        MergingThreads.Stop();
        NOTICE_LOG << "Stopping index merging thread. Finished." << Endl;
        SetStatus(tsStopped);
        return;
    CATCH("while stopping");
    SetStatus(tsFailOnStop);

}
