#include "docfetcher.h"
#include "distributor.h"
#include "mapreduce.h"
#include "messages.h"
#include "persqueue.h"
#include "snapshot.h"
#include "sync.h"

#include <saas/rtyserver/common/message_collect_server_info.h>
#include <saas/rtyserver/config/config.h>

#include <saas/rtyserver/docfetcher/dc_checker/dc_checker.h>


namespace NFusion {
    TDocFetcherModule::TDocFetcherModule(const IServerConfig& serverConfig)
        : Config(serverConfig.GetMeAs<TRTYServerConfig>())
        , FetcherConfig(*serverConfig.GetModuleConfig<TDocFetcherConfig>(NameStatic()))
        , Signals(*serverConfig.GetModuleConfig<TDocFetcherConfig>(NameStatic()))
        , Log(FetcherConfig.LogFile)
        , SysLog(FetcherConfig.SysLogFile)
    {
        RegisterGlobalMessageProcessor(this);

        if (FetcherConfig.PQDatacenterChecker && FetcherConfig.PQDatacenterChecker->Enabled) {
            PQDatacenterChecker.Reset(new TDatacenterChecker(*FetcherConfig.PQDatacenterChecker));
        }

        for (auto&& streamConfig : FetcherConfig.PersQueueStreams) {
            if (streamConfig.Enabled) {
                auto stream = MakeHolder<TSyncStream<TPersQueueStream>>(
                        /*SyncStream: */streamConfig, Config,
                        /*PersQueueStream: */*this, streamConfig, Config, PQDatacenterChecker, Log, SysLog
                );
                Streams.push_back(std::move(stream));
            }
        }
        for (auto&& streamConfig : FetcherConfig.MapReduceStreams) {
            if (streamConfig.Enabled) {
                auto stream = MakeHolder<TSyncStream<TMapReduceStream>>(
                        /*SyncStream: */streamConfig, Config,
                        /*MapReduceStream: */*this, streamConfig, Config, Log);
                Streams.push_back(std::move(stream));
            }
        }
        for (auto&& streamConfig : FetcherConfig.DistributorStreams) {
            if (streamConfig.Enabled) {
                auto stream = MakeHolder<TSyncStream<TDistributorStream>>(
                    /*SyncStream: */streamConfig, Config,
                    /*DistributorStream: */*this, streamConfig, Config, Log
                );
                Streams.push_back(std::move(stream));
            }
        }
        if (FetcherConfig.SnapshotStream && FetcherConfig.SnapshotStream.Get()->Enabled) {
            auto stream = MakeHolder<TSyncStream<TSnapshotStream>>(
                /*SyncStream: */*FetcherConfig.SnapshotStream, Config,
                /*SnapshotStream: */*this, *FetcherConfig.SnapshotStream, Config, Log);
            Streams.push_back(std::move(stream));
        }
    }

    TDocFetcherModule::~TDocFetcherModule() {
        UnregisterGlobalMessageProcessor(this);
    }

    bool TDocFetcherModule::Start(const TStartContext& context)  {
        if (!FetcherConfig.Enabled) {
            NOTICE_LOG << "DocFetcher: disabled" << Endl;
            return true;
        }

        NOTICE_LOG << "DocFetcher: starting" << Endl;

        if (context.HasWatchdogOpts()) {
            for (auto& stream : Streams) {
                stream->SubscribeToWatchdog(context.GetWatchdogOpts());
            }
        }

        if (PQDatacenterChecker) {
            PQDatacenterChecker->Start();
        }

        for (auto&& stream : Streams) {
            stream->Start();
        }

        if (FetcherConfig.EnableSearchOnStart) {
            // This is useful with "Searcher.AutoStartServer: false". The searcher port remains closed until
            // docfecther streams have a chance to evaluate the delay in OnStreamStart().
            // FIXME(salmin): If one stream does Disable+Enable sequence then search gets enabled before other streams
            // have a chance to be initialized.
            auto guard = Guard(SearchBanMutex);
            if (SearchBanCounter == 0) {
                SetSearchEnabled(true);
            }
        }

        NOTICE_LOG << "DocFetcher: started" << Endl;
        return true;
    }

    bool TDocFetcherModule::Stop(const TStopContext& /*context*/) {
        NOTICE_LOG << "DocFetcher: stopping" << Endl;

        if (FetcherConfig.EnableSearchOnStart) {
            // For consistent backend restart.
            auto guard = Guard(SearchBanMutex);
            if (SearchBanCounter == 0) {
                SetSearchEnabled(false);
            }
        }

        if (PQDatacenterChecker) {
            PQDatacenterChecker->Stop();
        }

        for (auto&& stream : Streams) {
            stream->Stop();
        }
        NOTICE_LOG << "DocFetcher: stopped" << Endl;
        return true;
    }

    void TDocFetcherModule::SetSearchEnabled(bool shouldEnable) {
        const auto action = shouldEnable ? TMessageSearchServerControl::sscaStart : TMessageSearchServerControl::sscaStop;
        const auto actionStr = shouldEnable ? "enable" : "disable";
        DEBUG_LOG << "DocFetcher is to " << actionStr << " search" << Endl;

        SearchEnabled = SendGlobalMessage<TMessageSearchServerControl>(action).IsRunning();
        if (SearchEnabled != shouldEnable)
            ERROR_LOG << "DocFetcher was unable to " << actionStr << " search" << Endl;
        else
            NOTICE_LOG << "DocFetcher has " << actionStr << "d search" << Endl;
    }

    bool TDocFetcherModule::Process(NMessenger::IMessage* message) {
        TMessageDocfetcherSearchBan* msg = message->As<TMessageDocfetcherSearchBan>();
        if (msg) {
            auto guard = Guard(SearchBanMutex);
            SearchBanCounter += (msg->GetSearchDisabled() ? 1 : -1);
            CHECK_WITH_LOG(SearchBanCounter >= 0);
            bool shouldEnable = SearchBanCounter == 0;
            if (SearchEnabled != shouldEnable)
                SetSearchEnabled(shouldEnable);

            return true;
        }
        TMessageCollectServerInfo* messInfo = message->As<TMessageCollectServerInfo>();
        if (messInfo) {
            {
                auto guard = Guard(SearchBanMutex);
                messInfo->SetDocfetcherSearchBan(SearchBanCounter != 0);
            }
            {
                auto guard = Guard(CurrentResourceMutex);
                messInfo->SetCurrentSyncResource(CurrentResource);
                messInfo->SetLastSyncResource(LastResource);
            }
            return true;
        }
        if (message->As<TMessageOnMergerTaskStart>()) {
            AtomicIncrement(MergerTaskCounter);
            return true;
        }
        if (message->As<TMessageOnMergerTaskFinish>()) {
            AtomicDecrement(MergerTaskCounter);
            return true;
        }
        TMessageOnSyncStart* messSyncStart = dynamic_cast<TMessageOnSyncStart*>(message);
        if (messSyncStart) {
            auto guard = Guard(CurrentResourceMutex);
            CurrentResource = messSyncStart->ResourceName;
            LastResource = messSyncStart->ResourceName;
            return true;
        }
        if (message->As<TMessageOnSyncFinish>()) {
            auto guard = Guard(CurrentResourceMutex);
            CurrentResource = "";
            return true;
        }
        return false;
    }
}

TDaemonModules::TFactory::TRegistrator<NFusion::TDocFetcherModule> DocfetcherRegistrator(TString{NFusion::DocfetcherModuleName});
