#pragma once
#include <saas/protos/rtyserver.pb.h>
#include <saas/rtyserver/indexer_core/guarded_document.h>
#include <saas/rtyserver/logging/rty_index.h>
#include <saas/library/indexer_protocol/protocol_abstract.h>
#include <saas/util/transaction.h>

#include <library/cpp/json/json_value.h>
#include <library/cpp/json/json_writer.h>

#include <util/system/mutex.h>
#include <util/generic/hash.h>
#include <util/generic/string.h>

class TRTYReplyWaiter {
private:
    NRTYServer::TReply Reply;
    TInstant StartTime;
    TInstant ModificationTime;
    TString OriginalMessage;
public:
    TRTYReplyWaiter() {
    }

    TRTYReplyWaiter(IDocumentForIndex& doc) {
        StartTime = Now();
        ModificationTime = Now();
        OriginalMessage = "indexing...";
        Reply.SetMessageId(doc.GetMessageId());
        Reply.SetStatus(NRTYServer::TReply::DATA_ACCEPTED);
        Reply.SetStatusMessage(GetStatusMessage());
    }

    NRTYServer::TReply::TRTYStatus GetStatus() {
        return (NRTYServer::TReply::TRTYStatus)Reply.GetStatus();
    }

    TString GetStatusMessage() {
        NJson::TJsonValue jv;
        if (!!OriginalMessage)
            jv.InsertValue("message", OriginalMessage);
        jv.InsertValue("start", StartTime.ToString());
        jv.InsertValue("last_modif", ModificationTime.ToString());
        TStringStream ss;
        NJson::WriteJson(&ss, &jv);
        return ss.Str();
    }

    void ChangeReply(const NRTYServer::TReply& reply) {
        VERIFY_WITH_LOG(Reply.GetStatus() == NRTYServer::TReply::DATA_ACCEPTED, "Incorrect ChangeReply method call");
        ModificationTime = Now();
        OriginalMessage = reply.GetStatusMessage();
        Reply.SetStatus(reply.GetStatus());
        Reply.SetStatusMessage(GetStatusMessage());
    }

};

class TDeferredRepliesProcessor {
private:
    typedef THashMap<TString, TMaybeFail<TRTYReplyWaiter> > TReplies;

private:
    TReplies Replies;
    TRWMutex Mutex;
    ITransaction Transaction;
public:
    enum TDeferredReplyCheck {drcNoNeed, drcWaiting, drcReady, drcStartNew};

public:
    TDeferredReplyCheck CheckDeferredReply(IDocumentForIndex* doc, const TString& serviceName);

    bool AddReply(const TString& hashDocument, const NRTYServer::TReply& reply) {
        TReadGuard rg(Mutex);
        TReplies::iterator i = Replies.find(hashDocument);
        VERIFY_WITH_LOG(i != Replies.end(), "Incorrect AddReply method usage");
        i->second->ChangeReply(reply);
        return true;
    }

    virtual ~TDeferredRepliesProcessor() {
        TGuardTransaction gt(Transaction);
    }
};

class TDeferredReplier: public IReplier {
private:
    TDeferredRepliesProcessor& Processor;
    TString HashDocument;
    TMaybe<TString> DocumentUrlMaybe;
    const TString& ServiceName;

protected:
    virtual bool DoReply() override {
        VERIFY_WITH_LOG(MessageId, "Incorrect DoReply method usage");
        return Processor.AddReply(HashDocument, BuildRTYReply());
    }

public:
    TDeferredReplier(TDeferredRepliesProcessor& processor, const TString& hashDocument, TMaybe<TString> documentUrlMaybe, const TString& serviceName, ITransaction& transaction, ui64 messageId)
        : IReplier(transaction, messageId)
        , Processor(processor)
        , HashDocument(hashDocument)
        , DocumentUrlMaybe(documentUrlMaybe)
        , ServiceName(serviceName)
    {
    }

    virtual void LogReply(const ui64 messageId, const TString& status, const ui32 httpCode, const ui64 duration, const TString& errorMessage) override {
        QueueReplyLogWithUnistat(ServiceName, DocumentUrlMaybe, messageId, status, httpCode, duration, errorMessage);
    }
};

class TFakeReplier: public IReplier {
protected:
    virtual bool DoReply() override {
        return true;
    }
public:
    TFakeReplier(ITransaction& transaction, ui64 messageId)
        : IReplier(transaction, messageId)
    {
    }

    virtual void LogReply(const ui64 /*messageId*/, const TString& /*status*/, const ui32 /*httpCode*/, const ui64 /*duration*/, const TString& /*errorMessage*/) override {
    }

};
