#include "sync.h"

#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/realm_config.h>
#include <saas/rtyserver/common/message_collect_server_info.h>
#include <saas/rtyserver/docfetcher/library/config.h>
#include <saas/rtyserver/synchronizer/library/sync.h>
#include <saas/rtyserver/synchronizer/library/config/config.h>
#include <saas/library/index_snapshot/snapshot_manager.h>

#include <library/cpp/logger/global/global.h>

#include <google/protobuf/text_format.h>

NFusion::TSync::TSyncStatus::TSyncStatus()
    : Required(false)
    , Status(ESyncStatus::UNNECESSARY)
{
}
void NFusion::TSync::TSyncStatus::Change(const ESyncStatus& status) {
    Required = true;
    Status = status;
}

NJson::TJsonValue NFusion::TSync::TSyncStatus::GetStatus() const {
    NJson::TJsonValue status;
    status.InsertValue("Required", Required);
    status.InsertValue("Status", ToString(Status));
    return status;
}

void NFusion::TSync::OnStreamStart(const NFusion::TBaseStreamConfig& stream, TDuration delay) {
    INFO_LOG << "Stream " << stream.Name << " delay is " << delay << ", sync is required" << Endl;
    SendGlobalMessageAsync<TMessageNotifyDiagEvent>("streamSyncRequired"); //diagnostic hook

    Status.Change(ESyncStatus::STARTED);
    if (GetResources(stream, delay) && SyncResources()) {
        NOTICE_LOG << "Successfully synchronized index for stream " << stream.Name << Endl;
        Status.Change(ESyncStatus::FINISHED);
    } else {
        ERROR_LOG << "Could not synchronize stream " << stream.Name << Endl;
        OnSyncFailure();
    }

    SendGlobalMessageAsync<TMessageNotifyDiagEvent>("streamSyncCompleted");
}

void NFusion::TSync::OnSyncFailure() {
    Status.Change(ESyncStatus::FAILED);
}

bool NFusion::TSync::DisableSearchWhileFetching() const {
    TMessageCollectServerInfo info(/* isHumanReadable = */ false);
    return (!Options.EnableSearchWhileFetching || !SendGlobalMessage(info) || info.GetSearchableDocs() == 0);
}

bool NFusion::TSync::CriticalDelay(TDuration delay) const {
    DEBUG_LOG << "delay: " << delay.Seconds() << " crit: " << Options.SyncThreshold.Seconds() << Endl;
    return delay >= Options.SyncThreshold;
}

bool NFusion::TSync::IsConfigForSync() const {
    return !!Options.SyncServer && !!Options.SyncPath;
}

bool NFusion::TSync::SyncResources() {
    Status.Change(ESyncStatus::APPLY_RESOURCES);
    for (auto resource : Resources) {
        ui32 attempt = 0;
        while (attempt < Options.SyncMaxAttempts) {
            if (SyncResource(resource)) {
                NOTICE_LOG << "Successfully synchronized resource " << resource.GetName() << Endl;
                return true;
            }
            attempt++;
            Sleep(Options.SyncAttemptPause);
        }
        WARNING_LOG << "Could not synchronize resource " << resource.GetName() << Endl;
    }
    return false;
}

bool NFusion::TSync::SyncResource(const NRTYServer::TRemoteResource& resource) {

    const NRTYServer::TResourceFetchConfig* fetchConfigPtr = &GlobalConfig.GlobalResourceFetchConfig;
    if (Options.ResourceFetchConfig) {
        fetchConfigPtr = Options.ResourceFetchConfig.Get();
    }

    if (!NRTYServer::FetchAndConsumeIndices(resource, NRTYServer::EConsumeMode::Replace, GlobalConfig.GetRealmListConfig().GetMainRealmConfig().RealmDirectory, *fetchConfigPtr)) {
        ERROR_LOG << "Could not sync resource " << resource.GetName() << Endl;
        return false;
    }
    return true;
}

bool NFusion::TSync::GetResources(const NFusion::TBaseStreamConfig& stream, TDuration delay) {
    Status.Change(ESyncStatus::GETTING_RESOURCES);
    if (!IsConfigForSync()) {
        ERROR_LOG << "Cannot get resource to sync: server or path is not specified" << Endl;
        return false;
    }
    const NUtil::TInterval<ui64> localShard = { stream.ShardMin, stream.ShardMax };
    IIndexSnapshotManager::TContext context;
    context.Server = Options.SyncServer;
    context.Path = Options.SyncPath;
    context.RetryOptions = TRetryOptions(Options.SyncMaxAttempts);
    try {
        THolder<IIndexSnapshotManager> snapshotManager = THolder(IIndexSnapshotManager::TFactory::Construct(Options.SnapshotManager, context));
        auto shardResources = snapshotManager->GetResourcesForShard(localShard, delay);
        Resources.clear();
        std::copy(shardResources.begin(), shardResources.end(), std::back_inserter(Resources));
    } catch (const yexception& e) {
        ERROR_LOG << "Failed to GetResourcesForShard from IIndexSnapshotManager: " << e.what() << Endl;
        return false;
    }
    if (Resources.empty()) {
        ERROR_LOG << "No index shapshots are available for " << stream.Name << Endl;
        return false;
    }
    return true;
}
