#pragma once

#include <library/cpp/logger/global/global.h>
#include <saas/protos/rtyserver.pb.h>
#include <saas/library/indexer_protocol/sender_neh.h>

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

#include <util/stream/str.h>
#include <saas/library/behaviour/behaviour.h>

enum ESendStatus {
    SS_OK,
    SS_READ_FAILED,
    SS_SEND_FAILED
};

enum class ESource {
    RTYServer,
    Distributor,
    PersQueue,
    Undefined
};

struct TSendStatus {
private:
    TString Backend;
    ESendStatus Status;
    NRTYServer::TReply::TRTYStatus BackendReplyStatus;
    NJson::TJsonValue Message;
    bool Stored;
    ESource Source;

private:
    void AddMessage(const TString& info, const TString& data) {
        Message.InsertValue(info, data);
    }

    NRTYServer::TReply::TRTYStatus ConvertStatus(NRealTime::TIndexedDocDepSlip::EStatus status) const {
        switch (status) {
        case NRealTime::TIndexedDocDepSlip::Ok:     return NRTYServer::TReply::OK;
        case NRealTime::TIndexedDocDepSlip::Reject: return NRTYServer::TReply::INCORRECT_DOCUMENT;
        case NRealTime::TIndexedDocDepSlip::Error:  return NRTYServer::TReply::INCORRECT_DOCUMENT;
        default:                                    return NRTYServer::TReply::INTERNAL_ERROR;
        }
    }
public:
    bool IsNeedInResend() const {
        if (Source != ESource::RTYServer)
            return false;

        if (!IsReplyRead())
            return true;

        const auto& behaviour = GetBehaviour(GetRTYStatus());
        return !behaviour.IsAsyncStatus && !behaviour.IsFinalStatus && behaviour.NeedInResend;
    }

    bool IsReplyRead() const {
        return Status == SS_OK;
    }

    TSendStatus() {

    }

    void SetStoreStatus(const TString& status) {
        if (!status) {
            Stored = true;
            AddMessage("Stored", "true");
        } else
            AddMessage("Stored", "false");
    }

    bool IsStored() const { return Stored; }
    ESendStatus GetDispStatus() const { return Status; }
    ESource GetSource() const { return Source; }
    NRTYServer::TReply::TRTYStatus GetRTYStatus() const { return BackendReplyStatus; }
    const TString& GetBackend() const { return Backend; }
    const NJson::TJsonValue& GetMessage() const { return Message; }

    inline TSendStatus(const NRTYServer::TQueryInfo& info, const NRTYServer::TReply reply)
        : Backend(info.Host + TString(":") + ToString(info.Port))
        , Status(SS_OK)
        , BackendReplyStatus(NRTYServer::TReply::TRTYStatus(reply.GetStatus()))
        , Message(NJson::JSON_MAP)
        , Stored(false)
        , Source(ESource::RTYServer)
    {
        AddMessage("RTYStatus", ::ToString((int)reply.GetStatus()));
        AddMessage("time_a", ::ToString(info.Duration.MilliSeconds()));
        AddMessage("iterations", ::ToString(info.Attempts));
        if (reply.HasStatusMessage() && !!reply.GetStatusMessage())
            AddMessage("RTYMessage", reply.GetStatusMessage());
    }

    inline TSendStatus(const NRTYServer::TQueryInfo& info, const TString& message)
        : Backend(info.Host + TString(":") + ToString(info.Port))
        , Status(info.SendComplete ? SS_READ_FAILED : SS_SEND_FAILED)
        , Stored(false)
        , Source(ESource::RTYServer)
    {
        AddMessage("time", ::ToString(info.Duration.MilliSeconds()));
        AddMessage("iterations", ::ToString(info.Attempts));
        AddMessage("DispStatus", ::ToString((int)Status));
        AddMessage("Info", message);
    }

    inline TSendStatus(const NBus::TNetAddr& addr, const NRealTime::TIndexedDocDepSlip::EStatus status, const TString& message)
        : Backend(ToString(addr))
        , Status(SS_OK)
        , BackendReplyStatus(ConvertStatus(status))
        , Stored(false)
        , Source(ESource::Distributor)
    {
        AddMessage("DistributorCode", NRealTime::TIndexedDocDepSlip::EStatus_Name(status));
        AddMessage("Message", message);
    }

    inline TSendStatus(const NBus::TNetAddr& addr, const TString& error)
        : Backend(ToString(addr))
        , Status(SS_SEND_FAILED)
        , Stored(false)
        , Source(ESource::Distributor)
    {
        AddMessage("SendError", error);
    }

    inline TSendStatus(ESource source, const TString& backend, const ESendStatus& status, bool stored = false, const TString& message = Default<TString>())
        : Backend(backend)
        , Status(status)
        , Stored(stored)
        , Source(source)
    {
        if (message) {
            AddMessage("Message", message);
        }
    }
};

