#include "backends_context.h"

#include <saas/indexerproxy/configs/config.h>
#include <saas/indexerproxy/dispatching/dispatch_queue.h>

#include <library/cpp/neh/neh.h>

TBackendsReplier::TBackendsReplier(const TString& serviceName, ISenderTask& task, const NRTYServer::TMessage& message, const TDispatcherConfig* config, TDeferredMessagesQueue& storage)
    : TReplier(serviceName, task, message, config, storage)
    , Handler(this, config->GetOwner().GetServicesConfig().GetConfig(serviceName))
{

}

bool TBackendsReplier::DoStoreStatus(const TString& backend, const TString& status) {
    auto i = Status.find(backend);
    VERIFY_WITH_LOG(i != Status.end(), "Incorrect StoreStatus usage");
    i->second->SetStoreStatus(status);
    if (!i->second->IsStored())
        ReplyOverride = NRTYServer::TReply::dsCANT_STORE_IN_QUEUE;
    return i->second->IsStored();
}

void TBackendsReplier::DoFinish() {
    if (IsAsync || !GetBehaviour(GetDispStatus()).NeedInFullDelivered) {
        return;
    }

    for (TSendStatusData::iterator i = Status.begin(), e = Status.end(); i != e; ++i) {
        if (i->second->IsNeedInResend()) {
            StoreInQueue(i->first, Message);
        }
    }
}

bool TBackendsReplier::DoSend(const TProxyConfig& config, TDispatchServiceSelector& selector) {
    TVector<std::pair<TString, ui16> > backends;
    const NSearchMapParser::TSearchCluster& unifiedService = config.GetServiceMap().find(GetServiceName())->second;

    TRoutesList blockedRoutes;
    const TRoutesList& routes = GetRoutesList(unifiedService, *this, blockedRoutes);
    for (TRoutesList::const_iterator iter = routes.begin(), end = routes.end(); iter != end; ++iter) {
        const TString queue = iter->Host + ':' + ToString(iter->Port);

        if (!GetContext().BackendAllowed(queue))
            continue;

        backends.push_back(std::make_pair(iter->Host, iter->Port));
    }

    SetBackendCounter(backends.size() + blockedRoutes.size());

    for (auto&& i : blockedRoutes) {
        NRTYServer::TQueryInfo qInfo;
        qInfo.Attempts = 1;
        qInfo.Duration = TDuration::Zero();
        qInfo.Host = i.Host;
        qInfo.Port = i.Port;
        qInfo.SendComplete = false;
        Handler.OnCancel(qInfo, "Blocked");
    }

    if (!backends.size() && !blockedRoutes.size()) {
        ForceMessageStatus(NRTYServer::TReply::dsIGNORED_BY_RANK, "no backends to send to");
        return false;
    }

    for (ui32 i = 0; i < backends.size(); ++i)
        selector.GetRequester().Send(backends[i].first, backends[i].second, GetMessage(), &Handler, "");
    return true;
}

NRTYServer::TReply::TDispStatus TBackendsReplier::DoGetDispStatus() const {
    TSet<NRTYServer::TReply::TDispStatus> finalStatuses;
    TSet<NRTYServer::TReply::TDispStatus> nonFinalStatuses;
    TSet<NRTYServer::TReply::TDispStatus> asyncStatuses;
    bool tolerable = true;

    for (TSendStatusData::const_iterator i = Status.begin(), e = Status.end(); i != e; ++i) {
        if (!i->second.Defined())
            continue;

        if (i->second->IsReplyRead()) {
            if (i->second->GetRTYStatus() == NRTYServer::TReply::INTERNAL_ERROR) {
                return NRTYServer::TReply::dsINTERNAL_ERROR;
            }

            const auto& b = GetBehaviour(i->second->GetRTYStatus());

            if (b.IsFinalStatus) {
                finalStatuses.insert(b.DispReplyType);
                tolerable &= b.IsTolerable;
            } else if (b.IsAsyncStatus) {
                asyncStatuses.insert(b.DispReplyType);
            } else {
                nonFinalStatuses.insert(b.DispReplyType);
            }
        } else if (IsAsync) {
            return NRTYServer::TReply::dsDATA_ACCEPTED;
        }
    }

    if (asyncStatuses.size()) {
        return *asyncStatuses.begin();
    }
    if (finalStatuses.size()) {
        if (finalStatuses.size() == 1) {
            return *finalStatuses.begin();
        }
        if (tolerable) {
            return NRTYServer::TReply::dsINCONSISTENCY;
        } else {
            return NRTYServer::TReply::dsDIFFERENT_ANSWERS;
        }
    }
    if (nonFinalStatuses.size()) {
        return *nonFinalStatuses.begin();
    }

    return NRTYServer::TReply::dsNO_REPLY;
}

bool TBackendsReplier::DoVerify(const TProxyConfig& config, const TRTYMessageBehaviour& bh) const {
    auto iter = config.GetServiceMap().find(GetServiceName());
    CHECK_WITH_LOG(config.GetServiceMap().end() != iter);
    if (!bh.IsBroadcastMessage) {
        TString error;
        if (iter->second.ShardsDispatcher->CheckMessage(GetMessage(), error)) {
            return true;
        }
        ForceMessageStatus(NRTYServer::TReply::dsUSER_ERROR, error);
        return false;
    }
    return true;
}

TDuration TBackendsReplier::THandlerOnSend::GetRecvTimeout() const {
    return RecvTimeout;
}

void TBackendsReplier::THandlerOnSend::OnNotify(const NRTYServer::TQueryInfo& stat, NNeh::TResponseRef& ref) const {
    Y_ASSERT(!!ref);
    if (ref->IsError()) {
        Replier->RegisterStatus(TSendStatus(stat, ref->GetErrorText()));
    } else {
        NRTYServer::TReply reply;
        if (reply.ParseFromArray(ref->Data.data(), ref->Data.size())) {
            Replier->RegisterStatus(TSendStatus(stat, reply));
        } else {
            Replier->RegisterStatus(TSendStatus(stat, "Can't parse reply"));
        }
    }
}

void TBackendsReplier::THandlerOnSend::OnCancel(const NRTYServer::TQueryInfo& stat, const TString& reason) const {
    DEBUG_LOG << "action=event;status=on_cancel" << Endl;
    Replier->RegisterStatus(TSendStatus(stat, reason));
}

void TBackendsReplier::THandlerOnSend::OnStart(const NRTYServer::TQueryInfo& stat) const {
    DEBUG_LOG << "action=event;status=on_start;host=" << stat.Host << ";port=" << stat.Port << Endl;
}

TBackendsReplier::THandlerOnSend::THandlerOnSend(ISrvDispContext* replier, const TProxyServiceConfig& config) {
    Replier = replier;
    TDuration delta = Now() - Replier->GetContext().GetContext().GetReceiveTime();
    TDuration waitTime;
    if (Replier->GetContext().GetContext().GetRequestOptions().HasTimeout()) {
        waitTime = Replier->GetContext().GetContext().GetRequestOptions().GetTimeout();
    } else {
        waitTime = config.InteractionTimeoutDuration * config.SendAttemptsCount;
    }
    if (waitTime > delta) {
        RecvTimeout = waitTime - delta;
    } else {
        RecvTimeout = TDuration::MilliSeconds(10);
    }
}

