#pragma once

#include <library/cpp/logger/global/global.h>
#include <saas/library/behaviour/behaviour.h>
#include <saas/indexerproxy/dispatching/send_status.h>

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

#include <util/generic/maybe.h>
#include <saas/indexerproxy/adapters/interface_adapter.h>
#include <yweb/realtime/distributor/client/distclient.h>
#include "action_reply.h"

class TLog;
class TDeferredMessagesQueue;
class TUkropModule;
class TDispatcherConfig;
class TProxyConfig;
class TDispatchServiceSelector;
struct TProxyServiceConfig;

class ISrvDispContext {
public:
    using TPtr = TAtomicSharedPtr<ISrvDispContext>;
    using TSendStatusData = TMap<TString, TMaybeFail<TSendStatus>>;
private:
    TMutex MutexRegisterStatus;
    TString ServiceName;
    ISenderTask& Task;
    TLog& Log;
    TMaybeFail<ui32> CounterReplies;
protected:
    mutable TActionReply ReplyOverride;
    TSendStatusData Status;
    const NRTYServer::TMessage& Message;
    const TProxyServiceConfig& ServiceConfig;

protected:
    virtual bool DoSend(const TProxyConfig& config, TDispatchServiceSelector& selector) = 0;
    virtual bool DoVerify(const TProxyConfig& config, const TRTYMessageBehaviour& bh) const = 0;
    virtual void DoFinish() = 0;
    virtual NRTYServer::TReply::TDispStatus DoGetDispStatus() const = 0;

public:
    static TAtomic GlobalCounter;

public:
    ISrvDispContext(const TString& serviceName, ISenderTask& task, const NRTYServer::TMessage& message, const TDispatcherConfig* config);

    virtual ~ISrvDispContext() {
        CHECK_EQ_WITH_LOG(*CounterReplies, 0);
    }

    virtual void Finish() final {
        DoFinish();
        Task.OnDocFinished();
    }

    const ISenderTask& GetContext() const {
        return Task;
    }

    const NRTYServer::TMessage& GetMessage() const {
        return Message;
    }

    void ForceMessageStatus(NRTYServer::TReply::TDispStatus status, const TString& message) const {
        ReplyOverride.SetInfo(status, message);
        WriteLog("dispatcher", status, message);
    }

    virtual bool Verify(const TProxyConfig& config) const final;

    virtual void Send(const TProxyConfig& config, TDispatchServiceSelector& selector) final;

    void FinishDocument() {
        Task.OnDocFinished();
    }

    const TString& GetServiceName() const {
        return ServiceName;
    }

    const TSendStatusData& GetStatuses() const {
        return Status;
    }

    void SetBackendCounter(ui32 value) {
        AtomicAdd(GlobalCounter, value);
        TGuard<TMutex> g(MutexRegisterStatus);
        VERIFY_WITH_LOG(!CounterReplies.Defined(), "Incorrect SetBackendCounter method usage");
        CounterReplies = value;
    }

    void CheckReplies() const {
        DEBUG_LOG << "check=" << Message.GetDocument().GetUrl() << Endl;
        VERIFY_WITH_LOG(!*CounterReplies, "Incorrect counter %d", *CounterReplies);
    }

    NRTYServer::TMessage::TMessageType GetMessageType() const {
        return Message.GetMessageType();
    }

    ui64 GetMessageId() const {
        if (Message.HasMessageId()) {
            return Message.GetMessageId();
        }
        return 0;
    }

    TString GetUrl() const {
        if (Message.HasDocument()) {
            return Message.GetDocument().GetUrl().Quote();
        }
        return "";
    }

    void WriteLog(const TSendStatus& status) const {
        TStringStream ss;
        NJson::WriteJson(&ss, &status.GetMessage());
        WriteLog(status.GetBackend(), status.GetDispStatus(), ss.Str());
    }

    void WriteLog(const TString& backend, ui32 status, const TString& description) const;

    void AccountStatus(const TSendStatus& status) {
        WriteLog(status);
        VERIFY_WITH_LOG(Status.find(status.GetBackend()) == Status.end(), "Incorrect AccountStatus usage");
        Status[status.GetBackend()] = status;
    }

    virtual NJson::TJsonValue GetReply() const = 0;

    NRTYServer::TReply::TDispStatus GetDispStatus() const {
        if (ReplyOverride.Defined()) {
            return ReplyOverride.GetDispStatus();
        }

        return DoGetDispStatus();
    }

    virtual bool DoStoreStatus(const TString& backend, const TString& status) = 0;

    void StoreStatus(const TString& backend, const TString& status) {
        if (!DoStoreStatus(backend, status)) {
            WriteLog(backend, 1, status);
            DEBUG_LOG << "For message url = " << GetUrl() << ". Store action into RMQ status = " << status << Endl;
        } else {
            WriteLog(backend, 0, "Store OK");
            DEBUG_LOG << "For message url = " << GetUrl() << ". Store action into RMQ status = STARTING" << Endl;
        }

    }

    void RegisterStatus(const TSendStatus& status);
};
