#pragma once

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

#include <saas/rtyserver/config/globals.h>
#include <saas/rtyserver/common/common_rty.h>
#include <saas/rtyserver/common/thread_status.h>
#include <saas/rtyserver/config/merger_config.h>
#include <saas/rtyserver/merger/library/merger.h>
#include <saas/rtyserver/synchronizer/library/sync.h>
#include <saas/util/app_exception.h>
#include <saas/util/transaction.h>

namespace NJson {
    class TJsonValue;
}

namespace NRTYServer {
    class IIndexManagersStorage;
    class IIndexOwner;
}

class IIndexWithComponents : public IIndexController {
public:
    virtual const NRTYServer::IIndexManagersStorage& GetManagers() const = 0;
    virtual NRTYServer::IIndexOwner& GetIndexOwner() = 0;
};

typedef TAtomicSharedPtr<IIndexWithComponents> TIndexControllerPtr;

class IIndexConsumeTransaction {
public:
    virtual ~IIndexConsumeTransaction() {}

    virtual TIndexControllerPtr ConsumeIndex(const TString& indexDirName, NRTYServer::EConsumeMode mode) = 0;
    virtual void Commit() = 0;
    virtual void Revert() = 0;
};

class IIndexStorage {
public:
    class TDeferredRemoveTask {
    public:
        virtual ~TDeferredRemoveTask() noexcept(false) {}
        virtual void DestroyIndexes() = 0;
    };

    typedef TAtomicSharedPtr<TDeferredRemoveTask> TDeferredRemoveTaskPtr;

public:
    virtual ~IIndexStorage() {}

    virtual bool HasIndexFiles(const TString& dirName) const = 0;
    virtual bool IsFinalIndex(const TString& dirName) const = 0;
    virtual bool IsEmptyIndex(const TString& dirName) const = 0;
    virtual ui32 GetDocsCount(const TString& dirName, bool withDeleted) const = 0;
    virtual TString GetFinalIndexDirs(bool fullPath) const = 0;
    virtual TString GetFullPath(const TString& indexDirOrName, const TString& path) const = 0;
    virtual TVector<TString> GetFinalIndexes(const TString& path) const = 0;
    virtual TVector<TString> GetFinalIndexesUnsafe(const TString& path) const = 0;
    virtual TIndexControllerPtr GetIndexController(const TString& dir) const = 0;

    virtual TDeferredRemoveTaskPtr RemoveIndexes(const TVector<TString>& indexDirs, bool ignoreIndexOrigin) = 0;
    TDeferredRemoveTaskPtr RemoveIndex(const TString& pindexDir, bool ignoreIndexOrigin) {
        return RemoveIndexes(TVector<TString>(1, pindexDir), ignoreIndexOrigin);
    }

    virtual void AllocateIndex(TString& indexDirName, TString& tempDirName, int shard = 0, bool isDiskIndex = true, bool isPrepIndex = false) = 0;
    virtual void AllocateIndexUnsafe(TString& indexDirName, TString& tempDirName, int shard = 0, bool isDiskIndex = true, bool isPrepIndex = false) = 0;
    virtual bool PrepareIndexForConsumption(const TString& indexDirName, bool storeInIndexRoot, TString& finalIndex) = 0;
    virtual TIndexControllerPtr ConsumeIndex(const TString& indexDirName, NRTYServer::EConsumeMode mode) = 0;
    virtual TIndexControllerPtr ConsumeAllocatedIndex(const TString& finalIndex, NRTYServer::EConsumeMode mode) = 0;
    virtual THolder<IIndexConsumeTransaction> StartIndexConsumeTransaction() = 0;
};

class IMergerTask;

class IMergerTaskNotifier {
public:
    virtual ~IMergerTaskNotifier() {}

    virtual void OnMergerTaskFinish(const IMergerTask*) = 0;
    virtual void OnMergerTaskStart(const IMergerTask*) = 0;
    virtual void OnMergerTaskFailed() = 0;
};

class IMergerLock {
public:
    using TPtr = THolder<IMergerLock>;

public:
    virtual ~IMergerLock() {}
};

class IMergerLockPolicy {
public:
    using TPtr = TAtomicSharedPtr<IMergerLockPolicy>;
    using TFactory = NObjectFactory::TParametrizedObjectFactory<IMergerLockPolicy, NRTYServer::TMergerConfig::ELockPolicy, const TRTYServerConfig&>;

public:
    virtual ~IMergerLockPolicy() {}

    virtual IMergerLock::TPtr LockOnStart(IMergerTask* task, const std::atomic<bool>* rigidStop) const = 0;
    virtual IMergerLock::TPtr LockOnFinish(IMergerTask* task, const std::atomic<bool>* rigidStop) const = 0;
};

class IMergerTask: public IMergerCallback {
protected:
    enum TMergerTaskStatus { mtsUnknown, mtsStarted, mtsActive, mtsFinished, mtsFailed };

private:
    const TString Path;
    IMergerLockPolicy::TPtr LockPolicy;
    IMergerLock::TPtr StartLock;
    IMergerLock::TPtr FinishLock;

    TMergerTaskStatus Status = mtsUnknown;
    TVector<TString> ProgressInfo;
    TString ProgressStatus;
    TMutex MutexStatus;
    TString RealmName;

protected:
    bool IsDecoderBuilt = false;
    bool IsSourcesBuilt = false;
    bool IsTempDestinationsBuilt = false;
    bool IsFullDestinationsBuilt = false;
    TVector<TString> Sources;
    TVector<TString> TempDestinations;
    TVector<TString> TmpfsDestinations;
    TVector<TString> FullDestinations;
    TInstant MainStageStartTime;
    TInstant MainStageFinishTime;

private:
    void BuildTempDestinations(IIndexStorage& storage);
    void BuildFullDestinations(IIndexStorage& storage);
    void BuildSources(IIndexStorage& storage);
    void BuildTemp(IIndexStorage& storage);

protected:
    virtual bool DoOnStart(const std::atomic<bool>* rigidStop) = 0;
    virtual void DoOnBeforeMainStage() = 0;
    virtual void DoOnFinished(const std::atomic<bool>* rigidStop) = 0;
    virtual void DoOnFailed() = 0;

    virtual TString DoBuildTmpfsDest(IIndexStorage& storage, const TString& segment) const {
        return DoBuildTempDest(storage, segment) + GetDirectorySeparator() + "pseudo_tmpfs" + GetDirectorySeparator(); // To Delete on DoOnFailed
    }
    virtual TString DoBuildTempDest(IIndexStorage& storage, const TString& segment) const = 0;
    virtual TString DoBuildFullDest(IIndexStorage& storage, const TString& segment) const = 0;
    virtual void DoBuildDecoder(IIndexStorage& storage) = 0;

public:
    typedef TAtomicSharedPtr<IMergerTask> TPtr;

public:
    IMergerTask(const TString& path, IMergerLockPolicy::TPtr lockPolicy, const TString& realmName)
        : Path(path)
        , LockPolicy(lockPolicy)
        , RealmName(realmName)
    {
    }

    virtual ~IMergerTask() {
        CHECK_WITH_LOG(Status == mtsFinished || Status == mtsFailed || Status == mtsUnknown);
    }

    virtual TRTYMerger::IRTYMergerDocIdDecoder* GetDecoder() = 0;
    virtual TRTYMerger::IRTYMergerDocIdInfo* GetInfo() = 0;
    virtual void MoveFromTemp(ui32 destIndex, IIndexStorage& storage, const std::atomic<bool>* rigidStop) = 0;

    void AddJsonInfo(NJson::TJsonValue& info) const;

    void SetProgressInfo(const TString& progressStatus) {
        TGuard<TMutex> g(MutexStatus);
        ProgressStatus = NLoggingImpl::GetLocalTimeS() + ":" + progressStatus;
    }

    void AddProgressInfo(const TString& progressInfo) {
        TGuard<TMutex> g(MutexStatus);
        INFO_LOG << GetName() << " merger task info: " << progressInfo << Endl;
        ProgressInfo.push_back(NLoggingImpl::GetLocalTimeS() + ":" + progressInfo + ";" + NLoggingImpl::GetSystemResources());
    }

    void BuildDirectoriesInfo(IIndexStorage& storage) {
        BuildSources(storage);
        BuildTempDestinations(storage);
        BuildFullDestinations(storage);
    }

    void BuildDecoder(IIndexStorage& storage) {
        if (!IsDecoderBuilt)
            DoBuildDecoder(storage);
        IsDecoderBuilt = true;
    }

    virtual const TVector<TString>& GetSources() const {
        CHECK_WITH_LOG(IsSourcesBuilt);
        return Sources;
    }

    virtual const TVector<TString>& GetTempDestinations() const {
        CHECK_WITH_LOG(IsTempDestinationsBuilt);
        return TempDestinations;
    }

    virtual const TVector<TString>& GetTmpfsDestinations() const {
        CHECK_WITH_LOG(IsTempDestinationsBuilt);
        return TmpfsDestinations;
    }

    virtual const TVector<TString>& GetFullDestinations() const {
        CHECK_WITH_LOG(IsFullDestinationsBuilt);
        return FullDestinations;
    }

    virtual int GetShardNumber() const = 0;
    virtual const TVector<TString>& GetSourceSegments() const = 0;
    virtual const TVector<TString>& GetDestSegments() const = 0;
    virtual TString GetName() const = 0;
    virtual bool NeedWriteSourceIndexes() const = 0;
    virtual bool GetIsPortions() const = 0;
    virtual NRTYServer::EExecutionContext GetExecutionContext() const {
        return NRTYServer::EExecutionContext::Merge;
    }

    TString GetRealmName() {
        return RealmName;
    }

    void OnFinished(const std::atomic<bool>* rigidStop) {
        VERIFY_WITH_LOG(Status == mtsStarted || Status == mtsActive, "Status=%i", (int)Status);
        Status = mtsFinished;
        FinishLock = LockPolicy ? LockPolicy->LockOnFinish(this, rigidStop) : nullptr;
        DoOnFinished(rigidStop);
    };

    void OnFailed() {
        VERIFY_WITH_LOG((Status != mtsFinished) && (Status != mtsFailed), "Status=%i", (int)Status);
        Status = mtsFailed;
        DoOnFailed();
    }

    bool OnStart(const std::atomic<bool>* rigidStop) {
        VERIFY_WITH_LOG(Status == mtsUnknown, "Status=%i", (int)Status);
        Status = mtsStarted;
        StartLock = LockPolicy ? LockPolicy->LockOnStart(this, rigidStop) : nullptr;
        return DoOnStart(rigidStop);
    }

    void OnBeforeMainStage() {
        VERIFY_WITH_LOG(Status == mtsStarted, "Status=%i", (int)Status);
        Status = mtsActive;
        MainStageStartTime = TInstant::Now();
        DoOnBeforeMainStage();
    }

    void OnAfterMainStage() {
        MainStageFinishTime = TInstant::Now();
    }

    const TString& GetPath() const {
        return Path;
    }

    bool IsFinished() const {
        return Status == mtsFinished;
    }

    TDuration GetMainStageDuration() const {
        return MainStageFinishTime - MainStageStartTime;
    }

protected:
    inline TMergerTaskStatus GetTaskStatus() const {
        return Status;
    }
};

struct TMergerMetrics;

class IMerger {
public:
    virtual ~IMerger() {}

    virtual void Start() = 0;
    virtual void Stop(bool rigidStop) = 0;
    virtual TThreadStatus GetStatus() const = 0;

    virtual IMergerTask::TPtr NextTask(unsigned timeWait) = 0;
    virtual bool Y_WARN_UNUSED_RESULT AddTask(unsigned shard, TVector<TString> segments, const TString& path, IMergerTaskNotifier& notifier, IMergerLockPolicy::TPtr lockPolicy, bool isPortions) = 0;

    virtual IIndexStorage& GetIndexStorage() = 0;
    virtual TMergerMetrics& GetMetrics() = 0;

    virtual void WaitMergingFinished(const TString& path) const = 0;
    virtual void DoTask(IMergerTask::TPtr task, const std::atomic<bool>* rigidStop) = 0;
    virtual TMutex& GetResourcesMutex() = 0;
};

class TMergeCancelException: public NUtil::TApplicationException {};

class TMergerEngine {
private:
    IMerger* Merger;
public:
    TMergerEngine();
    static void RegisterMerger(IMerger& merger);
    static IMerger* GetMerger();
};

class TMergerStopGuard {
public:
    TMergerStopGuard(bool rigid);
    ~TMergerStopGuard();
};
