#pragma once
#include "parsed_document.h"
#include <saas/protos/rtyserver.pb.h>
#include <saas/library/indexer_protocol/protocol_abstract.h>
#include <util/generic/noncopyable.h>
#include <util/digest/fnv.h>

class IDocumentForIndex {
public:
    enum TDeferredReplyPolicy {drpNoAsync, drpAsyncResponse, drpEarlyResponse};

    virtual ~IDocumentForIndex() {}
    virtual TDeferredReplyPolicy GetDeferredPolicy() const = 0;
    virtual void SetStatus(NRTYServer::TReply::TRTYStatus status, const TString& errorMessage) = 0;
    virtual void ExchangeReplier(IReplier::TPtr Replier) = 0;
    virtual TString GetDocumentHash() = 0;
    virtual ui64 GetMessageId() const = 0;
    virtual TMaybe<TString> GetDocumentUrlMaybe() const = 0;
};

// This class uses for lock documents with identity url-s and provide command to indexer

class TGuardedDocument : TNonCopyable, public IDocumentForIndex {
    INamedLockManager::TNamedLockPtr NamedLock;
    TString LockName;
    typedef NRTYServer::TMessage::TDocument TDocument;
    typedef NRTYServer::TMessage::TMessageType TMessage;
    TMessage Command;
    TParsedDocument::TPtr ParsedDoc;
    IReplier::TPtr Replier;

    bool RuntimeKey;
    bool CheckOnlyBeforeReply;
    bool Correct = true;

    TMaybeFail<ui64> MessageId;
    const TRTYServerConfig& Config;

    THolder<TTryGuardIncompatibleAction> TryGuard;
    THolder<TTryMultiGuardIncompatibleAction> TryMultiGuard;

    void InitDocument(const NRTYServer::TMessage::TDocument& document);
    void BuildLockName();
public:
    inline TGuardedDocument(const TRTYServerConfig& config)
        : Command(NRTYServer::TMessage::ADD_DOCUMENT)
        , ParsedDoc(new TParsedDocument(config))
        , RuntimeKey(true)
        , CheckOnlyBeforeReply(false)
        , Config(config)
    {}

    inline TGuardedDocument(TParsedDocument::TPtr parsedDoc)
        : Command(NRTYServer::TMessage::ADD_DOCUMENT)
        , ParsedDoc(parsedDoc)
        , RuntimeKey(true)
        , CheckOnlyBeforeReply(false)
        , Config(parsedDoc->GetConfig())
    {
        BuildLockName();
    }

    inline TGuardedDocument(const NRTYServer::TMessage& message, IReplier::TPtr replier, const TRTYServerConfig& config)
        : Command(message.GetMessageType())
        , ParsedDoc(new TParsedDocument(config))
        , Replier(replier)
        , RuntimeKey(!message.HasDocument() || !message.GetDocument().HasRealtime() || message.GetDocument().GetRealtime())
        , CheckOnlyBeforeReply(message.HasDocument() && message.GetDocument().HasCheckOnlyBeforeReply() && message.GetDocument().GetCheckOnlyBeforeReply())
        , MessageId(message.GetMessageId())
        , Config(config)
    {
        if (message.HasDocument()) {
            InitDocument(message.GetDocument());
        } else {
            TString ident = "CommandInMessage:" + ToString(message.GetMessageId()) + ":" + ToString((int)Command);
            LockName = ToString(FnvHash<ui64>(ident.data(), ident.size()));
        }
    }

    inline bool IsLocked() const {
        return !!NamedLock;
    }

    virtual ~TGuardedDocument();

    inline TDocSearchInfo GetDocSearchInfo() const {
        return ParsedDoc->GetDocSearchInfo();
    }

    bool IsEmpty() const;

    bool TryLockIndexer();
    bool UnLockIndexers();

    const TParsedDocument& GetDocument() const {
        return *ParsedDoc;
    }

    TParsedDocument& MutableDocument() {
        return *ParsedDoc;
    }

    TParsedDocument::TPtr GetDocumentPtr() {
        return ParsedDoc;
    }

    void SetDocument(TParsedDocument::TPtr newDocument) {
        ParsedDoc = newDocument;
    }

    bool& GetMutableRuntimeKey() {
        return RuntimeKey;
    }

    bool GetRuntimeKey() const {
        return RuntimeKey;
    }

    bool GetCheckOnlyBeforeReply() const {
        return CheckOnlyBeforeReply;
    }

    bool IsCorrect() const {
        return Correct;
    }

    void StoreLock(INamedLockManager::TNamedLockPtr lock) {
        VERIFY_WITH_LOG(!NamedLock, "Document %s already locked", LockName.data());
        NamedLock = lock;
    }

    inline void UnLock() {
        NamedLock.Destroy();
    }

    TMessage GetCommand() const {
        return Command;
    }

    TMessage& MutableCommand() {
        return Command;
    }

    TMaybeFail<ui64>& MutableMessageId() {
        return MessageId;
    }

    //IDocumentForIndex
    ui64 GetMessageId() const override {
        return MessageId.GetRef();
    }

    TString GetDocumentHash() override {
        VERIFY_WITH_LOG(!!LockName, "Incorrect GetDocumentHash usage");
        return LockName;
    }

    IDocumentForIndex::TDeferredReplyPolicy GetDeferredPolicy() const override {
        if (CheckOnlyBeforeReply) {
            return drpEarlyResponse;
        } else if (Command == NRTYServer::TMessage::DELETE_DOCUMENT) {
            if (!!ParsedDoc->GetQueryDel())
                return drpAsyncResponse;
        } else if (Command == NRTYServer::TMessage::REOPEN_INDEXERS) {
            return drpAsyncResponse;
        }
        return drpNoAsync;
    }

    void SetStatus(NRTYServer::TReply::TRTYStatus status, const TString& errorMessage) override {
        if (!!Replier)
            Replier->SetStatus(status, errorMessage);
    }

    NRTYServer::TReply::TRTYStatus GetStatus() {
        return Replier->GetStatus();
    }

    void ExchangeReplier(IReplier::TPtr replier) override {
        if (!!Replier)
            Replier->Reply();
        Replier = replier;
    }

    TMaybe<TString> GetDocumentUrlMaybe() const override final {
        TParsedDocument::TPtr ptrCopy = ParsedDoc;
        if (ptrCopy->GetDocSearchInfo().UrlInitialized()) {
            return ptrCopy->GetDocSearchInfo().GetUrl();
        } else {
            return Nothing();
        }
    }
};

using TQueuedDocument = TAtomicSharedPtr<TGuardedDocument>;
