#include "sync.h"
#include "resource.h"

#include <saas/rtyserver/common/common_messages.h>
#include <saas/rtyserver/common/search_area_modifier.h>
#include <saas/rtyserver/indexer_core/merger_interfaces.h>
#include <saas/library/yt/lock_unique_by_path/lock_unique.h>

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

namespace {
    class TSyncInformer {
    public:
        TSyncInformer(const TString& resourceName) {
            SendGlobalMessage<TMessageOnSyncStart>(resourceName);
        }
        ~TSyncInformer() {
            SendGlobalMessage<TMessageOnSyncFinish>();
        }
    };

    TFsPath ResolveDirEntity(TFsPath entity) {
        if (entity.IsRelative()) {
            entity = TFsPath::Cwd() / entity;
        }
        if (entity.IsSymlink()) {
            entity = entity.ReadLink();
        }
        return entity;
    }

    // recurse into resourceRoot, to find all Final indexes (helper method)
    TVector<TFsPath> FindIndexesInBackup(const IIndexStorage& storage, const TFsPath& resourceRoot, bool skipEmptyIndex) {
        TVector<TFsPath> indices;

        TVector<TFsPath> destinations;
        destinations.push_back(resourceRoot);
        ui32 len = destinations.size();
        for (ui32 i = 0; i < len; ++i) {
            const TFsPath entity = ResolveDirEntity(destinations[i]);
            Y_ASSERT(entity.IsAbsolute());

            if (!entity.IsDirectory()) {
                if (entity.Basename() != "shard.conf") {
                    WARNING_LOG << "Cannot consume " << entity << ": not an index directory" << Endl;
                }
                continue;
            }

            const TFsPath& dir = entity;

            const bool isIndexCandidate = storage.HasIndexFiles(dir);
            INFO_LOG << "Directory " << dir << " is" << (isIndexCandidate? "" : " not") << " an index candidate" << Endl;
            if (isIndexCandidate) {
                if (skipEmptyIndex && storage.IsEmptyIndex(dir)) {
                    INFO_LOG << "Skip empty index" << Endl;
                } else {
                    indices.push_back(dir);
                }
                continue;
            }

            TVector<TFsPath> childs;
            dir.List(childs);
            destinations.insert(destinations.end(), childs.begin(), childs.end());
            len += childs.size();
        }
        return indices;
    }

    bool IsReplaceMode(NRTYServer::EConsumeMode mode) {
        switch (mode) {
            case NRTYServer::EConsumeMode::Append:
            case NRTYServer::EConsumeMode::Apply:
                return false;
            case NRTYServer::EConsumeMode::Replace:
            case NRTYServer::EConsumeMode::HardReplace:
                return true;
        }
        Y_ASSERT("Unknown mode");
        return false;
    }
}

bool NRTYServer::FetchAndConsumeIndices(const TRemoteResource& resource, EConsumeMode mode,
    const TFsPath& temp, const NRTYServer::TResourceFetchConfig& config,
    NRTYServer::IRemoteResourceGetCallback* callback,
    std::function<bool(IIndexWithComponents&)> indexProcessorCb)
{
    INFO_LOG << "Started FetchAndConsume of resource " << resource.GetName().Quote() << ", mode=" << mode << Endl;
    if (!resource.GetName()) {
        WARNING_LOG << "Remote resource must have a name" << Endl;
        return false;
    }
    TSyncInformer si(resource.GetName());

    IIndexStorage& storage = TMergerEngine::GetMerger()->GetIndexStorage();

    if (mode == NRTYServer::EConsumeMode::HardReplace) {
        INFO_LOG << "Clear index for 'hard_replace' mode..." << Endl;
        NRTYServer::ClearFinalIndexes();
        VERIFY_WITH_LOG(storage.GetFinalIndexes(TString()).empty(), "Unable to clear index");
        INFO_LOG << "Clear index for 'hard_replace' mode... done" << Endl;
    }

    const TFsPath& destination = temp / resource.GetName();
    if (!resource.Fetch(destination, config, callback)) {
        WARNING_LOG << "Cannot fetch resource: " << resource.GetName() << Endl;
        return false;
    }
    INFO_LOG << "Finished fetch of resource " << resource.GetName().Quote() << ", starting consume" << Endl;

    TMergerStopGuard msg((mode != NRTYServer::EConsumeMode::Apply && mode != NRTYServer::EConsumeMode::Append));
    IIndexStorage::TDeferredRemoveTaskPtr removeTask;
    const TVector<TString> previous = storage.GetFinalIndexes(TString());

    TMaybe<TYtLockUnique> lock;
    if (config.YTFetchConfig && !config.YTFetchConfig->PathToLock.empty() && !config.YTFetchConfig->Proxy.empty()) {

        lock.ConstructInPlace(config.YTFetchConfig->Proxy, config.YTFetchConfig->PathToLock,
                              config.YTFetchConfig->MinutesOfWaitingForLock , config.YTFetchConfig->LockMaxAttempts);

        lock->TryLock();
    }

    const TVector<TFsPath> indices = FindIndexesInBackup(storage, destination, IsReplaceMode(mode));
    if (IsReplaceMode(mode) && indices.empty()) {
        WARNING_LOG << "No indices in " << destination << " are found" << Endl;
        return false;
    }

    THolder<IIndexConsumeTransaction> transaction = storage.StartIndexConsumeTransaction();
    Y_ASSERT(!!transaction);

    for (auto&& entity : indices) {
        Y_ASSERT(entity.IsAbsolute() && entity.IsDirectory());

        auto index = transaction->ConsumeIndex(entity, mode);
        if (!index) {
            WARNING_LOG << "Cannot consume " << entity << Endl;
            return false;
        }
        if (IsReplaceMode(mode) && index->GetDocumentsCount(true /*keyWithDeleted*/) == 0) {
            WARNING_LOG << "Empty index " << entity << " will be " << (config.ApplyEmptyIndex ? "applied" : "rejected") << Endl;
            if (!config.ApplyEmptyIndex) {
                return false;
            }
        }
        if (indexProcessorCb && !indexProcessorCb(*index)) {
            WARNING_LOG << "Error in index processor callback for" << entity << Endl;
            return false;
        }
    }
    transaction->Commit();

    INFO_LOG << "Finished consume of resource " << resource.GetName().Quote() << Endl;

    if (mode == NRTYServer::EConsumeMode::Replace) {
        INFO_LOG << "Removing previous index segments for 'replace' mode..." << Endl;
        removeTask = storage.RemoveIndexes(previous, false);
        INFO_LOG << "Removing previous index segments for 'replace' mode... done" << Endl;
    }

    destination.ForceDelete();
    INFO_LOG << "Successfully finished FetchAndConsume of resource " << resource.GetName().Quote() << ", mode=" << mode << Endl;
    return true;
}

void NRTYServer::ClearFinalIndexes() {
    auto merger = TMergerEngine::GetMerger();
    CHECK_WITH_LOG(merger);
    TMergerStopGuard msg(true);
    auto& indexStorage = merger->GetIndexStorage();
    const auto& finalIndexes = indexStorage.GetFinalIndexes({});
    indexStorage.RemoveIndexes(finalIndexes, false);
}
