#include "behaviour.h"

#include <util/generic/algorithm.h>
#include <util/generic/hash.h>

namespace {
    struct TBehaviourMap {
        TBehaviourMap(
            const TVector<TRTYStatusBehaviour>& status,
            const TVector<TDispStatusBehaviour>& disp,
            const TVector<TRTYMessageBehaviour>& message)
        {
            for (const auto& x: status) {
                ByRTYStatusType[x.ReplyType] = x;
            }
            for (const auto& x: disp) {
                ByDispStatusType[x.ReplyType] = x;
                Y_ENSURE(ByHttpStatus.emplace(x.HttpCode, x).second, "Http code " << x.HttpCode << " is duplicated");
            }
            for (const auto& x: message) {
                ByMessageType[x.MessageType] = x;
                ByMessageName[x.Name] = x;
            }
        }

        THashMap<NRTYServer::TMessage::TMessageType, TRTYMessageBehaviour> ByMessageType;
        THashMap<TString, TRTYMessageBehaviour> ByMessageName;
        THashMap<NRTYServer::TReply::TRTYStatus, TRTYStatusBehaviour> ByRTYStatusType;
        THashMap<NRTYServer::TReply::TDispStatus, TDispStatusBehaviour> ByDispStatusType;
        THashMap<ui32, TDispStatusBehaviour> ByHttpStatus;
    };

    TBehaviourMap BehaviourMap{
        {
            TRTYStatusBehaviour{
                .ReplyType = NRTYServer::TReply::OK,
                .DispReplyType = NRTYServer::TReply::dsOK,
                .Name = "OK",
                .IsFinalStatus = true,
                .IsTolerable = true
            },
            TRTYStatusBehaviour{
                .ReplyType = NRTYServer::TReply::INCORRECT_DOCUMENT,
                .DispReplyType = NRTYServer::TReply::dsUSER_ERROR,
                .Name = "INCORRDOC",
                .IsFinalStatus = true
            },
            TRTYStatusBehaviour{
                .ReplyType = NRTYServer::TReply::INCORRECT_UPDATE,
                .DispReplyType = NRTYServer::TReply::dsUSER_ERROR,
                .Name = "INCORRUP",
                .IsFinalStatus = true
            },
            TRTYStatusBehaviour{
                .ReplyType = NRTYServer::TReply::DATA_ACCEPTED,
                .DispReplyType = NRTYServer::TReply::dsDATA_ACCEPTED,
                .Name = "DATAACC",
                .IsAsyncStatus = true,
                .NeedInResend = true
            },
            TRTYStatusBehaviour{
                .ReplyType = NRTYServer::TReply::INTERNAL_ERROR,
                .DispReplyType = NRTYServer::TReply::dsINTERNAL_ERROR,
                .Name = "INTERR",
                .IsFinalStatus = true
            },
            TRTYStatusBehaviour{
                .ReplyType = NRTYServer::TReply::DEPRECATED,
                .DispReplyType = NRTYServer::TReply::dsDEPRECATED,
                .Name = "DEPR",
                .IsFinalStatus = true,
                .IsTolerable = true
            },
            TRTYStatusBehaviour{
                .ReplyType = NRTYServer::TReply::NOTNOW,
                .DispReplyType = NRTYServer::TReply::dsNOTNOW,
                .Name = "NOTNOW",
                .NeedInResend = true
            },
            TRTYStatusBehaviour{
                .ReplyType = NRTYServer::TReply::NOTNOW_ONSTOP,
                .DispReplyType = NRTYServer::TReply::dsNOTNOW,
                .Name = "ONSTOP",
                .NeedInResend = true
            }
        }, {
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsOK, .Name = "OK", .HttpCode = 200,
                .NeedInFullDelivered = true
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsDEPRECATED, .Name = "DEPR", .HttpCode = 304
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsIGNORED_BY_RANK, .Name = "LOWRANK", .HttpCode = 320,
                .IgnoreInMerge = true
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsNOTNOW, .Name = "NOTNOW", .HttpCode = 503
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsDATA_ACCEPTED, .Name = "DATA_ACCEPTED", .HttpCode = 202,
                .NeedInFullDelivered = true,  .IsAsyncCode = true
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsINTERNAL_ERROR, .Name = "INTERR", .HttpCode = 500
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsNO_REPLY, .Name = "NOREP", .HttpCode = 598
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsUSER_ERROR, .Name = "USRERR", .HttpCode = 400
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsDIFFERENT_ANSWERS, .Name = "DIFFANS", .HttpCode = 512
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsINCONSISTENCY, .Name = "INCONSISTENCY", .HttpCode = 409
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsUNDEFINED_REPLY_CODE, .Name = "UNDEFRPL", .HttpCode = 501
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsCANT_STORE_IN_QUEUE, .Name = "CANTSTORE", .HttpCode = 513
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsINTERNAL_TIMEOUT, .Name = "INTIMEOUT", .HttpCode = 514
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsQUORUM_PASSED, .Name = "DQPASS", .HttpCode = 206
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsQUORUM_FAILED, .Name = "DQFAIL", .HttpCode = 563
            },
            TDispStatusBehaviour{
                .ReplyType = NRTYServer::TReply::dsDOCUMENT_DISCARDED, .Name = "VOID", .HttpCode = 302
            }
        }, {
            TRTYMessageBehaviour{
                .MessageType = NRTYServer::TMessage::ADD_DOCUMENT, .Name = "add",
                .IsContentMessage = true, .IsDuplicatable = true
            },
            TRTYMessageBehaviour{
                .MessageType = NRTYServer::TMessage::MODIFY_DOCUMENT, .Name = "modify",
                .IsContentMessage = true, .IsModification = true, .IsDuplicatable = true
            },
            TRTYMessageBehaviour{
                .MessageType = NRTYServer::TMessage::DELETE_DOCUMENT, .Name = "delete",
                .IsContentMessage = true, .IsModification = true, .IsRequestProcessor = true, .IsPureDeletion = true
            },
            TRTYMessageBehaviour{
                .MessageType = NRTYServer::TMessage::DEPRECATED__UPDATE_DOCUMENT, .Name = "update",
                .IsContentMessage = true, .IsModification = true, .IdentifyByContent = true
            },
            TRTYMessageBehaviour{
                .MessageType = NRTYServer::TMessage::REOPEN_INDEXERS, .Name = "reopen",
                .IsBroadcastMessage = true, .IsAsync = true, .IsPrefixOmitted = true
            },
            TRTYMessageBehaviour{
                .MessageType = NRTYServer::TMessage::SWITCH_PREFIX, .Name = "switch",
                .IsBroadcastMessage = true
            }
        }
    };
}

NRTYServer::TReply::TDispStatus DecodeHttpToDisp(ui32 httpCode) {
    if (const auto* b = GetDispBehaviour(httpCode)) {
        return b->ReplyType;
    } else {
        return NRTYServer::TReply::dsINTERNAL_ERROR;
    }
}

const TDispStatusBehaviour* GetDispBehaviour(ui32 httpCode) {
    return BehaviourMap.ByHttpStatus.FindPtr(httpCode);
}

const TRTYMessageBehaviour& GetBehaviour(const NRTYServer::TMessage::TMessageType& message) {
    return BehaviourMap.ByMessageType.at(message);
}

const TRTYMessageBehaviour* GetBehaviour(const TString& name) {
    return BehaviourMap.ByMessageName.FindPtr(name);
}

bool IsAsyncMessage(const NRTYServer::TMessage& message) {
    return IsQueryProcessorMessage(message.GetMessageType(), message.GetDocument())
        || GetBehaviour(message.GetMessageType()).IsAsync;
}

const TDispStatusBehaviour& GetBehaviour(const NRTYServer::TReply::TDispStatus& status) {
    return BehaviourMap.ByDispStatusType.at(status);
}

const TRTYStatusBehaviour& GetBehaviour(const NRTYServer::TReply::TRTYStatus& status) {
    return BehaviourMap.ByRTYStatusType.at(status);
}

bool IsQueryProcessorMessage(const NRTYServer::TMessage::TMessageType& type, const TString& queryDel) {
    return type == NRTYServer::TMessage::DELETE_DOCUMENT && !!queryDel;
}

bool IsQueryProcessorMessage(const NRTYServer::TMessage::TMessageType& type, const NRTYServer::TMessage::TDocument& doc) {
    return type == NRTYServer::TMessage::DELETE_DOCUMENT && doc.GetBody().StartsWith("query_del:");
}

TVector<ui32> GetDispCodes() {
    TVector<ui32> result;
    for (const auto& [status, _] : BehaviourMap.ByHttpStatus) {
        result.push_back(status);
    }
    Sort(result);
    return result;
}

template<>
void Out<NRTYServer::TMessage_TMessageType>(IOutputStream& output, NRTYServer::TMessage_TMessageType messType) {
    output << NRTYServer::TMessage_TMessageType_Name(messType);
}
